Groovy convert a flat list of lists to a hierarchy - groovy

Assuming we have a flat list of lists in groovy, like the following:
[[id: 1,title: A],[id: 2, title: B],[id: 3, title: C]]
What is the fastest way to transform it in a hierarchy where B is children to A and C is Children to B?
I can do this with iterations, but since groovy is so creative, I am wondering if there is a smarter way.

You can do this sort of thing if you don't mind mutating your original list and its elements:
def list = [[id: 1,title: 'A'],[id: 2, title: 'B'],[id: 3, title: 'C']]
list.inject( [:] ) { prev, next ->
if( prev ) {
prev.child = next
}
next
}
assert list.head() == [id:1, title:'A', child:[id:2, title:'B', child:[id:3, title:'C']]]

Related

Pick elements on (un)even index from an array in Groovy

I have a Groovy array containing digits of a number. I need to create two new arrays containing only the digits at even resp. uneven positions from that array.
The best way that I could find is this, but I feel there's quite a lot of room for improvement here:
def evenDigits = digits
.indexed(1)
.findAll { i, v -> i % 2 == 0 }
.collect { it.value }
Obviously the unevenDigits variant would be to simply check the modulus in the findAll closure against 1 instead of 0.
Does anyone know if this code can be improved or compacted?
A "less smarter" (and definitely more performant) solution:
def evens = [], odds = []
digits.eachWithIndex{ v, ix -> ( ix & 1 ? odds : evens ) << v }
You can use groupBy to separate the results to odd/even items. E.g.
groovy:000> ["a","b","c"].indexed(1).groupBy{ i, _ -> i & 1 }.collectEntries{ k, v -> [k as Boolean, v.values()] }
===> [true:[a, c], false:[b]]
One more "Groovy" solution that uses withIndex() and findResults() combination.
withIndex() transforms a List<T> to List<Tuple2<T,Integer>> - a list of value-index tuples.
findResults(closure) runs filtering transformation - the closure it receives is a transforming predicate. In our case, it checks if the index value is odd or even and extracts the value from tuple if the predicate matches. (All null values are filtered out.)
Short and concise. Requires a minimal number of transformations: List<T> to List<Tuple2<T,Integer>> and then a single iteration to produce the final result.
def numbers = [1,2,3,4,5,6,2,3,1] // Some test data
def even = { t -> t.second % 2 == 0 ? t.first : null } // "Even" transforming predicate
def odd = { t -> t.second % 2 == 1 ? t.first : null } // "Odd" transforming predicate
def evens = numbers.withIndex(1).findResults even
def odds = numbers.withIndex(1).findResults odd
// And some assertions to test the implementation
assert evens == [2,4,6,3]
assert odds == [1,3,5,2,1]
Another option, for a single pass (but still with the intermediate collection due to indexed), would be a reduce:
def (odd,even) = digits.indexed().inject([[],[]]){ acc, it -> acc[it.key&1] << it.value; acc }
I came up with this, but it's probably not the cleverest way.
def isEven = { int x -> x % 2 == 0 ? x : null}
def (digits, evens, odds) = [[1, 2, 3, 4, 5, 6, 7, 8, 9], [], []]
digits.each {
if (isEven(it))
evens.add(isEven(it))
}
odds = digits - evens
assert evens == [2, 4, 6, 8]
assert odds == [1, 3, 5, 7, 9]

Print Data in Dictionary by using groovy

I have dictionary
Output = [a:2, c:13, b:5, z:'Test']
I use for loop as follows
for(i in Output){
println(Output[i.key]);
}
it is result.
2
13
5
Test
but I expect result (reverse).
Test
5
13
2
Could you please help me?
What you have is not a "dictionary" (this is not Python), but a Map.
The order of its entries is not defined it is just a mapping key to value without any ordering. The order you get is (pseudo-)random and can not be depended upon.
Btw. the more Groovy way for producing the output would be to not use a for-loop, but e. g.
[a:2, c:13, b:5, z:'Test'].values().each { println it }
If you want to have an order, you need a list instead of a map, e. g. like a list of maps as in
[[a:2], [c:13], [b:5], [z:'Test']].reverse().collectMany { it.values() }.each { println it }
or a list of lists as in
[['a', 2], ['c', 13], ['b', 5], ['z', 'Test']].reverse().collect { it[1] }.each { println it }
or maybe this list of maps which probably would be the cleanest of these solutions
[[key:'a', value:2], [key:'c', value:13], [key:'b', value:5], [key:'z', value:'Test']].reverse().collect { it.value }.each { println it }

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
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:
[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.
.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.
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]
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.
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]
}
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

