使用 Specter 实现递归映射查询

7
有没有一种简单的方式在Specter中收集满足谓词条件的所有结构?
(./pull '[com.rpl/specter "1.0.0"])

(use 'com.rpl.specter)

(def data {:items [{:name "Washing machine"
                    :subparts [{:name "Ballast" :weight 1}
                               {:name "Hull"    :weight 2}]}]})



(reduce + (select [(walker :weight) :weight] data))
;=> 3

(select [(walker :name) :name] data)
;=> ["Washing machine"]

我们如何获取所有的:name值,包括["Ballast" "Hull"]?

“(./pull ...)” 是从哪里来的?我以前没见过。 - Petrus Theron
3个回答

5
这里有一种方法,使用recursive-pathstay-then-continue来完成实际工作。(如果你在路径参数中省略了最后的:name,则会返回完整的“项/部件映射”,而不仅仅是:name字符串。)
(def data
  {:items [{:name "Washing machine"
            :subparts [{:name "Ballast" :weight 1}
                       {:name "Hull" :weight 2}]}]})

(specter/select
  [(specter/recursive-path [] p
     [(specter/walker :name) (specter/stay-then-continue [:subparts p])])
   :name]
  data)
;= ["Washing machine" "Ballast" "Hull"]
< p > 更新: 回应下面的评论,这是上述内容的一个版本,它可以降入树的任意分支,而不仅仅是进入任何给定节点的:subparts分支,排除:name(这是我们想要提取树中其值的键,不应被视为分支点):

(specter/select
  [(specter/recursive-path [] p
     [(specter/walker :name)
      (specter/stay-then-continue
        [(specter/filterer #(not= :name (key %)))
         (specter/walker :name)
         p])])
   :name]
  ;; adding the key `:subparts` with the value [{:name "Foo"}]
  ;; to the "Washing machine" map to exercise the new descent strategy
  (assoc-in data [:items 0 :subparts2] [{:name "Foo"}]))

;= ["Washing machine" "Ballast" "Hull" "Foo"]

是否可以遍历每个“子部分”,但不包括:name?这里的“子部分”只是一个示例。 - jwinandy

1

我认为你可以使用clojure.walk包递归地迭代地图。在每一步中,您可以检查当前值是否符合谓词,并将其推入原子以收集结果。


1
你可以使用 reduce 来代替原子。 - bfontaine

1

好的,但它不能处理递归结构 [{:a 0 :as [{:a 1} {:a 2}]} {:a3} {:as {:a 4}}]=> (select [(walker :a) (selected? [:a even?])] [{:a 0 :as [{:a 1} {:a 2}]} {:a 3} {:as {:a 4}}]) 生成 [{:a 0, :as [{:a 1} {:a 2}]} {:a 4}] 但不是 [{:a 0, :as [{:a 1} {:a 2}]} {:a 2} {:a 4}] - jwinandy
当我在真正的计算机上使用repl时,我会尽量更具体。简短回答是walker可能过于急切地递归。请查看链接文章中树遍历的示例。 - Arthur Ulfeldt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接