BrianN
BrianN

Reputation: 19

merging maps, but some values are nil

So I have two maps that can be anything and I want to merge them, but not include values that are nil. Let's say I have:

(def x {:a "A" :c 5 :d "D"})
(def y {:a 1 :b 2 :c nil})

I want to end up with

{:a 1 :b 2 :c 5 :d "D"}

I get the wrong value for :c if I just merge like (merge x y), but {:c nil} is there. I do not have control over what two maps come in. Any help would be aappreciated

Upvotes: 1

Views: 503

Answers (4)

Daniel Shriki
Daniel Shriki

Reputation: 41

A bit similar to what already Sean Corfield answered, but a little bit shorter:

(merge-with #(or %1 %2) x y)

Example:

user=> (def x {:a "A" :c 5 :d "D"})
#'user/x
user=> (def y {:a 1 :b 2 :c nil})
#'user/y
user=> (merge-with #(or %1 %2) x y)
{:a "A", :c 5, :d "D", :b 2}

Upvotes: 1

Rulle
Rulle

Reputation: 4901

Using into with the second argument being a filtering transducer results in a piece of code that is both fairly concise and readable:

(def x {:a "A" :c 5 :d "D"})
(def y {:a 1 :b 2 :c nil})

(into x (filter (comp some? val)) y)
;; => {:a 1, :c 5, :d "D", :b 2}

Only minor tweaks are required to have it remove nils from the first map too, if you need that:

(def x {:a "A" :c 5 :d "D" :e nil :f nil})
(def y {:a 1 :b 2 :c nil})

(into {} (comp cat (filter (comp some? val))) [x y])
;; => {:a 1, :c 5, :d "D", :b 2}

Upvotes: 5

Sean Corfield
Sean Corfield

Reputation: 6666

If you want to merge hash maps in a way that doesn't let nil values override non-nil values, you can use merge-with:

dev=> (def x {:a "A" :c 5 :d "D"})
#'dev/x
dev=> (def y {:a 1 :b 2 :c nil})
#'dev/y
dev=> (merge-with (fn [a b] (if (some? b) b a)) x y)
{:a 1, :c 5, :d "D", :b 2}
dev=> 

some? returns true if its argument is any non-nil value.

Upvotes: 5

Alan Thompson
Alan Thompson

Reputation: 29958

You can easily remove map entries that contain a nil like so:

(ns tst.demo.core
  (:use tupelo.core tupelo.test)
  (:require
    [schema.core :as s]
  ))

(defn remove-nil-vals
  [m]
  (into {}
        (for [[k v] m
              :when (not (nil? v))]
          [k v])))

(dotest
  (let [x              {:a "A" :c 5 :d "D"}
        y              {:a 1 :b 2 :c nil}
        all-maps       [x y]
        merged-no-nils (into {}
                             (for [m all-maps]
                               (remove-nil-vals m)))]

    (is= (remove-nil-vals x)  {:a "A" :c 5 :d "D"})
    (is= (remove-nil-vals y)  {:a 1 :b 2})
    (is= merged-no-nils       {:a 1 :c 5 :d "D" :b 2})

  ))

The above is based on this template project.

Upvotes: -1

Related Questions