2016-11-23 1 views
3

얼마 전, HackerRank에서 간단한 작업을 해결하기 위해 OCaml과 Core를 사용하기로 결정했습니다. 작업 중 하나, 나는 표준 입력에서 데이터를 읽을 생각 해요 :stdin에서 줄을 쉽게 읽는 방법?

첫 번째 줄은 전화 번호부에있는 항목 의 수를 나타내는 정수가 포함되어 있습니다. 후속 행은 각각 의 항목을 한 행에 공백으로 구분 된 값의 형식으로 설명합니다. 첫 번째 값인 은 친구의 이름이고 두 번째 값은 숫자로 된 전화 번호입니다.

전화 번호부 항목의 줄 뒤에 개의 쿼리 행을 알 수 없습니다. 각각의 라인 (질의)에는 룩업 (look up)을위한 A가 포함되어 있으며 더 이상의 입력이 없을 때까지 라인을 계속 읽어야합니다.

주요 문제 :

  • 내가
  • 마지막 줄 바꿈으로하지 끝을 할 수있을 것입니다 얼마나 많은 줄 모르는, 그래서 난 그냥 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" 
내가 파이썬에서이 작업을 해결하는 경우

는, 다음 코드는 다음과 같습니다

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를 사용하고 싶습니다.

답변

6

OCaml의에서 파이썬 코드의 직접 구현은 다음과 같습니다

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. 재미있게 보내십시오 (많은 즐거움).

즉, 프로그램 도메인에서 각 개념에 대한 유형을 할당하고 작은 기능을 많이 사용하십시오.

다음 코드는 두 배 크기이지만 읽기 쉽고 유지 보수가 용이하며 강력합니다.

먼저, 입력 해 봅시다. 항목은 단순히 레코드입니다. 단순화를 위해 전화를 나타 내기 위해 문자열 유형을 사용했습니다.

type entry = { 
    name : string; 
    phone : string; 
} 

쿼리

는 작업에 지정, 그래서 그냥 문자열로 스텁 못하게된다

이제
type query = Q of string 

우리 파서 상태. 가능한 상태는 Start이며 상태는 Entry n입니다. 여기서 우리는 n 항목을 지금까지 남겨두고 항목을 구문 분석하고 있으며 쿼리를 구문 분석 할 때 Query 상태가됩니다.

type state = 
    | Start 
    | Entry of int 
    | Query 

이제 각 상태에 대한 함수를 작성해야하지만 우선 오류 처리 정책을 정의합시다. 간단한 프로그램의 경우 파서 오류로 실패하는 것이 좋습니다.

let expect what got = 
    failwithf "Parser error: expected %s got %s\n" what got() 

이제 세 가지 분석 기능 :의 읽기하자, 마침내

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 

을의 파서를 쓸 수 우리의 기대가 실패 할 때 우리는 expect라는 이름의 함수를 호출합니다 파일의 데이터 :

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

파이썬에서와 같이 간결한 구현의 핵심은 표준 라이브러리가 대부분의 작업을 수행하도록하는 것입니다. 다음 코드는 파이썬의 목록 이해 대신 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,하지만 'Sequence'는 표준 라이브러리에는 없지만 코어에는 있다고 생각합니다. – antron

+0

OP는 구체적으로 그가 계속 Core를 사용하고 싶다고 말했고, 이것이 내가 Core 코드를 사용하는 이유입니다 (저는 보통 Core를 많이 사용하지 않습니다). –

+0

예, 이해합니다. 반대하지 않습니다. 제가 의미하는 바는, 첫 문장을 읽는 동안, Sequence가 stdlib에 있다는 막연한 제안을 느꼈으므로 나중에 다른 독자들에게 혼란을 줄 수 있습니다. 나는 그것이 그것을 직접 주장하지 않는다는 것을 깨닫는다, 그것은 어떻게 든 다소 혼란 스럽다. – antron

관련 문제