2016-05-13 70 views
0

我需要動態地修改這個結構的數據:Clojure的prewalk無限遞歸查詢datomic

[:db/id 
:list/title 
:list/type 
{:list/items [... lots of nested data ...]}] 

以下幾點:

[:db/id 
:list/title 
:list/type 
{(default :list/items []) [... lots of nested data ...]}] 

由於我處理幾個不同的查詢,我可以確保連接將成爲向量中的第四項。但我需要用(default :list/items [])替換:list/items的每個實例。

我知道這樣做的唯一方法是使用clojure.walk/prewalk。但是,它會導致無限遞歸:

(clojure.walk/prewalk #(if (= :list/items %) 
         '(default :list/items []) 
         %) 
         query) 

一旦發現步行和:list/items'(default :list/items [])替換它,然後它發現在更換價值:list/items,並替換。等等等等。

我可以使用一個原子來確保值只被替換一次,但這就像作弊。

還有其他方法嗎?

在這種情況下,你可能需要使用

回答

1

postwalk:

user> 
(def query [:db/id 
      :list/title 
      :list/type 
      {:list/items [:db/id 
          :list/title 
          :list/type 
          {:list/items []}]}]) 
#'user/query 

user> (clojure.walk/postwalk #(if (= :list/items %) 
           '(default :list/items []) 
           %) 
          query) 
[:db/id :list/title :list/type 
{(default :list/items []) [:db/id :list/title :list/type {(default :list/items []) []}]}] 

即使葉子已經換成了新的集合postwalk不走更深的內容:

user> (clojure.walk/prewalk #(do (println %) 
           (if (= % 1) [10] %)) 
          [[1 2 3 [1 2]] [1 2]]) 
[[1 2 3 [1 2]] [1 2]] 
[1 2 3 [1 2]] 
1 
10 ;; goes deeper 
2 
3 
[1 2] 
1 
10 ;; and here 
2 
[1 2] 
1 
10 ;; and here 
2 
[[[10] 2 3 [[10] 2]] [[10] 2]] 

user> (clojure.walk/postwalk #(do (println %) 
            (if (= % 1) [10] %)) 
          [[1 2 3 [1 2]] [1 2]]) 
1 
2 
3 
1 
2 
[[10] 2] 
[[10] 2 3 [[10] 2]] 
1 
2 
[[10] 2] 
[[[10] 2 3 [[10] 2]] [[10] 2]] 
[[[10] 2 3 [[10] 2]] [[10] 2]] 

通過順便說一下,有一個很好的功能prewalk-replace/postwalk-replace爲您的具體情況:

user> (clojure.walk/postwalk-replace 
     {:list/items '(default :list/items [])} query) 

[:db/id :list/title :list/type 
{(default :list/items []) [:db/id :list/title :list/type {(default :list/items []) []}]}] 

更新後,評論: 一些(綜合)更多控制替代的例子。比方說,你想在嵌套向量的一些亂收費來代替具體的項目,但只有一次更換項目(你第一次看到它),並留下其餘不變:

user> (require '[clojure.zip :as z]) 

user> 
(defn replace-once [rep coll] 
    (loop [curr (z/vector-zip coll) rep rep] 
    (if (empty? rep) (z/root curr) 
     (let [n (z/node curr) r (rep n)] 
      (cond (z/end? curr) (z/root curr) 
       r (recur (z/replace curr r) (dissoc rep n)) 
       :else (recur (z/next curr) rep)))))) 
#'user/replace-once 

user> (replace-once {1 100 2 200} [[4 3 2] [10 1 2] 1 2 [5 3 2]]) 
[[4 3 200] [10 100 2] 1 2 [5 3 2]] 

(在這裏你只是刪除取代從替換候選人地圖(rep),並進一步遞推,直到它是空的)

+0

哦,很好,postwalk替換是完美的。出於好奇,假設我需要使用prewalk來以其他方式操縱查詢。我需要使用原子來跟蹤替換嗎?或者有其他方法嗎? – egracer

+0

你的意思是什麼?現在想不出來) – leetwinski

+1

但我建議你看看'拉鍊'。如果你想對樹上的替換和迭代進行細粒度的控制。例如,它可以讓你替換一些項目,然後跳過這個項目,或者在一些累加器中跟蹤它(因爲拉鍊與'loop/recur'很好地配合工作。 – leetwinski