Reputation: 5891
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}
Upvotes: 41
Views: 28748
Reputation: 422
reduce-kv can also be used to remove the keys
(reduce-kv (fn [m key value]
(if (nil? value)
(dissoc m key)
m))
{:test nil, :test1 "hello"}
{:test nil, :test1 "hello"})
Upvotes: 3
Reputation: 91
A variation on @Eelco's answer:
(defn remove-nils [m]
(let [f (fn [x]
(if (map? x)
(let [kvs (filter (comp not nil? second) x)]
(if (empty? kvs) nil (into {} kvs)))
x))]
(clojure.walk/postwalk f m)))
To @broma0's point, it elides any empty maps.
user> (def m {:a nil, :b 1, :c {:z 4, :y 5, :x nil}, :d {:w nil, :v nil}})
user> (remove-nils m)
{:b 1, :c {:z 4, :y 5}}
user> (remove-nils {})
nil
Upvotes: 9
Reputation: 768
Here's one that works on maps and vectors:
(defn compact
[coll]
(cond
(vector? coll) (into [] (filter (complement nil?) coll))
(map? coll) (into {} (filter (comp not nil? second) coll))))
Upvotes: 2
Reputation: 1698
Here is one that works on nested maps:
(defn remove-nils
[m]
(let [f (fn [[k v]] (when v [k v]))]
(postwalk (fn [x] (if (map? x) (into {} (map f x)) x)) m)))
Upvotes: 10
Reputation: 603
Jürgen Hötzel solution refined to fix the nil/false issue
(into {} (filter #(not (nil? (val %))) {:a true :b false :c nil}))
A bit shorter version of @thnetos solution
(into {} (remove #(nil? (val %)) {:a true :b false :c nil}))
Upvotes: 5
Reputation: 19727
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}
Upvotes: 73
Reputation: 484
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}
Upvotes: 3
Reputation: 15276
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}
Upvotes: 1
Reputation: 1326
You could squish it into a map:
(into {} (remove (fn [[k v]] (nil? v)) {:a 1 :b 2 :c nil}))
=> {:a 1 :b 2}
Upvotes: 5