2016-11-23 11 views
3

少し前に、私はHackerRankで単純なタスクを解決することに決めましたが、OCamlとCoreを使ってそれらを学びました。タスクの一つで、私は、標準入力からデータを読みになってる:stdinから簡単に行を読み込むには?

最初の行には、電話帳のエントリ の数を表す、整数が含まれています。後続の各行には、 のエントリが1行の空白で区切られた値の形式で記述されています。最初の値 は友人の名前で、2番目の値は数字の電話番号です。

電話帳エントリの行の後に、 のクエリ行が不明です。それぞれの行(照会)には、検索する項目が含まれており、入力がなくなるまで は行を読み取る必要があります。

主な問題:

  • は私が
  • 最終行が改行でない端をそこに行いますどのように多くの行を知らないので、私はちょうどEnd_of_file
  • まで scanf "%s\n"を読み取ることができません

そして、私のコードを乱雑になった:

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コードを改善する方法についてのアドバイスは?そして、2つの重要なことがあります。私はOCamlを放棄したくありません。ただ学びたいだけです。 2番目 - 同じ理由でCoreを使いたい。

答えて

6

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は小さなスクリプトとワンショットのプロトタイプを作成するためのスクリプト言語ではありません。これは、読み取り可能で、サポート可能で、テスト可能で、保守可能でなければならない、実際のソフトウェアを書くための言語です。そういうわけで、タイプ、モジュール、そしてすべてのものがあります。ですから、もし私が生産品質プログラムを書いていたら、それはそのような入力を扱うことに責任があり、それは非常に異なって見えます。疑いでより多くの種類を使用する場合

  1. :私は関数型言語でプログラムを書いている私は個人的に採用

    一般的なスタイルは、これら2つの簡単なルールに従うことです。

  2. 楽しんでください(たくさんの楽しみ)。

つまり、プログラムドメイン内の各概念にタイプを割り当て、小さな機能をたくさん使用します。

次のコードは2倍の大きさですが、読みやすく、保守性があり、堅牢です。

まず、入力しましょう。エントリは単なるレコードです。簡単にするために電話を表す文字列型を使用しました。

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() 

今3つの解析関数:

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 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" 

状態マシンが適切な終了状態に達したこと、つまりQueryまたはEntry 0の状態になっていることも確認します。

4

Pythonと同様に、簡潔な実装の鍵は、標準ライブラリにほとんどの作業をさせることです。次のコードでは、Pythonのリストの理解の代わりにSequence.foldを使用しています。また、In_channel.input_lineではなくPervasives.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 ->() 
+0

Nitですが、「シーケンス」は標準ライブラリにはないと思いますが、コアにあります。 – antron

+0

OPは、彼がCoreを使い続けたかったと具体的に述べました。そのため私は自分のコードをベースにしています(私は通常Coreをあまり使わない)。 –

+0

はい、理解して、それに反対しません。私が言いたいのは、最初の文章を読んでいる間、Sequenceがstdlibにあるというあいまいな示唆を感じました。後で他の読者を混乱させるかもしれません。私はそれが直接それを主張していないことを理解しています、それはちょっと混乱しています。 – antron

関連する問題