2
votes

I have a map where each key has a collection as value:

{:name ["Wut1" "Wut2"] :desc ["But1" "But2"]}

The value collections can be assumed to have the same number of elements.

How to transform it into a list (or vector) where each element is a map with key being the key from original collection and value being 1 value like:

[{:name "Wut1" :desc "But1"} {:name "Wut2" :desc "But2"}]

It should be said that the number of keys is not known before (so I can't hardcode for :name and :desc)

2

2 Answers

3
votes

As a general rule apply map vector will always do a transpose operation:

(apply map vector '(["Wut1" "Wut2"] ["But1" "But2"]))
;;=> (["Wut1" "But1"] ["Wut2" "But2"])

With that one trick in hand, we just need to make sure to zipmap each vector object (e.g. ["Wut1" "But1"]) with the keys:

(fn [m]
  (->> m
       vals
       (apply map vector)
       (map #(zipmap (keys m) %))))
;;=> ({:name "Wut1", :desc "But1"} {:name "Wut2", :desc "But2"})

Another rule to go by is that there's a problem when you have consecutive maps - really you ought to bring together the map functions, rather than needlessly transferring items from list to list. (This can often be done using comp). See @amalloy's solution for how to avoid double mapping. Also of course the keys function call should not be done repeatedly as here.

6
votes
(fn [m]
  (let [ks (keys m)]
    (apply map (fn [& attrs]
                 (zipmap ks attrs))
           (vals m))))
  1. Get the list of keys to use to label everything with
  2. Use apply map to "transpose" the list-of-lists in (vals m) from "for each attribute, a list of the values it should have" to "for each object, a list of the attributes it should have".
  3. zipmap that back together with the keys extracted in the first part.