This is kind of a tricky thing to do, but it's doable. Here's what I came up with:
(def by-level
{1 {"Jan" 10, "Feb" 23}
2 {"Jan" 55, "Feb" 30}
3 {"Jan" 112, "Feb" 268}})
(def by-month
(reduce (fn [table [level months]]
(apply merge-with merge table (map (fn [[month count]]
{month {level count}})
months)))
{}
by-level))
; by-month =>
{"Jan" {3 112, 2 55, 1 10}, "Feb" {3 268, 2 30, 1 23}}
(This is snowballing off of Albus' answer -- he pointed out that your map should be arranged using the levels as keys, since that's how your XML file is laid out.)
reduce
is the functional counterpart to a for-loop. We're using it here to iterate through our table (by-level
) and build up a new table by-month
starting from an empty map, {}
. On each iteration, we take one of the entries in by-level
(for example, {1 {"Jan" 10, "Feb" 23}}
) and use map
on each month-count pair to return a list of maps of the form {"Jan" {1 10}}
(the first number is the level (1) and the second is the count (10)). Then we merge
these "month to level/count" maps into our table-in-progress. Once we've iterated through each "level" in our original map, we end up with a map of each month to a secondary map of each level and its count.
"apply merge-with merge
" might look a little weird at first. The reason we have a secondary merge
operation is that we're dealing with nested maps. If we had just said apply merge
instead, "Jan"
and "Feb"
would be "overwritten" with each pass through the next level, and in the end we would just have something like {"Jan" {3 112}, "Feb" {3 268}}
. Using merge-with
, we make it so that if a key like "Jan"
already exists, we merge
the existing value for "Jan"
with the new value -- the values that we're working with are maps like {2 55}
, so instead of replacing the value we have, we're "adding onto it" by merging in a new map with a new level/count combination.
Now you can obtain individual values from the map using get
, for example:
(get (get by-month "Jan") 3) ;=> 112 (January, Level 3)
Or you can leave the get
s out and simply use the map itself as a function:
((by-month "Jan") 3) ;=> 112