Gather up values in Clojure
2016-09-24
Write a function to turn
[[:a 1] [:b 2] [:c 3] [:a 11] [:c 33]]
into
{:a [1 11], :b [2], :c [3 33]}
Firstly, note:
(def m {:a [1]})
(assoc m :b 2) ;=> {:a [1] :b 2}
(assoc-in m [:b] 2) ;=> {:a [1] :b 2}
(conj [1]) ;=> [1] <-- You didn't conj anything on.
(update-in m [:a] conj) ;=> {:a [1]} <-- Again, didn't conj anything on.
(update-in m [:a] conj 11) ;=> {:a [1 11]}
(get m :b) ;=> nil, and note that `assoc-in` and `update-in` use `get`.
(conj nil) ;=> nil
(update-in m [:b] conj) ;=> {:a [1] :b nil}
(conj nil 2) ;=> (2)
(update-in m [:b] conj 2) ;=> {:a [1] :b (2)}
(update-in m [:b] (fnil conj []) 2) ;=> {:a [1] :b [2]}
Recall,
fnil
is used when you have a function that doesn’t take anil
argument, but you want to use it where it might indeed be passednil
.
So, if you want a function that gathers up values and puts them into nice neat cubbies (vectors), maybe try:
#!/usr/bin/env inlein
'{:dependencies [[org.clojure/clojure "1.8.0"]]}
(defn gather-up-vals
"Pass in a sequence of k-v pairs."
[pairs]
(reduce (fn [accum [k v]]
(update-in accum [k] (fnil conj []) v))
{}
pairs))
(defn main
[]
(let [pairs [[:a 1] [:b 2] [:c 3] [:a 11] [:c 33]]]
(println pairs)
(println (gather-up-vals pairs))))
(main)
Output:
[[:a 1] [:b 2] [:c 3] [:a 11] [:c 33]]
{:a [1 11], :b [2], :c [3 33]}
That said, another way to get a similar result (which happens to execute just a bit faster for me):
(defn gather-up-vals-2
[pairs]
(reduce (fn [accum [k pairs]]
(assoc accum k (map second pairs)))
{}
(group-by first pairs)))