Clojure为数据组查询预先行走无限递归

时间:2016-05-13 16:47:48

标签: clojure datomic

我需要动态修改此结构的数据:

var safari = Application('Safari');
safari.includeStandardAdditions = true; 
safari.quit(); // Avoid having multiple Safari windows open
safari.activate(); // Start Safari and bring to front  
var document = safari.documents[0];
document.url = 'http://stackoverflow.com';

以下内容:

[: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 ...]}] 替换:list/items的每个实例。

我知道这样做的唯一方法是使用(default :list/items [])。但是,它会导致无限递归:

clojure.walk/prewalk

一旦步行找到(clojure.walk/prewalk #(if (= :list/items %) '(default :list/items []) %) query) 并将其替换为:list/items,它就会在替换后的值中找到'(default :list/items []),然后替换它。依此类推。

我可以使用一个原子来确保该值只被替换一次,但这就像是作弊。

还有其他方法吗?

1 个答案:

答案 0 :(得分: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)中删除被替换的项目,并通过递归进一步传递,直到它为空)