Dennis Hackethal’s Blog

My blog about coding, philosophy, and anything else that interests me.

Neat Clojure Function

Yesterday, I learned about the Clojure core function merge-with. Imagine you keep an inventory of two stores:

(def inventory-1 {:cornflakes 3 :chunky-soup 2 :nutella 2})
(def inventory-2 {:cornflakes 1 :chunky-soup 4 :milk 2})

Say you want to find out the total number of each item. In this case, a map saying that you have 4 boxes of cornflakes left, 6 chunky soups, 2 jars of Nutella, and 2 cartons of milk. How could you go about this? You can’t just merge them or the second map’s items will just override those it shares with the first one’s:

(merge inventory-1 inventory-2)
> {:cornflakes 1 :chunky-soup 4 :nutella 2 :milk 2}

This says you have, say, only 4 chunky soups total, when in reality you have 6.

Try merge-with instead:

(merge-with + inventory-1 inventory-2)
> {:cornflakes 4 :chunky-soup 6 :nutella 2 :milk 2}

What’s going on here? It basically iterates over the tuples in each map and constructs a new map that will have both maps’ keys. Whenever the given maps share a key, it passes both corresponding values to the given function (in this case +); otherwise, it just adds the remaining key and value to the result without invoking the given function.

In this particular case, it sees that :cornflakes occurs in both maps, once with value 3, and once with value 1, so the result will have an entry of :cornflakes (+ 3 1) . It also sees that :nutella only occurs in the first map, so an entry of :nutella 2 is added to the result. And so on.

This works for any number of maps:

(merge-with + {:a 1} {:a 2} {:b 1})
> {:a 3 :b 1}

Of course, it works for any function, not just +. What function you pass it is up to you. You may get creative; say you have a bunch of vectors of numbers in each key, and you want to concat them:

(merge-with concat {:a [1 2 3]} {:a [4]} {:b [1 5]})
> {:a (1 2 3 4) :b [1 5]}

(Note that while the value of :a value turned into a list (the result of concat:b‘s stayed a vector because concat was not invoked with it since no other map had :b for a key.)

Or perhaps you can add a bunch of integers up that are hidden within vectors behind keys? You sure can:

(merge-with (comp #(apply + %) concat)
  {:a [4 2 1]} {:a [7] :b [3]} {:b [3 2]})
> {:a 14 :b 8}

As you can see, merge-with is powerful. Neato, huh fellas?

PS: Guess how often I miss OOP when writing Clojure.

If you enjoyed this article, follow me on Twitter for more content like it.


What people are saying

What are your thoughts?

You are responding to comment #. Clear

Preview

Markdown supported. cmd + enter to submit. You are responsible for what you write.
This small puzzle helps protect the blog against automated spam.

Preview