2

What's the idiomatic way of merging two lists of maps in Clojure where each map entry is identified by an id key?

What's an implementation for foo so that

(foo '({:id 1 :bar true :value 1} 
       {:id 2 :bar false :value 2} 
       {:id 3 :value 3})
     '({:id 5 :value 5} 
       {:id 2 :value 2} 
       {:id 3 :value 3}
       {:id 1 :value 1} 
       {:id 4 :value 4})) => '({:id 1 :bar true :value 1}
                               {:id 2 :bar false :value 2}
                               {:id 3 :value 3}
                               {:id 4 :value 4}
                               {:id 5 :value 5})

is true?

tobiasbayer
  • 10,269
  • 4
  • 46
  • 64
  • possible duplicate of [Outer join in Clojure](http://stackoverflow.com/questions/13009939/outer-join-in-clojure) – A. Webb Aug 21 '13 at 17:31

3 Answers3

2
(defn merge-by
  "Merges elems in seqs by joining them on return value of key-fn k.
   Example: (merge-by :id [{:id 0 :name \"George\"}{:id 1 :name \"Bernie\"}]
                          [{:id 2 :name \"Lara\"}{:id 0 :name \"Ben\"}])
   => [{:id 0 :name \"Ben\"}{:id 1 :name \"Bernie\"}{:id 2 :name \"Lara\"}]"
  [k & seqs]
  (->> seqs
       (map (partial group-by k))
       (apply merge-with (comp vector
                               (partial apply merge)
                               concat))
       vals
       (map first)))
Leon Grapenthin
  • 9,246
  • 24
  • 37
1

This is generalized so that you can have an arbitrary number of input sequences, and an arbitrary grouping selector:

(def a [{:id 5 :value 5} 
        {:id 2 :value 2} 
        {:id 3 :value 3}
        {:id 1 :value 1} 
        {:id 4 :value 4}])

(def b [{:id 1 :bar true :value 1} 
        {:id 2 :bar false :value 2} 
        {:id 3 :value 3}])

(def c [{:id 1 :bar true :value 1}
        {:id 2 :bar false :value 2}
        {:id 3 :value 3}
        {:id 4 :value 4}
        {:id 5 :value 5}])

(defn merge-vectors
  [selector & sequences]
  (let [unpack-grouped (fn [group]
                         (into {} (map (fn [[k [v & _]]] [k v]) group)))
        grouped (map (comp unpack-grouped (partial group-by selector))
                     sequences)
        merged (apply merge-with merge grouped)]
    (sort-by selector (vals merged))))

(defn tst
  []
  (= c
   (merge-vectors :id a b)))
noisesmith
  • 20,076
  • 2
  • 41
  • 49
1

How about this:

(defn foo [& colls]
  (map (fn [[_ equivalent-maps]] (apply merge equivalent-maps)) 
       (group-by :id (sort-by :id (apply concat colls)))))
aldazosa
  • 345
  • 3
  • 5