Ocaml 如何轻松地从stdin中读取行?

Ocaml 如何轻松地从stdin中读取行?,ocaml,Ocaml,不久前,我决定在HackerRank上解决一个简单的任务,但要使用OCaml和Core来学习它们。在其中一项任务中,我应该从标准输入读取数据: 第一行包含一个整数,表示条目数 在电话簿里。后面的每一行都描述了中的一个条目 单行上以空格分隔的值的形式。第一个值 是一个朋友的名字,第二个值是一个数字的电话号码 在电话簿条目行之后,有未知数量的 一行行的查询。每行(查询)包含一个要查找的 必须继续阅读行,直到没有更多的输入 主要问题: 我不知道会有多少行 最后一行不以换行符结尾,所以我不能只读取sc

不久前,我决定在HackerRank上解决一个简单的任务,但要使用OCaml和Core来学习它们。在其中一项任务中,我应该从标准输入读取数据:

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

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

主要问题:

  • 我不知道会有多少行
  • 最后一行不以换行符结尾,所以我不能只读取
    scanf“%s\n”
    ,直到
    End\u文件
我的代码变得一团糟:

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"
如果我用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')
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)

这比OCaml代码更短更清晰。关于如何改进我的OCaml代码有什么建议吗?有两件事很重要:我不想放弃OCaml,我只想学习它;第二,出于同样的原因,我想使用Core。

在OCaml中直接实现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')
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)
但是,OCaml不是一种用于编写小脚本和一次性原型的脚本语言。它是编写真实软件的语言,必须是可读的、可支持的、可测试的和可维护的。这就是为什么我们有类型,模块和所有的东西。所以,如果我在写一个生产质量程序,负责处理这些输入,那么它看起来会非常不同

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

  • 如有疑问,请使用更多类型
  • 玩得开心(很多)
  • 即,为程序域中的每个概念分配一个类型,并使用许多小功能

    下面的代码是原来的两倍,但可读性、可维护性和健壮性更强

    因此,首先,让我们键入:条目只是一条记录。为了简单起见,我使用了字符串类型来表示电话

    type entry = {
      name : string;
      phone : string;
    }
    
    该查询未在任务中指定,因此让我们用字符串将其存根:

    type query = Q of string
    
    现在我们的解析器状态。我们有三种可能的状态:
    Start
    状态,一种状态是
    Entry n
    ,在这里我们解析到目前为止剩下
    n
    项的条目,以及解析查询时的
    Query
    状态

    type state =
      | Start
      | Entry of int
      | Query
    
    现在我们需要为每个状态编写一个函数,但首先,让我们定义一个错误处理策略。对于一个简单的程序,我建议在解析器错误时失败。当预期失败时,我们将调用名为
    expect
    的函数:

    let expect what got =
      failwithf "Parser error: expected %s got %s\n" what got ()
    
    现在有三个解析函数:

    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
    
    最后,让我们从文件中读取数据:

    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"
    
    let of_文件名=
    让es,qs,state=
    在_channel.with _文件名~f:(fun ch->
    在_channel.fold中_行ch~init:([],[],Start)~f:parse)In
    匹配状态
    |条目0 |查询->()
    |开始->预期“
    …” |条目n->expect(sprintf“%d个条目”n)“更少”

    我们还检查状态机是否达到适当的完成状态,即它处于
    Query
    Entry 0
    状态

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


    不,但我认为
    Sequence
    不在标准库中,而是在Core中。OP明确表示他希望继续使用Core,这就是为什么我的代码基于此(我通常不太使用Core)。是的,理解,不反对。我的意思是,在阅读第一句话时,我感觉到一个模糊的暗示,
    Sequence
    在stdlib中,这以后可能会让其他读者感到困惑。我意识到它并没有直接宣称,只是有点让人困惑。它可能只是一个术语。核心、电池、ExtLib和容器通常都被描述为替代标准库,所以我倾向于使用OCaml这个术语。我现在意识到,如果您认为“标准库”指的是OCaml标准库,那么这可能会令人困惑。