Mario
Mario

Reputation: 4998

Groovy way to merge two lists of maps with some calculations

I need to merge to maps while perform some calculation for example having the following maps that always will be the same size

def map1 = [
    [name: 'Coord1', quota: 200],
    [name: 'Coord2', quota: 300]
]

def map2 = [
    [name: 'Coord1', copiesToDate: 270],
    [name: 'Coord2', copiesToDate: 30]
]

I want to get this map

def map3 = [
    [name: 'Coord1', quota: 200, copiesToDate: 60, balance: 140],
    [name: 'Coord2', quota: 300, copiesToDate: 30, balance: 270]
]

Right now i am trying with this solution and its working

def map4 = map1.collect { m1 ->
[
    name: m1.name,
    quota: m1.quota,
    copiesToDate: map2.find { m2 ->
        m1.name == m2.name
    }.copiesToDate,
    balanceToDate: m1.quota - map2.find { m2 ->
        m1.name == m2.name
    }.copiesToDate
]}

Could you please share a groovy way to do this task. Thanks

Upvotes: 3

Views: 2285

Answers (3)

Matias Bjarland
Matias Bjarland

Reputation: 4482

Grooviest code I could come up with:

def map3 = [map1, map2].transpose()*.sum().each { m ->
  m.balance = m.quota - m.copiesToDate
}

edit: as noted by Tim, this code works as long as the two input lists (map1 and map2) are of the same size and have the maps in order. If this is not the case I would recommend Tim's answer which handles those cases.

The above returns the map as defined in your question. The following code:

def list1 = [
    [name: 'Coord1', quota: 200],
    [name: 'Coord2', quota: 300]
]

def list2 = [
    [name: 'Coord1', copiesToDate: 60],
    [name: 'Coord2', copiesToDate: 30]
]

def x = [list1, list2].transpose()*.sum().each { m ->
  m.balance = m.quota - m.copiesToDate
}

x.each { 
  println it
}

demonstrates the idea and prints:

[name:Coord1, quota:200, copiesToDate:60, balance:140]
[name:Coord2, quota:300, copiesToDate:30, balance:270]

I have renamed map1 and map2 into list1 and list2 since they are in fact two lists containing inner maps.

The code is somewhat concise and might need a bit of explanation if you're not used to transpose and the groovy spread and map operations.

Explanation:

  1. [list1, list2] - first we create a new list where the two existing lists are elements. So we now have a list of lists where the elements in the inner lists are maps.
  2. .transpose() - we then call transpose which might need a bit of effort to grasp when you see it for the first time. If you have a list of lists, you can see transpose as flipping the lists "into the other direction".

In our case the two lists:

[[name:Coord1, quota:200], [name:Coord2, quota:300]]
[[name:Coord1, copiesToDate:60], [name:Coord2, copiesToDate:30]]

become:

[[name:Coord1, quota:200], [name:Coord1, copiesToDate:60]]
[[name:Coord2, quota:300], [name:Coord2, copiesToDate:30]]

i.e. after transpose, everything relating to Coord1 is in the first list and everything relating to Coord2 is in the second.

  1. Each of the lists we have now is a list of Maps. But what we want is just one map for Coord1 and one map for Coord2. So for each of the above lists, we now need to coalesce or merge the contained maps into one map. We do this using the fact that in groovy map+map returns a merged map. Using the groovy spread operator *. we therefore call sum() on each list of maps.

i.e.:

[[name:Coord1, quota:200], [name:Coord1, copiesToDate:60]].sum()

computes into:

[name:Coord1, quota:200, copiesToDate:60]

and:

[[name:Coord2, quota:300], [name:Coord2, copiesToDate:30]].sum()

into:

[name:Coord2, quota:300, copiesToDate:30]
  1. lastly we want to add the balance property to the maps so we iterate through what is now a list of two maps and add balance as a computation of quota - copiesToDate. The each construct returns the list it is working on which is what we assign to x.

Upvotes: 3

tim_yates
tim_yates

Reputation: 171084

Another option for fun :-)

def result = (map1 + map2).groupBy { it.name }
                          .values()
                         *.sum()
                          .collect { it << ['balance': it.quota - it.copiesToDate] }
  • add the lists together
  • group by the name
  • get the grouped values and concatenate them
  • then for each of them, work out the balance

Upvotes: 2

glenn jackman
glenn jackman

Reputation: 246807

Don't call find twice. Use the Map.plus() method to append new entries. Handle missing names from map2.

def map3 = map1.collect {m1 ->
    def m2 = map2.find {it.name == m1.name} ?: [copiesToDate: 0]
    m1 + m2 + [balance: m1.quota - m2.copiesToDate]
}

Upvotes: 2

Related Questions