为什么我得到stackoverflow?

时间:2011-03-14 02:10:42

标签: clojure

我一直在和Clojure一起玩,我设法打破了筹码。在这段代码中,我只使用recur进行递归。我正在进行大量的连接(请注意下面的跟踪调用concat)。我试图在所有连接列表上执行操作,因为这些是懒惰的,我希望它们在我进行时进行评估。我仍然得到堆栈溢出。这是跟踪。我认为这可能是一个普遍的问题,而且有更多黑客攻击经验的人可以指出我正确的方向。

以下是造成问题的代码段。

(defn move-split [[xs ys]] (doall (concat (list (concat xs (list (first ys))))  (list (next ys)))))

由于stackoverflow,我把doall放在那里,但仍然无法解决问题。

(defn move-split [[xs ys]] (doall (concat (list (doall (concat xs (list (first ys))))  ) (doall (list (next ys)))   )))

注意额外的doalls?在这里,我称之为concat,我通过doall过滤结果。 Stackoverflow消失了。

doall似乎不是递归的。这是嵌套列表,也是concat的结果,不会被评估。你觉得怎么样?

Exception in thread "main" java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at jline.ConsoleRunner.main(Unknown Source)
Caused by: java.lang.StackOverflowError (bubble_sort2.clj:0)
    at clojure.lang.Compiler.eval(Compiler.java:5440)
    at clojure.lang.Compiler.load(Compiler.java:5857)
    at clojure.lang.Compiler.loadFile(Compiler.java:5820)
    at clojure.main$load_script.invoke(main.clj:221)
    at clojure.main$script_opt.invoke(main.clj:273)
    at clojure.main$main.doInvoke(main.clj:354)
    at clojure.lang.RestFn.invoke(RestFn.java:409)
    at clojure.lang.Var.invoke(Var.java:365)
    at clojure.lang.AFn.applyToHelper(AFn.java:163)
    at clojure.lang.Var.applyTo(Var.java:482)
    at clojure.main.main(main.java:37)
    ... 5 more
Caused by: java.lang.StackOverflowError
    at clojure.core$seq.invoke(core.clj:122)
    at clojure.core$concat$fn__3450.invoke(core.clj:599)
    at clojure.lang.LazySeq.sval(LazySeq.java:42)
    at clojure.lang.LazySeq.seq(LazySeq.java:56)
    at clojure.lang.RT.seq(RT.java:450)
    at clojure.core$seq.invoke(core.clj:122)
    at clojure.core$concat$fn__3450.invoke(core.clj:599)
    at clojure.lang.LazySeq.sval(LazySeq.java:42)
    at clojure.lang.LazySeq.seq(LazySeq.java:56)
    at clojure.lang.RT.seq(RT.java:450)
    at clojure.core$seq.invoke(core.clj:122)
    at clojure.core$concat$fn__3450.invoke(core.clj:599)
    at clojure.lang.LazySeq.sval(LazySeq.java:42)
    at clojure.lang.LazySeq.seq(LazySeq.java:56)
    at clojure.lang.RT.seq(RT.java:450)
    at clojure.core$seq.invoke(core.clj:122)
    at clojure.core$concat$fn__3450.invoke(core.clj:599)
    at clojure.lang.LazySeq.sval(LazySeq.java:42)
    at clojure.lang.LazySeq.seq(LazySeq.java:56)
    at clojure.lang.RT.seq(RT.java:450)
    at clojure.core$seq.invoke(core.clj:122)
    at clojure.core$concat$fn__3450.invoke(core.clj:599)

3 个答案:

答案 0 :(得分:6)

你正在连续堆积一堆懒惰的操作,构建一个像

这样的表达式
(concat (concat (concat ... [3]) [2]) [1])

或类似的。为了确定结果列表中的第一个元素,编译器必须深入研究您给出的这些函数堆栈。尝试构造您的代码,以便不会发生这种情况,或者每隔一段时间抛出一次(doall)以强制执行急切/严格的计算。我不能仅仅通过堆栈跟踪了解更多详细信息 - 代码会有所帮助。

答案 1 :(得分:1)

现在添加另一个答案,OP已经提出了一些源代码并询问了什么是有效的不同问题。如果这是错误的,有人会适当地编辑我。

