我应该在OCaml递归函数中使用数组还是列表?

时间:2018-10-25 03:51:24

标签: recursion optimization ocaml

我在OCaml中有一个递归函数,该函数接受一个表达式并返回指令数组(这是针对编译器项目的)。

代码基本上看起来像

let rec compile_body (body:expr):fn = 
  match body with 
  | Seq (expr1, expr2) -> Array.concat [compile_body expr1; compile_body expr2;]
  | Add (n1, n2) -> [|Load 0 n1; Load 1 n2; Add 0 0 1;|]
  | ... 

将函数编写为返回列表,然后最后将列表转换为数组,是否会更有性能?

2 个答案:

答案 0 :(得分:1)

Array.concatArray.append,根据文档,分配一个新数组,然后在后面复制两个初始数组的内容。

List.append的复杂度仅与第一个参数的长度成比例。

因此,从理论上讲,使用list应该更有效,因为最后一个数组串联将与最后的Array.of_list操作具有相同的复杂性。

edit:如@coredump所述,保留一棵数组树(该数组代表所有数组的逻辑串联)将是较便宜的结构,如果最后需要一个实际的数组,则可以计算总大小您的“抽象”数组的大小,创建此大小的数组,然后用数组树的内容填充它。该操作的最终数组大小将是线性的。

话虽这么说,但我怀疑除非您有丰富的表达要编译,否则最终是否会有很大的不同。在这种情况下,您还应该注意List.append不是尾递归的事实,因此可能会导致(确实)大列表上的堆栈溢出。

答案 1 :(得分:1)

使用数组会一遍又一遍地复制整个内容。很快变得太慢了。

轻松使用带有append的列表存在相同的问题,甚至更糟。切勿在长列表中添加任何内容。订购代码,以便将其追加到简短列表中。 rev_append也可能有用。但是在您的情况下,Seq (a, b)可以为ab设置很长的列表,因此追加是个坏主意。

解决方案是使用列表,但不使用追加。从右到左遍历语法树并从末尾开始构建列表。这样,所有操作都只会添加到列表的最前面。

或者进行从左到右遍历以向后构建列表,最后将其反向。当您按照预期的顺序构造事物时,这通常更容易阅读。