为什么我在没有显式递归的函数上得到StackoverflowError

时间:2015-07-19 13:05:23

标签: clojure

我正在尝试生成一个相对较小的(1296个元素)向量列表,基本上枚举从[0 0 0 0]到[5 5 5 5]的4个基数6位数

[0 0 0 0], [1 0 0 0] ... [5 0 0 0], [0 1 0 0] ... [5 5 5 5]

目前我所拥有的是:

(letfn [(next-v [v]
          (let [active-index (some (fn [[i e]] (when (> 5 e) i)) 
                                   (map-indexed vector v))]
            (map-indexed #(cond
                           (> active-index %1) 0
                           (= active-index %1) (inc %2)
                           :else %2)
                         v)))]
  (last (take 1290 (iterate next-v [0 0 0 0]))))

这样可行,但它最终会打击堆栈。

我在这做什么导致StackOverflowError? 如何构建我的代码以使其“安全”? 有没有更好的方法来做我想做的事情?

2 个答案:

答案 0 :(得分:5)

我解决这个问题的方法是:

(def my-range
  (for [i (range 0 6)
        j (range 0 6)
        x (range 0 6)
        y (range 0 6)]
    [i j x y]))

(nth my-range 1295) ;;=> [5 5 5 5]

广义:

(defn combine [coll]
  (for [i (range 6)
        j coll]
    (conj j i)))

(combine (map list (range 6)))
(combine (combine (map list (range 6))))
(combine (combine (combine (map list (range 6)))))

(def result (nth (iterate combine (map list (range 6))) 3))

答案 1 :(得分:4)

这是由于迭代函数体中的懒惰。请注意,第一次调用next-v返回的结果在被评估之前再次传递给next-v(因为它是一个懒惰的seq),然后next-v再次返回一个未经评估的lazy-seq,它将再次传递给它

当你意识到最终的延迟seq时,为了产生第一个元素,必须实现所有链接的seqs以通过你的初始[0 0 0 0]。这会炸掉堆栈。

Stuart Sierra通过更多例子写了一篇很好的文章:http://stuartsierra.com/2015/04/26/clojure-donts-concat

您可以简单地将地图索引的调用包装在vec

中的let主体中

建议您为您的问题寻找更通用的算法。