在ocaml中展平数组数组的最快方法是什么?

时间:2016-01-12 19:18:19

标签: arrays functional-programming ocaml

在ocaml中展平数组数组的最快方法是什么?请注意,我的意思是数组,而不是列表。

我想以线性方式做到这一点,尽可能使用最低系数。

1 个答案:

答案 0 :(得分:3)

OCaml标准库相当缺乏,需要您从头开始实现这么多东西。这就是为什么我们扩展了像Batteries和Core这样的库。我建议你使用它们,这样你就不会遇到这样的问题。

尽管如此,为了完整起见,让我们尝试实现我们自己的解决方案,然后将其与提议的fun xxs -> Array.(concat (to_list xxs))解决方案进行比较。

在实施中,我们几乎没有什么小问题。首先,为了构造一个数组,我们需要为每个单元格提供一个值。我们不能只创建一个未初始化的数组,这会打破一个类型系统。我们当然可以使用Obj模块,但这很难看。另一个问题是输入数组可能是空的,所以我们需要以某种方式处理这种情况。当然,我们可以提出一个异常,但我更愿意将我的功能整合在一起。但是,如何创建一个空数组并不明显,但这并非不可能:

 let empty () = Array.init 0 (fun _ -> assert false)

这是一个创建空多态数组的函数。我们使用底值(每种类型的居民的值),表示为assert false。这是类型安全和整洁。

接下来是如何在没有默认值的情况下创建数组。我们可以编写一个非常复杂的代码,使用Array.init并将i索引转换为j n'th数组的索引。但这很乏味,容易出错并且效率很低。另一种方法是在输入数组中找到第一个值并将其用作默认值。这是另一个问题,因为在标准库中我们没有Array.find函数。骰。遗憾的是,在21世纪我们需要写一个Array.find函数,但这就是生活的方式。再次,使用Core(或Core_kernel)库或Batteries。通过opam可以在OCaml社区中获得许多优秀的图书馆。但回到我们的问题,由于我们没有查找功能,我们将使用我们自己的自定义解决方案。我们可以使用fold_left,但它将遍历整个数组,尽管我们只需要找到第一个元素。有一个解决方案,我们可以使用异常,用于非本地退出。不要害怕,这在OCaml中是惯用的。在OCaml中引发和捕获异常非常快。除了非本地退出,我们还需要发送我们发现的价值。我们可以使用参考小区作为通信信道。但这是相当丑陋的,我们将使用例外本身为我们承担价值。由于我们事先并不知道元素的类型,因此我们将使用OCaml语言的两个现代特性。本地抽象类型和本地模块。所以,让我们去实施:

let array_concat (type t) xxs =
  let module Search = struct exception Done of t end in
  try
    Array.iter (fun xs ->
        if Array.length xs <> 0
        then raise_notrace (Search.Done xs.(0))) xxs;
    empty ()
  with Search.Done default ->
    let len =
      Array.fold_left (fun n xs -> n + Array.length xs) 0 xxs in
    let ys = Array.make len default in
    let _ : int = Array.fold_left (fun i xs ->
        let len = Array.length xs in
        Array.blit xs 0 ys i len;
        i+len) 0 xxs in
    ys

现在,有趣的部分。标杆!让我们使用建议的解决方案进行比较:

let default_concat xxs = Array.concat (Array.to_list xxs)

这是我们的测试工具:

let random_array =
  Random.init 42;
  let max = 100000 in
  Array.init 1000 (fun _ -> Array.init (Random.int max) (fun i -> i))


let test name f =
  Gc.major ();
  let t0 = Sys.time () in
  let xs = f random_array in
  let t1 = Sys.time () in
  let n = Array.length xs in
  printf "%s: %g sec (%d bytes)\n%!" name (t1 -. t0) n

let () =
  test "custom "  array_concat;
  test "default" default_concat

结果:

$ ./array_concat.native
custom : 0.38 sec (49203647 bytes)
default: 0.20 sec (49203647 bytes)
顺便说一句,他们不会让我感到惊讶。我们的解决方案比标准库慢两倍。这个故事的寓意是:

  1. 始终在优化
  2. 之前对进行基准测试
  3. 使用扩展库(核心,电池,容器......)