我有一个我要过滤的持久性地图。像这样:
(filter #(-> % val (= 1)) {:a 1 :b 1 :c 2})
以上是([:a 1] [:b 1])
(一个懒惰的地图条目序列)。但是我希望得到{:a 1 :b 1}
。
如何过滤地图以使其保持地图而无需从一系列地图条目重建地图?
答案 0 :(得分:47)
还有一个:
(let [m {:a 1 :b 2 :c 1}]
(select-keys m (for [[k v] m :when (= v 1)] k)))
答案 1 :(得分:20)
(into {} (filter #(-> % val (= 1)) {:a 1 :b 1 :c 2}))
当然这个 从一系列地图条目重建地图,但没有办法绕过它。如果您要按值过滤条目,则必须逐个查看条目以查看哪些值与谓词匹配,哪些值不匹配。
更新(见下面的评论):
使用新引入的keep
函数,您可以看到here的源代码(如果您想要向后移动,在Clojure 1.1中应该可以正常工作),这似乎是一个很好的方法如果您不使用nil
作为密钥:
(let [m {:a 1 :b 1 :c 2}]
(apply dissoc m (keep #(-> % val (= 1) (if nil (key %))) m)))
; => {:a 1, :b 1}
此外,如果您确实看到与重建地图相关的减速,您可以在重建步骤中使用瞬态地图:
(persistent! (loop [m (transient {})
to-go (seq [[:a 1] [:b 2]])]
(if to-go
(recur (apply assoc! m (first to-go))
(next to-go))
m)))
; => {:a 1, :b 2}
答案 2 :(得分:3)
根据您对MichałMarczyk的评论:
(defn filter* [f map]
(reduce (fn [m [k v :as x]]
(if-not (f x)
(dissoc m k)
m))
map map))
user> (filter* #(-> % val (= 1)) {:a 1 :b 1 :c 2})
{:a 1, :b 1}
我不认为你会对Michał的版本获得太多收获。
答案 3 :(得分:3)
需要遍历所有条目,但可以利用Clojures持久映射:
(apply dissoc my-map (for [[k v] my-map :when (not= v 1)] k))
答案 4 :(得分:1)
我根据kotarak的版本尝试了自己的宏。它是我的第一个做有用的宏,所以请耐心等待,欢迎评论。
(defmacro filter-map [bindings pred m]
`(select-keys ~m
(for [~bindings ~m
:when ~pred]
~(first bindings)
)
)
)
实施例
user=> (filter-map [key val] (even? (:attr val)) {:a {:attr 2} :b {:attr 3} :c {:attr 4}})
{:c {:attr 4}, :a {:attr 2}}
答案 5 :(得分:0)
这里还有一个使用reduce-kv
(defn filter-kv [pred map]
(reduce-kv (fn [accumulator key value]
(if (pred key value)
(assoc accumulator key value)
accumulator)) {} map))
用法
(filter-kv (fn [key _]
(not (= key "a"))) {"a" {:some "a"}
"b" {:some "b"}
"c" {:some "c"}})
>> {"b" {:some "b"}
"c" {:some "c"}}