Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/logging/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
OCaml中的高效输入_Ocaml_User Input - Fatal编程技术网

OCaml中的高效输入

OCaml中的高效输入,ocaml,user-input,Ocaml,User Input,假设我正在编写一个OCaml程序,我的输入将是一个由空格分隔的大整数流,即 let string = input_line stdin;; 将返回一个类似于“2 4 34 765 5…”的字符串。现在,程序本身将获取另外两个值i和j,这两个值指定主程序将在其上执行的输入的一小子序列(假设主程序是查找此子列表的最大值)。换句话说,整个流将被输入到程序中,但程序最终只会作用于输入的一小部分 我的问题是:什么是将输入流的相关部分转换为可用内容的最佳方式,即字符串INT?一个选项是使用 let lis

假设我正在编写一个OCaml程序,我的输入将是一个由空格分隔的大整数流,即

let string = input_line stdin;;
将返回一个类似于“2 4 34 765 5…”的字符串。现在,程序本身将获取另外两个值i和j,这两个值指定主程序将在其上执行的输入的一小子序列(假设主程序是查找此子列表的最大值)。换句话说,整个流将被输入到程序中,但程序最终只会作用于输入的一小部分

我的问题是:什么是将输入流的相关部分转换为可用内容的最佳方式,即字符串INT?一个选项是使用

