Question

I have a number of XML files that I have successfully worked with to produce a number of counts against a monthly time period although I need to transform it to condense it slightly. The list I have produced is done via a number of for loops as below (for example) and I have output it into a table in the format below:

LEVEL1 (for [forloop LVL4_records]
{
    :MONTH(get (split (:date forloop) #"-") 1)
    :LEVEL(:lvl forloop)
})

group-by-date(group-by :MONTH LEVEL1)

LEVEL1_2 (for [forloop2 group-by-date]
{
    :MONTH(:MONTH (first(second forloop2)))
    :LEVEL(:LEVEL(first (second forloop2)))
    :TOTAL(count (distinct (second forloop2)))
})

MONTH--LEVEL--COUNT

Jan--------01---------10

Jan--------02---------55

Jan--------03---------112

Feb--------01---------23

Feb--------02---------30

Feb--------03---------268


What I want to do is organise this into a way that the Months are distinct and the Levels are the main headers like:


MONTH--LEVEL 01--LEVEL 02-- LEVEL 03

Jan--------10------------55-------------112

Feb--------23------------30-------------268


I'm guessing that I need another loop to run through the different levels and to make the month distinct although I've tried a few things and they're not working... the code up to now works as in the first table so any help here would be much appreciated in reformatting the data into the second style.

Was it helpful?

Solution 2

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 gets out and simply use the map itself as a function:

((by-month "Jan") 3) ;=> 112

OTHER TIPS

Take each group of LEVEL from the data here

MONTH--LEVEL--COUNT

Jan--------01---------10

Jan--------02---------55

Jan--------03---------112

Feb--------01---------23

Feb--------02---------30

Feb--------03---------268

to and convert them into a map, from :COUNT to :MONTH, then print the table through these maps using the format you provided.

e.g. the map should be something like this

{:LEVEL 1 :DATA [{:MONTH "Jan" :COUNT 10}
                 {:MONTH "Feb" :COUNT 23}]
 :LEVEL 2 :DATA [{:MONTH "Jan" :COUNT 55}
                 {:MONTH "Feb" :COUNT 30}]
 :LEVEL 3 :DATA [{:MONTH "Jan" :COUNT 112}
                 {:MONTH "Feb" :COUNT 268}]

or even

{1  {"Jan" 10
     "Feb" 23}
 2  {"Jan" 55
     "Feb" 30}
 3  {"Jan" 112
     "Feb" 268}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top