【问题标题】:stack overflow when generating large sequence of letters in ocaml在 ocaml 中生成大序列字母时堆栈溢出
【发布时间】:2011-07-26 12:12:19
【问题描述】:

给定一个字母表["a"; "b"; "c"] 我想将所有长度为 25 的序列转储到一个文件中。 (字母可以按顺序重复;这不是排列。)问题是,当我尝试使用以下代码时,我得到了 Stack overflow during evaluation (looping recursion?)

let addAlphabetToPrefix alphabet prefix =
  List.map (function letter -> (prefix ^ letter)) alphabet;;

let rec generateWords alphabet counter words =
  if counter > 25 then
    words
  else
    let newWords = List.flatten(List.map (function word -> addAlphabetToPrefix alphabet word) words) in 
    generateWords alphabet (counter + 1) newWords;;

generateWords ["a"; "b"; "c"] 0 [""];; (* Produces a stack overflow. *)

有没有更好的方法来做到这一点?我正在考虑先生成整个列表,然后将整个列表转储到文件中,但是我是否必须重复生成部分列表然后转储?做一些懒惰的事情会有帮助吗?

究竟为什么会发生堆栈溢出? AFAICT,我的 generateWords 函数是尾递归的。我生成的words 列表是否太大而无法放入内存?

【问题讨论】:

  • ocaml 是否优化尾递归?
  • @Jeff:有趣!真的,我不知道 ocaml 是关于什么的。就是这样,我知道的语言似乎并不费心尝试优化尾递归:-)
  • 你应该 #trace generateWords 函数来看看它是否对这个主题有所启发。我认为这只是你在这里生成了一个巨大的列表(可能是 26^25 个单词),所以你自然会用完内存。加上你是递归生成这个,会有很多中间结果。

标签: ocaml stack-overflow tail-recursion


【解决方案1】:

您的函数正在编译为尾调用。我从线性化代码中确认;从本机编译器中的-dlinear 选项获得,ocamlopt[.opt]

事实是,你的堆正呈指数级增长,而 25 个单词在这种方法中是不可持续的。尝试 11 效果很好(这是我能处理的最高值)。

是的,有更好的方法来做到这一点。您可以通过查找index of the combination in lexicographical order 或使用格雷码(同一页面)来生成组合。这些只需要存储一个单词,可以并行运行,并且永远不会导致分段错误——尽管使用索引方法可能会溢出,在这种情况下,您可以切换到大整数,但会牺牲速度,或者格雷码(可能难以并行化,具体取决于格雷码)。

【讨论】:

    【解决方案2】:

    OCaml 优化了尾递归,因此您的代码应该可以工作,除了:不幸的是,标准库的 List.map 函数不是尾递归的。堆栈溢出可能发生在其中一个调用中,因为您的列表变得相当大。

    Batteries Included 和 Jane Street 的核心库都提供了 map 的尾递归版本。尝试其中一种,看看它是否能解决问题。

    【讨论】:

    • 该死;我忘记了。我只是在查看他声明的尾递归函数。尽管如此,他/她仍将不得不处理这种大小的字长的记忆问题。
    • 在这种情况下,List.rev_map 也可以使用(而不是更改List.map 实现)。
    猜你喜欢
    • 2011-11-24
    • 2017-09-29
    • 1970-01-01
    • 2016-02-26
    • 1970-01-01
    • 1970-01-01
    • 2017-12-01
    • 2012-01-12
    • 1970-01-01
    相关资源
    最近更新 更多