我一直在和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)
答案 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行构建list
,next
已经返回了一个序列。有了这两个变化:
(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 ...)应该是首选。
让我们使用reduce
到concat
10个10个整数序列的序列:
boot.user=> (take 5 (reduce concat [] (for [x (range 1e1)] (range 1e1))))
(0 1 2 3 4)
但是如果我们尝试使用reduce
到concat
一系列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
时要记住一些事项。