使用以下递归定义深度优先搜索Clojure(JVM)和ClojureScript(使用浏览器连接的repl和lumo进行测试)REPL产生两个不同的输出,即打印节点的顺序不同而Clojure REPL产生一个重复:f
。 ClojureScript顺序是我期望的行为。这是为什么?
代码:
(defn dfs
([g v] (dfs g v #{}))
([g v seen]
(println v)
(let [seen (conj seen v)]
(for [n (v g)]
(if-not (contains? seen n)
(dfs g n seen))))))
(def graph {:a [:b :c :e]
:b [:d :f]
:c [:g]})
(dfs graph :a)
Cloure REPL输出:
:a
:b
:c
:e
:d
:f
:g
:f
;; => ((() ()) (()) (()))
CLojureScript REPL输出:
:a
:b
:d
:f
:c
:g
:e
;; => ((() ()) (()) ())
答案 0 :(得分:6)
Clojure的for
生成一个懒惰的序列。所有重复dfs
次调用的实际评估仅由您的REPL触发,因为它需要打印函数的输出,即((() ()) (()) ())
。如果您评估(do (dfs graph :a) nil)
,则只会打印:a
。
现在,Clojure的懒惰序列的效率为evaluated in chunks of size 32。因此,当REPL(通过str
函数)评估第一个元素延迟序列顶级for
(应该打印:b
)时,该seq的其他元素也会被评估,并且您在计算子节点的序列之前打印:c
和:e
(也是懒惰的)。
相比之下,Clojurescript的延迟序列没有分块(LazySeq does not implement IChunkedSeq)并且逐个进行评估,因此当返回值递归转换为字符串时,所有内容都按深度优先顺序进行评估。
为了说明这一点 - 在Clojure和CLJS中的REPL中尝试(first (for [i (range 300)] (do (println "printing:" i) i)))
- 你将获得32个用clojure打印的数字,只有一个数字用于CLJS。
如果您想要更好地保证评估顺序,可以使用doseq
代替for
或将for
包裹在doall
。
希望这有帮助。
旁注:就像@Josh一样,我在Clojure 1.8中最终得不到:f
,并且parens与cljs输出相同 - 这真的很奇怪......
我不确定我是否正在关注您目前想要如何使用DFS的结果。如果你想使用副作用,我。即将所有节点打印到控制台,使用doseq
确保遍历它们:
(defn dfs-eager
([g v] (dfs-eager g v #{}))
([g v seen]
(println v)
(let [seen (conj seen v)]
(doseq [n (v g)]
(if-not (contains? seen n)
(dfs-eager g n seen))))))
这会将所有节点打印到控制台,深度优先。如果要将遍历作为返回值,请使用for
但请确保实际返回有意义的值:
(defn dfs-lazy
([g v] (dfs-lazy g v #{}))
([g v seen]
(cons v
(let [seen (conj seen v)]
(for [n (v g)]
(if-not (contains? seen n)
(dfs-lazy g n seen)))))))
您将获得一个嵌套列表(:a (:b (:d) (:f)) (:c (:g)) (:e))
- 然后您可以将其展平以进行遍历。你也会得到懒惰的好处。