4

使用深度的下列遞歸定義第一搜索的Clojure(JVM)和ClojureScript REPL產生兩個不同的輸出,其中節點被印刷即順序(與兩個瀏覽器連接REPL和LUMO測試)是不同的,並且Clojure的REPL產生一個重複:f。 ClojureScript順序是我期望的行爲。爲什麼是這樣?的Clojure和ClojureScript REPL產生不同的輸出

代碼:

(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 
;; => ((()()) (())()) 
+2

在clojure 1.8和1.9-alpha14中,我最終沒有得到':f',而我的parens與您的cljs parens完全相同。 – Josh

+0

這很奇怪,我使用Clojure 1.8.0和ClojureScript 1.9.229。 – mac

回答

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))) - 您將在clojure中獲得32個數字,而在CLJS中只會獲得一個數字。

如果您希望對評估順序有更好的保證,您可以使用doseq而不是for或將for包裝在doall中。

希望這會有所幫助。

邊注:正如@Josh,我得到了用Clojure 1.8到底有沒有:f,和括號是相同的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)) - 然後你就可以拼合得到遍歷。你也會得到懶惰的好處。

+0

感謝您的回答。對我來說,評估順序會受到組塊的影響,這似乎很瘋狂。另外當我只打印'''v'''時,爲什麼其他元素會輸出到REPL。如果我刪除'''println'''電話沒有打印。 – mac

+0

問題是你正在混合副作用代碼和惰性代碼。在你的情況下,評估的順序(和事實)不僅取決於分塊,還取決於你的REPL是否輸出函數'(((()())(())())'的返回值控制檯 - 如果你仔細想想它本身就是瘋狂的。如果你想把結果輸出到控制檯,你可以使用命令式的代碼,如果你想把結果作爲一個返回值,可以使用函數式/懶惰式的代碼(請看上面編輯中的幾個例子)。但混合這兩種方法並不合適Clojure。 –

相關問題