Reputation: 258
I am trying to merge vector of maps.
I tried doing it using the reduce
method but unable to retrieve the expected result.
(def data '([{:padding-top "30px"} {:padding-top "40px"} {:padding-top "50px"}] [{:margin "40px"}]))
(reduce #(hash-map %1 %2) () data)
Input data:
(def data '([{:padding-top "30px"} {:padding-top "40px"} {:padding-top "50px"}] [{:margin "40px"}]))
(defn merge-data
[data]
)
Expected Output:
(merge-data data)
({:padding-top "30px" :margin "40px"}
{:padding-top "40px"}
{:padding-top "50px"})
Coming from the JS background, I can easily do it using something like forEach and conditionals to build expected output. But how to do it in functional way?
SOLUTION:
I was able to solve this problem in the following way
(defn merge-styles
[& args]
(let [max-count (apply max (map #(count %1) args))
items (map #(take max-count (concat %1 (repeat nil))) args)]
(apply map merge items)))
The code snippet makes it much clearer and leaner. Thanks a lot for all the answers which helped me get up to this point.
Upvotes: 0
Views: 443
Reputation: 29958
I would break it down into multiple simple steps like so:
(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test))
(def data [[{:padding-top "30px"} {:padding-top "40px"} {:padding-top "50px"}]
[{:margin "40px"}]])
; be clear about the 2 sequences we are combining
(def data-1 (first data))
(def data-2 (second data))
(defn do-merge []
(let [N (max (count data-1) (count data-2))]
(vec (for [i (range N)]
(let [map-1 (get data-1 i)
map-2 (get data-2 i)] ; returns `nil` if no value present
(merge map-1 map-2) ; if merge a map & nil, it's a noop
)))))
(dotest
(is= (do-merge)
[{:padding-top "30px", :margin "40px"}
{:padding-top "40px"}
{:padding-top "50px"}] ) )
You could also pad the shorter sequence with nils, then use (map merge ...)
, but you would have to make sure you get the lengths just right first which would still involve count
and max
....not sure if that is simpler.
Upvotes: 0
Reputation: 111
(map into [{:a 1} {:c 3}] (concat [{:b 2}] (repeat nil)))
yields
({:a 1, :b 2} {:c 3})
.
map
reads from both vectors and applies the respective indexes to into
, merging the maps for each index. As map
stops when one of the collections is exhausted, we need to concat nil
values to the shorter collection.
Edit: As always, this has already been answered before: Using 'map' with different sized collections in clojure
Upvotes: 0
Reputation: 3346
Generally, you can just use map
and merge
to merge collections of hashmaps, but it will end merging when one of the collections is exhausted.
You can create a function like the following to "extend" the collections to have the same length, then merge as usual:
(defn merge-all
"Merges two sequences of maps using merge. Consumes all entries."
[xs ys]
(let [n (max (count xs) (count ys))
xs (take n (concat xs (repeat nil)))
ys (take n (concat ys (repeat nil)))]
(map merge xs ys)))
(def data [[{:padding-top "30px"} {:padding-top "40px"} {:padding-top "50px"}]
[{:margin "40px"}]])
;; (apply merge-all data)
;; => ({:padding-top "30px", :margin "40px"} {:padding-top "40px"} {:padding-top "50px"})
Note that in your example, you used a parenthesis around the data
, but in Clojure this means you want to call it as if it were a function. In the example above I switched it to a [
and ]
instead. Also, note that this function depends in the fact that you can actually count
the collections that you pass to it (in Clojure you can have "infinite" collections, such as (range)
).
Upvotes: 1