前段时间,我决定在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"
如果我在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')
这比OCaml代码更短更清晰。关于如何改进我的OCaml代码的任何建议?还有两个重要的事情:我不想放弃OCaml,我只想学习它;第二 - 我想因为同样的原因而使用Core。
答案 0 :(得分: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)
但是,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 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
最后,让我们从文件中读取数据:
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"
我们还检查我们的状态机是否达到了正确的完成状态,即它处于Query
或Entry 0
状态。
答案 1 :(得分:4)
与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 -> ()