Reputation: 62
I am given a list maps:
({:a 1 :b ["red" "blue"]} {:a 2 :b ["green"]} {:a 1 :b ["yellow"]} {:a 2 :b ["orange"]})
and I need to combine them to ultimately look like this:
({:a 1 :b ["red" "blue" "yellow"]} {:a 2 :b ["green" "orange"]})
Where the maps are combined based off the value of the key "a". So far, I have this
(->> (sort-by :a)
(partition-by :a)
(map (partial apply merge)))
But the merge will overwrite the vector in "b" with the last one giving me
({:a 1 :b ["yellow"]} {:a 2 :b ["orange"]})
Upvotes: 2
Views: 866
Reputation: 92147
You don't really want to sort or partition anything: that is just CPU time spent on nothing, and at the end you have an awkward data structure (a list of maps) instead of something that would be more convenient, a map keyed by the "important" :a
value.
Rather, I would write this as a reduce, where each step uses merge-with
on the appropriate subsection of the eventual map:
(defn combine-by [k ms]
(reduce (fn [acc m]
(update acc (get m k)
(partial merge-with into)
(dissoc m k)))
{}, ms))
after which we have
user=> (combine-by :a '({:a 1 :b ["red" "blue"]}
{:a 2 :b ["green"]}
{:a 1 :b ["yellow"]}
{:a 2 :b ["orange"]}))
{1 {:b ["red" "blue" "yellow"]}, 2 {:b ["green" "orange"]}}
which is usefully keyed for easily looking up a map by a specific :a
, or if you prefer to get the results back out as a list, you can easily unroll it.
Upvotes: 0
Reputation: 3801
Instead of merge
, use merge-with
, i.e.
(->> a (sort-by :a)
(partition-by :a)
(map (partial
apply
merge-with (fn [x y] (if (= x y) x (into x y))))))
Outputs
({:a 1, :b ["red" "blue" "yellow"]} {:a 2, :b ["green" "orange"]})
Upvotes: 3