Filter a Map in Clojure


Have some keys dragging you down? Let's filter those bad guys out. This is Clojure. Ready your parens.

The Map

(def articles
  {:a {:draft "false"}
   :b {:draft "true"}
   :c {:draft "true"}
   :d {:draft "false"}})

Looping Through the Map

To do this, use a for expression. See the Looping Through a Map in Clojure article for more explanation. The result will look like this:

(for [[k v] articles] v)
; => ({:draft "false"} {:draft "true"} {:draft "true"} {:draft "false"})

The Filter

But I don't just want to loop, I want to filter. So let's first define a filter that's useful for this map data:

(defn is-published? [x]
  (not= "true" (:draft x)))

Filter the Map

We'll apply the filter with a :when modifier:

(for [[k v] articles :when (is-published? v)] v)
; => ({:draft "false"} {:draft "false"})

Filter and Keep Keys and Values

But when we "filter a map", we want a map to come out the other side, not a sequence of the values in the original map.

To do this, we need to filter to keys instead of values, as we've been doing. And then we pass that sequence of keys to another api, select-keys.

Its function signature:

(select-keys map keyseq)

We'll pass the original map and then the filtered keys expression from the for loop:

(select-keys articles (for [[k v] articles :when (is-published? v)] k))
; => {:a {:draft "false"}, :d {:draft "false"}}

(Note that important change to return k from the for expression.)

Yay!, we filtered the map.

There's gotta be 799 different ways to do that. Is there a more readable or more elegant way that you prefer?