tags:

views:

99

answers:

4

I have a Clojure map that may contain values that are nil and I'm trying to write a function to remove them, without much success (I'm new to this).

E.g.:

(def record {:a 1 :b 2 :c nil})
(merge (for [[k v] record :when (not (nil? v))] {k v}))

This results in a sequence of maps, which isn't what I expected from merge:

({:a 1} {:b 2})

I would like to have:

{:a 1, :b 2}
+1  A: 

You could squish it into a map:

(into {} (remove (fn [[k v]] (nil? v)) {:a 1 :b 2 :c nil}))
=> {:a 1 :b 2}
thnetos
+4  A: 

your for list comprehension returns a LIST of maps, so you need to APPLY this list to the merge function as optional arguments:

user> (apply merge (for [[k v] record :when (not (nil? v))] {k v}))
{:b 2, :a 1}      

More concise solution by filtering the map as a sequence and conjoining into a map:

user> (into {} (filter second record))
{:a 1, :b 2}  

Dont remove false values:

user> (into {} (remove (comp nil? second) record))
{:a 1, :b false}  

Using dissoc to allow persistent data sharing instead of creating a whole new map:

user> (apply dissoc                                                                                            
       record                                                                                                  
       (for [[k v] record :when (nil? v)] k))
{:a 1, :b 2}  
Jürgen Hötzel
+1 for the (filter second ...) approach. Very clever.
Alex Taggart
Unfortunately the `(filter second ...)` approach does not work. `(into {} (filter second {:a true :b false}))` gives `{:a true}`-
kotarak
@kotorak: Good catch on the nil/false issue -> reedit
Jürgen Hötzel
The API doc for dissoc says: "Returns a new map of the same (hashed/sorted) type, that does not contain a mapping for key(s)". Like most of the API docs, I find this completely inpenetrable coming from a C++/Java background. Anyone care to explain what it means?
edoloughlin
+1 for pointing out data sharing with dissoc
Bendlas
A: 

You can use reduce.

user> (reduce (fn [m [k v]] (if (nil? v) m (assoc m k v))) {} record)
{:b 2, :a 1}

If for some reason you want to keep the ordering (which is usually not important in a map), you can use dissoc.

user> (reduce (fn [m [k v]] (if (nil? v) (dissoc m k) m)) record record)
{:a 1, :b 2}
Jonathan Tran
A: 

Though Jürgen's (filter second record) approach gets my vote for Niftiest Clojure Trick, I thought I'd toss another way out there, this time using select-keys:

user> (select-keys record (for [[k v] record :when (not (nil? v))] k))
{:b 2, :a 1}
Christopher Maier