let list = List.map int_of_string(Str.split (Str.regexp_string " ") string;;
一旦输入了界i和j,就可以很容易地找到相关的子列表及其最大值。问题是,大数据流的初始预处理非常耗时


是否有一种直接从大数据流定位小子列表的有效方法,即与主程序一起处理输入

您可以使用
Scanf
模块功能系列。例如,
Scanf.fscanf
允许您根据字符串格式(这是OCaml中的一种特殊类型)从通道读取令牌

您的程序可以分解为两个功能:

  • 从输入通道跳过一个令牌数
    i
  • 从通道中的数字
    j
    中提取最大整数的一种
让我们写下这些:

let rec skip_tokens c i =
  match i with
    | i when i > 0 -> Scanf.fscanf c "%s " (fun _ -> skip_tokens c @@ pred i)
    | _ -> ()


let rec get_max c j m =
  match j with
    | j when j > 0 -> Scanf.fscanf c "%d " (fun x -> max m x |> get_max c (pred j))
    | _ -> m
注意字符串中标记格式指示符后面的空格,它告诉扫描器也吞下所有空格,并在标记之间返回回车

你现在需要做的就是把它们结合起来。下面是一个可以从CLI运行的小程序,它接受
i
j
参数,需要一个令牌流,并根据需要打印出最大值:

let _ =
  let i = int_of_string Sys.argv.(1)
  and j = int_of_string Sys.argv.(2) in
  skip_tokens stdin (pred i);
  get_max stdin j min_int |> print_int;
  print_newline ()

通过提取递归部分,您可能可以编写更灵活的组合器。我将把它留给读者作为练习。

OCaml的标准库相当小。它提供了必要且充分的正交特性集,正如任何好的标准库一样。但是,通常情况下,这对于普通用户来说是不够的。这就是为什么有图书馆,做这些事情,这是相当普遍的

我想提到两个最著名的图书馆:Jane Street的核心图书馆和电池(又名核心和电池)

这两个库都提供了一系列高级I/O函数,但存在一个小问题。试图在库中处理任何用例是不可能的,甚至是不合理的。否则,图书馆的界面就不会简洁易懂。你的案子是不标准的。有一种约定,数据工程师之间的默契,用文件中的一组行表示一组内容。用一条线来表示一个“事物”(或特征)。因此,如果您有一个数据集,其中每个元素都是标量,那么您应该将其表示为由换行符分隔的标量序列。单行上的多个元素仅适用于多维要素

因此,通过适当的表示,您的问题可以简单地解决(核心):

您可以使用
corebuild test.byte--
编译和运行此程序,假设代码位于文件名
test.byte
中,并且安装了核心库(如果您使用
opam
,则使用
opam install core

此外,还存在一个优秀的库
Lwt
,它为I/O提供了一个一元高级接口。使用该库,您可以通过以下方式解析一组标量:

open Lwt

let program =
  let filename = "data" in
  let lines = Lwt_io.lines_of_file filename in
  Lwt_stream.fold (fun s m -> max m @@ int_of_string s) lines 0 >>=
  Lwt_io.printf "Max number is %s is %d\n" filename

let () = Lwt_main.run program
如果系统上安装了
lwt
库(
opam install lwt
),则可以使用
ocamlbuild-package lwt.unix test.byte--
)编译和运行此程序

所以,这并不是说您的问题无法在OCaml中解决(或者很难解决),只是要提到的是,您应该从一个适当的表示开始。但是,假设您不拥有该表示,并且无法更改它。让我们看看如何使用OCaml有效地解决这个问题。如前面的示例所示,通常您的问题可以描述为通道折叠,即对文件中的每个值应用函数
f
。因此,我们可以定义一个函数fold_channel,它将从一个通道中读取一个整数值,并将一个函数应用于它和以前读取的值。当然,这个函数可以通过提升format参数来进一步抽象,但是为了演示的目的,我想,这就足够了

let rec fold_channel f init ic =
  try  Scanf.fscanf ic "%u " (fun s -> fold_channel f (f s init) ic)
  with End_of_file -> init

let () =
  let max_value = open_in "atad" |> fold_channel max 0 in
  Printf.printf "max value is %u\n" max_value
尽管如此,我应该注意到,这项实施并不是一项繁重的工作。它甚至不是尾部递归的。例如,如果您需要真正高效的lexer,您可以使用ocaml的lexer生成器

更新1 由于标题中有“高效”一词,而且每个人都喜欢基准测试,所以我决定比较这三种实现。当然,由于纯OCaml实现不是尾部递归的,因此与其他实现不可比较。您可能想知道,为什么它不是尾部递归的,因为对
fold\u channel
的所有调用都处于尾部位置。问题在于异常处理程序——每次调用折叠通道时,我们都需要记住
init
值,因为我们将返回它。这是递归和异常的常见问题,您可以在谷歌上搜索更多示例和解释

因此,首先我们需要修复第三个实现。我们将使用具有期权价值的常见技巧

let id x = x
let read_int ic =
  try Some (Scanf.fscanf ic "%u " id) with End_of_file -> None

let rec fold_channel f init ic =
  match read_int ic with
  | Some s -> fold_channel f (f s init) ic
  | None   -> init

let () =
  let max_value = open_in "atad" |> fold_channel max 0 in
  Printf.printf "max value is %u\n" max_value
因此,通过一个新的尾部递归实现,让我们在一个大数据平台上尝试它们。对于我7岁的笔记本电脑来说,10万个数字是一个大数据。我还添加了C实现作为基线,以及C实现的OCaml克隆:

let () =
  let m = ref 0 in
  try
    let ic = open_in "atad" in
    while true do
      let n = Scanf.fscanf ic "%d " (fun x -> x) in
      m := max n !m;
    done
  with End_of_file ->
    Printf.printf "max value is %u\n" !m;
    close_in ic
let rec fold_channel f init buf =
  match Lex_int.next buf with
  | Some s -> fold_channel f (f s init) buf
  | None   -> init

let () =
  let max_value = open_in "atad" |>
                  Lexing.from_channel |>
                  fold_channel max 0 in
  Printf.printf "max value is %u\n" max_value
更新2 另一个实现
{}
let digit = ['0'-'9']
let space = [' ' '\t' '\n']*

rule next = parse
| eof {None}
| space {next lexbuf}
| digit+ as n {Some (int_of_string n)}

{}
let rec fold_channel f init buf =
  match Lex_int.next buf with
  | Some s -> fold_channel f (f s init) buf
  | None   -> init

let () =
  let max_value = open_in "atad" |>
                  Lexing.from_channel |>
                  fold_channel max 0 in
  Printf.printf "max value is %u\n" max_value
implementation   time  ratio rate (MB/s)
plain C          22 s  1.0   12.5
ocamllex         33 s  1.5    8.4
Core             62 s  2.8    4.5
C-like OCaml     83 s  3.7    3.3
fold_channel     84 s  3.8    3.3
Lwt             143 s  6.5    1.9
open Core.Std

let () =
  let filename = "data" in
  let b1,b2 = Int.(of_string Sys.argv.(1), of_string Sys.argv.(2)) in
  let range = Interval.Int.create b1 b2 in
  let _,max_number =
    let open In_channel in
    with_return begin fun call ->
      with_file filename
        ~f:(fold_lines ~init:(0,0)
              ~f:(fun (i,m) s ->
                  match Interval.Int.compare_value range i with
                  | `Below -> i+1,m
                  | `Within -> i+1, Int.(max m @@ of_string s)
                  | `Above -> call.return (i,m)
                  | `Interval_is_empty -> failwith "empty interval"))
    end in
  printf "Max number is %s is %d\n" filename max_number