Search code examples
mergeclojuregroup-byreduce

Clojure merge list of maps based on keys


I need to convert the following data:

[{["A" 1] [{:ms "A", :s 1, :v 15}], ["A" 2] [{:ms "A", :s 2, :v 18}], ["A" 4] [{:ms "A", :s 4, :v 19}]}
 {["A" 1] [{:ms "A", :s 1, :v2 5}], ["A" 2] [{:ms "A", :s 2, :v2 8}], ["B" 4] [{:ms "B", :s 4, :v2 9}]}]

to:

[{:ms "A", :s 1, :v 15, :v2 5} {:ms "A", :s 2, :v 18, :v2 8} {:ms "A", :s 4, :v 19} {:ms "B", :s 4, :v2 9}]

I'm struggling with the implementation, any help would be appreciated :)


Solution

  • this would work:

    (def data [{["A" 1] [{:ms "A", :s 1, :v 15}],
                ["A" 2] [{:ms "A", :s 2, :v 18}],
                ["A" 4] [{:ms "A", :s 4, :v 19}]}
               {["A" 1] [{:ms "A", :s 1, :v2 5}],
                ["A" 2] [{:ms "A", :s 2, :v2 8}],
                ["B" 4] [{:ms "B", :s 4, :v2 9}]}])
    
    (map (partial apply merge) (vals (apply merge-with concat data)))
    

    here's a step by step explaination:

    first of all we merge all the toplevel maps, concatenating values for equal keys:

    (apply merge-with concat data)
    ;;=> {["A" 1] ({:ms "A", :s 1, :v 15} {:ms "A", :s 1, :v2 5}), 
    ;;    ["A" 2] ({:ms "A", :s 2, :v 18} {:ms "A", :s 2, :v2 8}), 
    ;;    ["A" 4] [{:ms "A", :s 4, :v 19}], 
    ;;    ["B" 4] [{:ms "B", :s 4, :v2 9}]}
    

    then for every value in the map we should perform the merge to one map:

    (apply merge '({:ms "A", :s 1, :v 15} {:ms "A", :s 1, :v2 5}))
    ;;=> {:ms "A", :s 1, :v 15, :v2 5}
    

    so we should use map:

    (map (partial apply merge) (vals (apply merge-with concat data)))
    ;;=> ({:ms "A", :s 1, :v 15, :v2 5}
    ;;    {:ms "A", :s 2, :v 18, :v2 8}
    ;;    {:ms "A", :s 4, :v 19}
    ;;    {:ms "B", :s 4, :v2 9})