如何从stdin轻松读取行?

gri*_*kov 3 ocaml

前段时间,我决定在HackerRank上解决一个简单的任务,但是使用OCaml和Core,以便学习它们.在其中一项任务中,我应该从标准输入中读取数据:

第一行包含一个整数,表示电话簿中的条目数.每个后续行在单行上以空格分隔值的形式描述条目.第一个值是朋友的名字,第二个值是-digit电话号码.

在电话簿条目行之后,存在未知数量的查询行.每行(查询)包含一个要查找的行,您必须继续读取行,直到没有更多输入.

主要问题:

  • 我不知道会有多少行
  • 最后一行不以换行结束,所以我不能scanf "%s\n"直到读End_of_file

我的代码变得凌乱:

open Core.Std
open Printf
open Scanf


let read_numbers n =
    let phone_book = String.Table.create () ~size:n in

    for i = 0 to (n - 1) do
        match In_channel.input_line stdin with
        | Some line -> (
            match (String.split line ~on:' ') with
            | key :: data :: _ -> Hashtbl.set phone_book ~key ~data
            | _ -> failwith "This shouldn't happen"
        )
        | None -> failwith "This shouldn't happen"
    done;

    phone_book


let () =
    let rec loop phone_book =
        match In_channel.input_line stdin with
        | Some line -> (
            let s = match Hashtbl.find phone_book line with
                | Some number -> sprintf "%s=%s" line number
                | None -> "Not found"
            in
            printf "%s\n%!" s;
            loop phone_book
        )
        | None -> ()
    in

    match In_channel.input_line stdin with
    | Some n -> (
        let phone_book = read_numbers (int_of_string n) in
        loop phone_book
    )
    | None -> failwith "This shouldn't happen"
Run Code Online (Sandbox Code Playgroud)

如果我在Python中解决此任务,那么代码如下所示:

n = int(input())
book = dict([tuple(input().split(' ')) for _ in range(n)])

while True:
    try:
        name = input()
    except EOFError:
        break
    else:
        if name in book:
            print('{}={}'.format(name, book[name]))
        else:
            print('Not found')
Run Code Online (Sandbox Code Playgroud)

这比OCaml代码更短更清晰.关于如何改进我的OCaml代码的任何建议?还有两个重要的事情:我不想放弃OCaml,我只想学习它; 第二 - 因为同样的原因我想使用Core.

ivg*_*ivg 7

在OCaml中直接实现Python代码如下所示:

let exec name =
  In_channel.(with_file name ~f:input_lines) |> function
  | [] -> invalid_arg "Got empty file"
  | x :: xs ->
    let es,qs = List.split_n xs (Int.of_string x) in
    let es = List.map es ~f:(fun entry -> match String.split ~on:' ' entry with
        | [name; phone] -> name,phone
        | _ -> invalid_arg "bad entry format") in
    List.iter qs ~f:(fun name ->
        match List.Assoc.find es name with
        | None -> printf "Not found\n"
        | Some phone -> printf "%s=%s\n" name phone)
Run Code Online (Sandbox Code Playgroud)

但是,OCaml不是用于编写小脚本和一次性原型的脚本语言.它是编写真实软件的语言,必须具有可读性,可支持性,可测试性和可维护性.这就是为什么我们有类型,模块和所有东西.所以,如果我正在编写一个生产质量计划,负责处理这样的输入,那么它看起来会有很大不同.

当我用函数式语言编写程序时,我个人使用的一般风格是遵循以下两个简单的规则:

  1. 如有疑问,请使用更多类型.
  2. 玩得开心(很有趣).

即,为程序域中的每个概念分配一个类型,并使用许多小函数.

以下代码是两倍大,但更具可读性,可维护性和健壮性.

所以,首先,让我们输入:条目只是一个记录.为简单起见,我使用字符串类型来表示手机.

type entry = {
  name : string;
  phone : string;
}
Run Code Online (Sandbox Code Playgroud)

在任务中没有指定查询,所以让我们用字符串将其存根:

type query = Q of string
Run Code Online (Sandbox Code Playgroud)

现在我们的解析器状态.我们有三种可能的状态:Start状态,状态Entry n,我们正在解析n目前为止留下条目的条目,以及Query当我们解析查询时的状态.

type state =
  | Start
  | Entry of int
  | Query
Run Code Online (Sandbox Code Playgroud)

现在我们需要为每个状态编写一个函数,但首先,让我们定义一个错误处理策略.对于一个简单的程序,我建议只是在解析器错误上失败.expect当我们的期望失败时,我们将调用一个名为的函数

let expect what got =
  failwithf "Parser error: expected %s got %s\n" what got ()
Run Code Online (Sandbox Code Playgroud)

现在三个解析函数:

let parse_query s = Q s

let parse_entry s line = match String.split ~on:' ' line with
  | [name;phone] ->  {name;phone}
  | _ -> expect "<name> <phone>" line

let parse_expected s =
  try int_of_string s with exn ->
    expect "<number-of-entries>" s
Run Code Online (Sandbox Code Playgroud)

现在让我们编写解析器:

let parse (es,qs,state) input = match state with
  | Start -> es,qs,Entry (parse_expected input)
  | Entry 0 -> es,qs,Query
  | Entry n -> parse_entry input :: es,qs,Entry (n-1)
  | Query -> es, parse_query input :: qs,Query
Run Code Online (Sandbox Code Playgroud)

最后,让我们从文件中读取数据:

let of_file name =
  let es,qs,state =
    In_channel.with_file name ~f:(fun ch ->
      In_channel.fold_lines ch ~init:([],[],Start) ~f:parse) in
  match state with
  | Entry 0 | Query -> ()
  | Start -> expect "<number-of-entries><br>..." "<empty>"
  | Entry n -> expect (sprintf "%d entries" n) "fewer"
Run Code Online (Sandbox Code Playgroud)

我们还检查我们的状态机是否达到了正确的完成状态,即它处于QueryEntry 0处于状态.


Rei*_*nds 6

与Python一样,简洁实现的关键是让标准库完成大部分工作;下面的代码用来Sequence.fold代替 Python 的列表理解。此外,使用Pervasives.input_line而不是In_channel.input_line允许您减少无关的模式匹配(它将报告文件结束条件作为异常而不是结果None)。

open Core.Std

module Dict = Map.Make(String)

let n = int_of_string (input_line stdin)
let d = Sequence.fold
  (Sequence.range 0 n)
  ~init:Dict.empty
  ~f:(fun d _ -> let line = input_line stdin in
      Scanf.sscanf line "%s %s" (fun k v -> Dict.add d ~key:k ~data:v))

let () =
  try while true do
    let name = input_line stdin in
    match Dict.find d name with
    | Some number -> Printf.printf "%s=%s\n" name number
    | None -> Printf.printf "Not found.\n"
  done with End_of_file -> ()
Run Code Online (Sandbox Code Playgroud)