数据查询的 Clojure prewalk 无限递归
Clojure prewalk infinite recursion for datomic queries
我需要动态修改这个结构的数据:
[: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)
一旦 walk 找到 :list/items
并将其替换为 '(default :list/items [])
,它就会在替换值中找到 :list/items
,并将其替换。等等等等。
我可以使用原子来确保值只被替换一次,但这感觉就像作弊。
还有其他方法吗?
在这种情况下,您可能必须使用 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
)中删除替换的项目,并通过递归进一步传递它,直到它为空)
我需要动态修改这个结构的数据:
[: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)
一旦 walk 找到 :list/items
并将其替换为 '(default :list/items [])
,它就会在替换值中找到 :list/items
,并将其替换。等等等等。
我可以使用原子来确保值只被替换一次,但这感觉就像作弊。
还有其他方法吗?
在这种情况下,您可能必须使用 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
)中删除替换的项目,并通过递归进一步传递它,直到它为空)