Reputation: 81
I have a list of maps
( {:path "first" :size "1 gb"}
{:path "second" :size "500 mb"}
...)
and another list of maps
( {:path "first" :size "1 gb" :date "1"}
{:path "second" :size "500 mb" :date "1"}
{:path "first" :size "0.9 gb" :date "2"}...
{:path "second" :size "400 mb" :date "2"}...
...)
I want to get the first list of maps transformed to something like
( {:path "first" :sizeon1 "1 gb" :sizeon2 "0.9 gb"...}
{:path "second" :sizeon1 "500 mb" :sizeon2 "400 mb"...}
....)
I am a Clojure noob and having a hard time doing this. Can you please help me out?
Upvotes: 1
Views: 311
Reputation: 34790
(def data '({:path "first" :size "1 gb" :date "1"}
{:path "second" :size "500 mb" :date "1"}
{:path "first" :size "0.9 gb" :date "2"}
{:path "second" :size "400 mb" :date "2"}))
(defn- reduce-group [g]
(reduce (fn [acc m] (assoc acc
(keyword (str "sizeon" (:date m)))
(:size m)))
(first g) g))
(let [groups (group-by :path data)]
(map reduce-group (vals groups)))
Upvotes: 2
Reputation: 17859
what would i do, is to rethink the resulting data structure:
I don't know about how would you potentially use the resulting collection, but naming keys :sizeonX
, especially when there is potentially variable amount of registered dates or maybe some of them are missing (like for example if you have dates 1
and 3
for first path, and 1
2
3
5
for the second one) leads to a mess of unpredictably named keys in resulting maps, which would make it way more difficult when it comes to retrieving these keys.
to me it looks like that it would be better to use this structure:
{:path "first" :sizes {"1" "500" "2" "1g" "10" "222"}}
so this sizes map is easily iterated and processed.
that is how would i do that:
(def data '({:path "first" :size "1 gb" :date "1"}
{:path "first" :size "0.9 gb" :date "3"}
{:path "second" :size "500 mb" :date "1"}
{:path "second" :size "700 mb" :date "2"}
{:path "second" :size "400 mb" :date "3"}
{:path "second" :size "900 mb" :date "5"}))
(map (fn [[k v]] {:path k
:sizes (into {} (map (juxt :date :size) v))})
(group-by :path data))
;; ({:path "first", :sizes {"1" "1 gb", "3" "0.9 gb"}}
;; {:path "second", :sizes {"1" "500 mb",
;; "2" "700 mb",
;; "3" "400 mb",
;; "5" "900 mb"}})
update
but as you still need the structure from the question, i would do it like this:
(map (fn [[k v]]
(into {:path k}
(map #(vector (keyword (str "sizeon" (:date %)))
(:size %))
v)))
(group-by :path data))
;;({:path "first", :sizeon1 "1 gb", :sizeon3 "0.9 gb"}
;; {:path "second",
;; :sizeon1 "500 mb", :sizeon2 "700 mb",
;; :sizeon3 "400 mb", :sizeon5 "900 mb"})
which is basically similar to @superkonduktr variant.
Upvotes: 2
Reputation: 655
It all becomes clear when you break down your task into smaller parts.
First, define a helper to create those :sizeon1
keys in the result dataset:
(defn date-key
[date]
(keyword (str "sizeon" date)))
Next, you want to reduce a collection of single path data into an aggregated map, assuming such a collection looks as you described:
[{:path "first" :size "1 gb" :date "1"}
{:path "first" :size "0.9 gb" :date "2"}
;; ...
]
reduce
is just the tool for that:
(defn reduce-path
[path-data]
(reduce
;; A function that takes an accumulator map and an element in the collection
;; from which you take date and size and assoc them under the appropriate keys
(fn [acc el]
(let [{:keys [date size]} el]
(assoc acc (date-key date) size)))
;; A starting value for the accumulator containing the common path
;; for this collection
{:path (:path (first path-data))}
;; The collection of single path data to reduce
path-data))
Finally, take the raw dataset containing different paths, partition it by path, and map the reduce-path
function onto it.
(def data
[{:path "first" :size "1 gb" :date "1"}
{:path "first" :size "0.9 gb" :date "2"}
{:path "second" :size "500 mb" :date "1"}
{:path "second" :size "400 mb" :date "2"}])
(->> data
(partition-by :path)
(map reduce-path))
Note that this code assumes that your initial data
collection is already sorted by :path
. Otherwise, partition-by
will not work as you would expect, and the data will have to be prepared accordingly.
Upvotes: 2