开发者

How to filter a persistent map in Clojure?

I have a persistent map which I want to filter. Something like this:

(filter #(-> % val (= 1)) {:a 1 :b 1 :c 2})

The above comes out as ([:a 1] [:b 1]) (a lazy sequence of map entries). However I want to be get {:a 1 :b 1}.

How can I filter a map so it remains a map without having to开发者_高级运维 rebuild it from a sequence of map entries?


And another one:

(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}))

Of course this does rebuild the map from a sequence of map entries, but there is no way around it. If you're going to filter the entries by value, you're going to have to go through them one by one to see which values match your predicate and which don't.

Updated (see comments below):

With the newly introduced keep function, the source of which you can see here (should work just fine in Clojure 1.1 if you want to backport), this seems like a nice way to go about it if you don't use nil as a key:

(let [m {:a 1 :b 1 :c 2}]
  (apply dissoc m (keep #(-> % val (= 1) (if nil (key %))) m)))
; => {:a 1, :b 1}

Also, if you do actually see a slowdown related to rebuilding your map, you can use a transient map at the rebuilding step:

(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}


Per your comment to 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}

I don't see that you're going to gain much with this vs. Michał's version.


Need to traverse all entries, but can leverage Clojures persistent maps:

(apply dissoc my-map (for [[k v] my-map :when (not= v 1)] k))


Here's another one using reduce-kv

(defn filter-kv [pred map]
  (reduce-kv (fn [accumulator key value]
               (if (pred key value)
                 (assoc accumulator key value)
                 accumulator)) {} map))

Usage

(filter-kv (fn [key _]
             (not (= key "a"))) {"a" {:some "a"}
                                 "b" {:some "b"}
                                 "c" {:some "c"}})

>> {"b" {:some "b"}
    "c" {:some "c"}}


I tried myself on macro for this based on kotarak's version. Its my first macro doing something useful, so please bear with me and comments welcome.

(defmacro filter-map [bindings pred m]
  `(select-keys ~m
    (for [~bindings ~m
      :when ~pred]
      ~(first bindings)
    )
  )
)

Example

user=> (filter-map [key val] (even? (:attr val)) {:a {:attr 2} :b {:attr 3} :c {:attr 4}})
{:c {:attr 4}, :a {:attr 2}}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