UVTC's Blog

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 a nil argument, but you want to use it where it might indeed be passed nil.

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)))