说我有一堆:
Array.make (int_of_float (2. ** 17.)) 1
|> Array.to_list;;
- : int list
= [1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1;
1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1;
1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1;
1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1;
1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1;
1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1;
1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1;
1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1;
1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1;
1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1;
1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1;
1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; ...]
我想在这些函数上映射一个函数:
Array.make (int_of_float (2. ** 17.)) 1
|> Array.to_list
|> List.map (fun x -> x * 2);;
Stack overflow during evaluation (looping recursion?).
好像太多了!看看如何在Ocaml中实现List.map,我发现了这一点 (https://github.com/ocaml/ocaml/blob/trunk/stdlib/list.ml#L57-L59):
let rec map f = function
[] -> []
| a::l -> let r = f a in r :: map f l
我对Ocaml很陌生,但看起来map的编写方式使得它不是尾递归的。
是吗?
如何将功能映射到很多东西上?
答案 0 :(得分:4)
是的,OCaml标准库中的List.map
不是尾递归的。你有几个选择:
Array.map
List.rev_map
(可能与List.rev
创造)虽然前两个选项很明显,但后两个选项需要一些解释。
如果您确实拥有大量数据,并且此数据是数字的,那么您应该考虑使用Bigarrays。此外,根据您的使用情况,Maps和Hashtables可能会更好。函数式编程语言中的人倾向于在任何地方使用列表,而不是选择适当的数据结构。不要陷入这个陷阱。
List.map
非尾递归是有充分理由的。堆栈使用和性能之间总是存在权衡。对于小列表(这是最常见的用例),非尾递归版本要快得多。因此标准库决定提供快速List.map
,如果您需要处理大型列表,则可以使用List.rev_map xs |> List.rev
。此外,有时您可以省略List.rev
部分。所以,标准库,不是试图为你思考,它给你一个选择。
另一方面,随着时间的推移,人们设法在这种权衡中找到一些最优,即具有相当快的堆栈版本。解决方案是在列表很小时使用非尾递归方法,然后回退到慢速版本。此类实现由Janestreet Core Library,Batteries和Containers库提供。
因此,您可以切换到这些库并忘记此问题。虽然仍然强烈建议使用List.map
几乎不小心,因为此操作总是具有线性内存消耗(堆或堆栈内存),这是可以避免的。因此,最好尽可能使用rev_map
。
答案 1 :(得分:3)
你是正确的List.map不是尾递归的,而是使用List.rev_map而不是尾递归。 ...和List.rev应该是尾递归。
List记录了List模块的所有功能,指示它是否不是尾递归。
答案 2 :(得分:2)
您要做的是在计算结束时构建列表:
let construct_value _ =
1 |> (fun x -> x * 2)
let rec construct_list f n =
let rec aux acc n =
if n < 0
then acc
else aux (f n) (n-1)
in
aux [] (n-1)
let result = construct_list construct_value (int_of_float (2. ** 17.))
这里最终会得到与计算相同的列表,但时间和内存使用量会减少。请注意,列表的元素是从列表的底部构造的(你必须为尾部递归做),我添加了一个“index”参数,它对应于数组中的确切位置(我没有构造,另一个时间和记忆增益)
另外,如果你想对那么大的数据结构进行计算,列表可能不是最好的选择,但这取决于你打算用它做什么。