我很难找到由Bron-Kerbosch算法的clojure实现产生的stackoverflow错误。
下面是我对算法本身的实现,这里有一个SSCCE的链接,概述了这个问题http://pastebin.com/gtpTT4B4.
(defn Bron-Kerbosch [r p x graph cliques]
;(println (str "R:" r " P:" p " X:" x " Clq:" cliques))
(cond (and (empty? p) (empty? x)) (concat cliques r)
:else
(let [neigh (neighV graph (dec (count p)))]
(loop [loop-clq '(cliques)
loop-cnt (dec (count p))
loop-p '(p)
loop-x '(x)]
(cond (= -1 loop-cnt) loop-clq
:else
(recur (concat loop-clq
(Bron-Kerbosch (concat r loop-cnt)
(concat p neigh)
(filter (set x) neigh)
graph cliques))
(dec loop-cnt)
(filter (set p) loop-cnt)
(concat x loop-cnt)))))))
我不得不假设问题显然存在于我的两个引导条件(cond (and (empty? p) (empty? x))
和(cond (= -1 loop-cnt)
中的一个之内,因为函数算法是递归的。
尽管这是假设我正在正确地构建列表x r p
。从注释输出的打印声明(派系总是作为EmptyList打印)的输出来看,我认为我的列表理解可能也是问题所在。
同样,我可以看到的另一个问题是,我实际上没有在BK-Call
函数(在SSCCEE中)中正确调用算法。
我的总体问题是,是什么导致了这种情况?尽管这有点过于开放,但另一个可能让我找到答案的问题是,我如何在第一行使用打印语句。
当取消对打印语句的注释时,它会产生输出
R:clojure.lang.LazySeq@2044e0b9 P:clojure.lang.LazySeq@7f9700a5 X:clojure.lang.LazySeq@1 Clq:clojure.lang.PersistentList$EmptyList@1
我想,如果我能看到x r p
在每次调用中,我可能就能看到算法哪里出了问题。
有人能给我指正确的方向吗?
编辑:SSCCE 的neighV
功能
(defn neighV [graph nodenum]
(let [ret-list (for [i (range (count graph)) :when (contains? (graph i) nodenum)] i)]
ret-list))
EDIT2:Noisesmith的回答让我更接近解决方案,对我来说也很有意义。我把所有的concat
都封装在doall
中。在再次尝试调用该函数后,我遇到了"Cannot cast Long to Seq"错误,我认为这些错误源于尝试将concat
loop-cnt
添加到函数中的列表中
fptests.core> (BK-Call (sanity1))
IllegalArgumentException Don't know how to create ISeq from: java.lang.Long clojure.lang.RT.seqFrom (RT.java:505)
fptests.core> (concat 1 '(2 3))
IllegalArgumentException Don't know how to create ISeq from: java.lang.Long clojure.lang.RT.seqFrom (RT.java:505)
因此,我将每个loop-cnt
封装在'()
中,在它成为concat
之前将其转换为列表
fptests.core> (concat '(1) '(2 3))
(1 2 3)
在我做了所有这些更改之后,我又回到了堆栈溢出。。这是带有所有编辑的新Bron-Kerbosch
函数。我想我现在有和以前一样的问题。。
尽管新的是,我是否正确地实现了我应该实现的更改,但使用'()
来解决在实现noisesmith的更改后出现的问题有意义吗?
(defn Bron-Kerbosch1 [r p x graph cliques]
(cond (and (empty? p) (empty? x)) (doall (concat cliques r))
:else
(let [neigh (neighV graph (dec (count p)))]
(loop [loop-clq '(cliques)
loop-cnt (dec (count p))
loop-p '(p)
loop-x '(x)]
(cond (= -1 loop-cnt) loop-clq
:else
(recur (doall (concat loop-clq
(Bron-Kerbosch1 (doall (concat r '(loop-cnt)))
(doall (concat p neigh))
(filter (set x) neigh)
graph cliques)))
(dec loop-cnt)
(filter (set p) '(loop-cnt))
(doall (concat x '(loop-cnt)))))))))
EDIT3:在耐心等待我的prn
语句停止后(不确定我知道它在SO中时为什么要把它们放进去),我发现打印的大多数语句(如果不是所有的话)都是按照的行
"R:" (loop-cnt loop-cnt loop-cnt loop-cnt loop-cnt loop-cnt loop-cnt ...)
"P:" (range (count graph) 0 2 3) " X:" () " Clq:" ()
在检查了一些之后,我意识到我没有正确地递归调用函数。我一直在将项目统一到P
,而不是删除它们。这导致CCD_ 18持续生长。这很可能是我的堆栈溢出的原因。尽管仍然存在一些问题。我仍然在创造一个堆叠的流动,再一次。
一旦我解决了继续联合到p的问题,我的问题是,当我concat
loop-cnt
时,我想说,它不会被求值为一个值,但它仍然是一个变量名loop-cnt
。我怀疑我的堆栈溢出现在是因为我的引导条件没有得到满足,因为如果loop-cnt
没有被计算为一个数字,它就不能得到满足。
所以我认为我现在的问题在于concat
loop-cnt
作为一个数字而不是一个变量。
concat
是懒惰的。在concat
之上构建concat
的递归调用,没有实现任何先前的惰性层,每个调用都增加了实现惰性seq所需的调用堆栈的大小。
这种串联需要懒惰吗?如果没有,请将对concat
的调用封装在对doall
的调用中。这将使串联变得迫切,从而减少了实现最终结果所需的调用堆栈的大小,从而消除了StackOverflowError。
此外,打印懒惰序列并查看内容的正确方法是prn
,如果需要,可以使用pr-str
获取pr
或prn
将用作字符串的值的形式。
我认为您滥用了带引号的列表。
例如,在(defn Bron-Kerbosch1 [ ... ] ... )
中,'(cliques)
的计算结果为包含符号cliques
的列表,而不是包含自变量cliques
作为其一个元素的列表。你需要(list cliques)
。
类似的案例比比皆是。