Collect only if result is not null

I have a collection and I'm wanting to find certain elements and transform them. I can do this in two closures but I was wondering if it is possible with only one?
def c = [1, 2, 3, 4]
def result = c.findAll {
it % 2 == 0
}
result = result.collect {
it /= 2
}
My true use case is with Gradle, I want to find a specific bunch of files and transform them to their fully-qualified package name.
You can use findResults:
def c = [1, 2, 3, 4]
c.findResults { i ->
i % 2 == 0 ? // if this is true
i / 2 : // return this
null // otherwise skip this one
}
Also, you will get [] in case none of the elements satisfies the criteria (closure)

How to fold into a multi level map using gpars and map reduce

I have a collection of maps that looks something like this:
def list = [
[key1: 'ABC', key2: 3, value: 1.01],
[key1: 'ABC', key2: 4, value: 1.02],
[key1: 'ABC', key2: 4, value: 1.03],
[key1: 'DEF', key2: 3, value: 1.04]]
I'm trying to get a result that looks like this that groups and sums up the values for the unique key1 and key2 values and results in a hierarchy.
['ABC':[[key2: 2, value: 1.01]
[key2: 4, value: 2.05]], //note values are added
'DEF':[[key2: 3, value: 1.04]]
]
There are many examples of mapping routines that have one key, but what is the best way to fold these when using more than one key?
One solution I thought of was to use groupby to get the list grouped by the first key. The problem there is the combine or reduce must then be run on the sub list of each element:
list.parallel
.map{it}
.groupBy{it.key1}
at this point I want to reduce on the .value() of the grouped maps which I can't really do within the chain
I also tried to use combine, which works a bit like the examples here. However it looks like if combine gets a map back, it wants to combine it further.
def result = list.parallel
.map{[it.key1, it]}
.combine({-> [:]}) { map, v -> println "$map - $v = ${v.getClass()}"
map[v.key2] = map[v.key2]?:0 + v.value
map
}
Then there is the option to just reduce on the maps, but the reduce routine then becomes a pretty complicated beast of combining nested maps. So I'm wondering if there is something simpler, or should I just run a reduce routine to combine the complex maps.
list.parallel
.map{[(it.key1):it]}
.reduce([:]) { a, b ->
complexMapCombiner(a, b)
}
So here's a solution that works, but is less elegant than I'd like. If anyone has something better please post an answer.
#Grab(group='org.codehaus.gpars', module='gpars', version='1.0.0')
import static groovyx.gpars.GParsPool.*
def list = [
[key1: 'ABC', key2: 3, value: 1.01],
[key1: 'ABC', key2: 4, value: 1.02],
[key1: 'ABC', key2: 4, value: 1.03],
[key1: 'DEF', key2: 3, value: 1.04]]
withPool {
def mapInner = { entrylist ->
withPool{
entrylist.getParallel()
.map{[it.key2, it.value]}
.combine(0) {acc, v -> acc + v}.getParallel()
.map{[key2: it.key, value: it.value]}.collection
}
}
//for dealing with bug when only 1 list item
def collectSingle = { entrylist ->
def first = entrylist[0]
return [[key2:(first.key2), value:first.value]]
}
def result = list.parallel
.groupBy{it.key1}.getParallel()
.map{ [(it.key) : (it.value?.size())>1?mapInner.call(it.value):collectSingle.call(it.value) ] }
.reduce([:]) {a, b -> a + b}
println "result = $result"
}

Resources