看起来你正在将[[1 2 3 4] [5 6 7]]转换为[[1 2 3 4 5] [6 7]]。列表和懒惰的concats真的不是解决这个问题的正确方法,这也是导致你如此痛苦的原因之一。我的直觉是从你永久保留的向量[1 2 3 4 5 6 7]开始,并根据需要构建该向量的子集。 subvec既便宜又快速,无论是构建还是使用。

例如:

(defn vector-split [v at]
  [(subvec v 0 at) (subvec v at)])

答案 2 :(得分:0)

我不确定是什么输入导致你的功能爆炸。我无法让它爆炸出两个50亿元的子序列:

boot.user=> (defn move-split [[xs ys]]
       #_=>   (doall
       #_=>     (concat
       #_=>       (list (concat xs (list (first ys))))
       #_=>       (list (next ys)))))
#'boot.user/move-split
boot.user=> (def x (move-split [(range 5e0) (range 5e0)]))
#'boot.user/x
boot.user=> x
((0 1 2 3 4 0) (1 2 3 4))
boot.user=> (def x (move-split [(range 5e9) (range 5e9)]))
#'boot.user/x
boot.user=> (-> (nth x 0) first)
0
boot.user=> (-> (nth x 1) first)
1
然而,有很多机会使这个功能更加惯用。看看这个功能:

(defn move-split [[xs ys]]
  (doall
    (concat
      (list (concat xs (list (first ys))))
      (list (next ys)))))

第4行:我们无需构造list,因为concat已经返回了一个序列。同样,我们也不需要在第5行构建listnext已经返回了一个序列。有了这两个变化:

(defn move-split [[xs ys]]
  (doall
    (concat
      (concat xs (list (first ys)))
      (next ys))))

现在为什么在世界上我们会通过在顶部粘贴doall来搞乱一个完美的(懒惰)功能?让我们摆脱它。此外,由于函数需要返回一个双元素序列,为什么不这样做而不是在第3行调用concat?构造双元素序列的惯用方法是通过矢量文字语法。有了这两个变化,我们就有了:

(defn move-split [[xs ys]]
  [(concat xs (list (first ys)))(next ys)])

哦,我们可以再次应用矢量文字语法代替那个list构造函数,给我们这个最终版本:

(defn move-split [[xs ys]]
  [(concat xs [(first ys)])(next ys)])

现在让我们试试这个fn ......

boot.user=> (defn move-split [[xs ys]]
       #_=>   [(concat xs [(first ys)])(rest ys)])
#'boot.user/move-split
boot.user=> [(range 5e0) (range 5e0)]
[(0 1 2 3 4) (0 1 2 3 4)]
boot.user=> (def x (move-split [(range 5e0) (range 5e0)]))
#'boot.user/x
boot.user=> x
[(0 1 2 3 4 0) (1 2 3 4)]
boot.user=> (-> (nth x 0) first)
0
boot.user=> (-> (nth x 1) first)
1

这似乎有效。如果我们有两个巨大的子序列怎么办?让我们尝试使用几个50亿元素的子序列:

boot.user=> (def x (move-split [(range 5e9) (range 5e9)]))
#'boot.user/x
boot.user=> (-> (nth x 0) first)
0
boot.user=> (-> (nth x 1) first)
1

对于你原来的问题,正如我所说,我不知道是什么输入导致原始功能烧坏堆栈。我确实在concat doc文档中看到了这一点:

  

连续的连接不会自动展平!因此,使用concat的clj :: clojure.core / reduce将生成大量嵌套的结构,并且在尝试遍历生成的序列时可以轻松生成堆栈溢出。 (应用concat ...)应该是首选。

让我们使用reduceconcat 10个10个整数序列的序列:

boot.user=> (take 5 (reduce concat [] (for [x (range 1e1)] (range 1e1))))
(0 1 2 3 4)

但是如果我们尝试使用reduceconcat一系列10,000个10个整数的序列::

boot.user=> (take 5 (reduce concat [] (for [x (range 1e5)] (range 1e1))))

java.lang.StackOverflowError: 

使用apply代替工作正常:

boot.user=> (take 5 (apply concat (for [x (range 1e5)] (range 1e1))))
(0 1 2 3 4)

在递归应用concat时要记住一些事项。