【发布时间】:2010-05-02 15:08:51
【问题描述】:
我有一个要过滤的持久性地图。像这样的:
(filter #(-> % val (= 1)) {:a 1 :b 1 :c 2})
上面的结果是([:a 1] [:b 1])(映射条目的惰性序列)。但是我想得到{:a 1 :b 1}。
如何过滤地图,使其保持地图不变,而无需从一系列地图条目中重建它?
【问题讨论】:
标签: clojure
我有一个要过滤的持久性地图。像这样的:
(filter #(-> % val (= 1)) {:a 1 :b 1 :c 2})
上面的结果是([:a 1] [:b 1])(映射条目的惰性序列)。但是我想得到{:a 1 :b 1}。
如何过滤地图,使其保持地图不变,而无需从一系列地图条目中重建它?
【问题讨论】:
标签: clojure
还有一个:
(let [m {:a 1 :b 2 :c 1}]
(select-keys m (for [[k v] m :when (= v 1)] k)))
【讨论】:
(into {} (filter #(-> % val (= 1)) {:a 1 :b 1 :c 2}))
当然,这确实从一系列地图条目重建地图,但没有办法绕过它。如果您要按值过滤条目,则必须逐个检查它们以查看哪些值与您的谓词匹配,哪些不匹配。
已更新(参见下面的 cmets):
使用新引入的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}
【讨论】:
根据您对 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ł 的版本相比,您不会获得太多收益。
【讨论】:
需要遍历所有条目,但可以利用 Clojures 持久映射:
(apply dissoc my-map (for [[k v] my-map :when (not= v 1)] k))
【讨论】:
这是另一个使用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"}}
【讨论】:
{} 为(empty map) 以使其更通用并适用于任何支持kv-reduce 协议的集合。
{} 开头的结果映射中会有一些assoc。如果期望只删除几个键,那么使用原始的map 开始accumulator 并将if 反转为dissoc 过滤掉的键会更有效。
我根据 kotarak 的版本尝试了自己的宏。这是我的第一个宏做一些有用的事情,所以请多多包涵,欢迎 cmets。
(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}}
【讨论】: