在ocaml中展平数组数组的最快方法是什么?请注意,我的意思是数组,而不是列表。
我想以线性方式做到这一点,尽可能使用最低系数。
答案 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)
顺便说一句,他们不会让我感到惊讶。我们的解决方案比标准库慢两倍。这个故事的寓意是: