Groovy - Join filter maps - groovy

I want to check who can come up with the best Groovy-sh way to achieve this -
def m1 = [["id":"1","o":"11"],["id":"1","o":"12"],["id":"2","o":"21"]]
def m2 = [["o":"11","t":"t1"],["o":"11","t":"t2"],["o":"21","t":"t1"]]
I want result
[["id":"1","t":"t1"],["id":"1","t":"t2"],["id":"2","t":"t1"]]
I'm currently iterating the maps and doing this. I'm looking for a solution using Gpath and findAll
Thanks,
Sreehari.

You can transpose both lists and get the entry (id or t) from each list:
def fn = { m1, m2 ->
return [m1,m2]
.transpose()
.collect { [ id: it.first().id, t: it.last().t ] }
}
def m1 = [["id":"1","o":"11"],["id":"1","o":"12"],["id":"2","o":"21"]]
def m2 = [["o":"11","t":"t1"],["o":"11","t":"t2"],["o":"21","t":"t1"]]
assert fn(m1, m2) ==
[["id":"1","t":"t1"],["id":"1","t":"t2"],["id":"2","t":"t1"]]

You can use transpose to zip the maps into pairs, then combine the pairs and filter by map key:
[m1, m2]
.transpose()
.collect { (it[0] + it[1]).subMap(['id', 't']) }
which evaluates to
[[id:1, t:t1], [id:1, t:t2], [id:2, t:t1]]
This works in groovysh using groovy-2.4.4, with either jdk7 or jdk8.

Related

Solving a ranking problem in a purely functional way using groovy

I have solved a problem that ranks fruits by the number of votes. Unfortunately, I want to solve the problem in a purely functional way without mutating the rankPosition variable. Here is my solution:
def fruits=[
[name:'apple', votes:120 , ranking:null ],
[name:'banana', votes:200, ranking: null],
[name:'apricot', votes:66, ranking:null ],
[name:'pear', votes:84, ranking:null],
[name:'kiwi', votes:77, ranking:null],
[name:'plum', votes:66, ranking:null],
[name:'berry', votes:120, ranking:null],
[name:'pineapple', votes:50, ranking:null],
[name:'grapes', votes:200, ranking:null]
]
def rankPosition= 1
def groupedByVotes = fruits.groupBy {it.votes }
println "Ratings $groupedByVotes"
def finalResults=groupedByVotes.sort().each { votes, items ->
items.each { it.ranking = rankPosition }
rankPosition += items.size()
}
println "Final Results are $finalResults"
How can I solve this problem without having to declare a rankingPosition variable external to the closure and mutating its state. Please notes that this solution works but I have since learned that I shouldn't be doing it this way.
I want to be able to fill the rankings with the correct ranking. The inject function does an accumulation but I don't know how to combine it in a way to also set the ranking with the value accumulated in the inject.
I am simply stuck, just don't seem to be able to reason about this one. My attempt below to use inject, simply did not work. Maybe there isn't a way to do this in a purely functional way, better thsn my attempt.
def res= groupedByVotes.collectEntries{votes, list1->
println "list class $list1"
def r= list1.inject(0){acc,l-> acc+l.size()}
list1.each{it.ranking=r}
println "$r"
[(votes): list1]
}
println "$res"
I anyone can then I would appreciate your solution or just assume my attempt is the most realistic way of solving this one.
This is a pure functional solution. It leaves the initial map of maps unchanged and produces a new one:
def results = groupedByVotes.sort().inject(new Tuple(1, [:]), { acc, entry ->
def newRank = acc[0] + entry.value.size()
def newValue = entry.value.collect { [*:it, ranking:acc[0]] }
return new Tuple(newRank, [*:acc[1], (entry.key):newValue] )
})
finalResults = results[1]
I probably did not explain very well what I was trying to achieve. After calculating the rank, I wanted the values inserted for the respective elements in the list. Here is what I came up with:
def sortedFruits= fruits.sort{f1, f2 -> f1.votes <=> f2.votes}
(0..sortedFruits.size()-1)
.each{ i ->
if(i==0){
sortedFruits.get(i).ranking=1
}else if(sortedFruits.get(i-1).votes==sortedFruits.get(i).votes){
sortedFruits.get(i).ranking=i
}else{
sortedFruits.get(i).ranking=i+1
}
if(i<sortedFruits.size()){
def f= sortedFruits.get(i)
println "${f}"
}
}
println "Sorted Fruits are $sortedFruits"
The final result looks like
Sorted Fruits are [[name:lemons, votes:20, ranking:1], [name:guava, votes:20, ranking:1],
[name:pineapple, votes:50, ranking:3], [name:apricot, votes:66, ranking:4], [
name:plum, votes:66, ranking:4] etc.
You can try this:
def results = groupedByVotes.sort()
.inject(new Tuple(1, []), { acc, entry ->
entry.value.each { it.ranking = acc[0] }
return new Tuple(acc[0] + entry.value.size(), acc[1] << entry.value)
})
finalResults = results[1]
In each step of the folding (done by .inject(...)) you have a Tuple containing the next rank and the partial list that has been computed so far. As a final step you extract the result list from the Tuple. But this converts the map to a list.
This solution is even simpler because it is not necessary to make a new collection if you modify the old one in place, and it preserves the map:
def finalResults = groupedByVotes.sort()
finalResults.inject(1, { acc, entry ->
entry.value.each { it.ranking = acc }
return acc + entry.value.size()
})
But both solutions are not really functional. Real functional code treats all values as if they were immutable. See my other answer (coming) for a real functional solution.

Return multiple values from map in Groovy?

Let's say I have a map like this:
def map = [name: 'mrhaki', country: 'The Netherlands', blog: true, languages: ['Groovy', 'Java']]
Now I can return "submap" with only "name" and "blog" like this:
def keys = ['name', 'blog']
map.subMap(keys)
// Will return a map with entries name=mrhaki and blog=true
But is there a way to easily return multiple values instead of a list of entries?
Update:
I'd like to do something like this (which doesn't work):
def values = map.{'name','blog'}
which would yield for example values = ['mrhaki', true] (a list or tuple or some other datastructure).
map.subMap(keys)*.value
The Spread Operator (*.) is used to invoke an action on all items of
an aggregate object. It is equivalent to calling the action on each
item and collecting the result into a list
You can iterate over the submap and collect the values:
def values = map.subMap(keys).collect {it.value}
// Result: [mrhaki, true]
Or, iterate over the list of keys, returning the map value for that key:
def values = keys.collect {map[it]}
I would guess the latter is more efficient, not having to create the submap.
A more long-winded way to iterate over the map
def values = map.inject([]) {values, key, value ->
if (keys.contains(key)) {values << value}
values
}
For completeness I'll add another way of accomplishing this using Map.findResults:
map.findResults { k, v -> k in keys ? v : null }
flexible, but more long-winded than some of the previous answers.

Good Way to Filter Object in a List Which A property Equal B property in Two Object

I have list of Objects(Name A), A have property B and C. I need to find the object in the list which B property equal with another object's C property. For Example:
def objectList = [A1,A2,A3,A4,A5,A6,A7,A8];
if A1.B == A2.C then return A1,A2;
Any good way to do that?
You can use the findAll method for this:
def list = []
def matching = list.findAll { A a ->
a.B == a.C
}
Update
You can get all the pairs of matching objects this way:
def matching = []
list.unique { A a1, A a2 ->
if (a1.B == a2.C || a1.C == a2.B) {
matching << a1 << a2
}
return 1
}
This is kind of a hacky solution since it does not use the unique method as intended.
Not sure whether you want your result flattened or not, anyway here's a solution returning a list of tuples:
def result = list.inject([]) {acc,a1->
list.each {a2->
if (!a1.is(a2) && a1.b == a2.c) {
acc << [a1,a2]
}
}
acc
}

Splitting String with delimiter

I am currently trying to split a string 1128-2 so that I can have two separate values. For example, value1: 1128 and value2: 2, so that I can then use each value separately. I have tried split() but with no success. Is there a specific way Grails handles this, or a better way of doing it?
Try:
def (value1, value2) = '1128-2'.tokenize( '-' )
How are you calling split? It works like this:
def values = '1182-2'.split('-')
assert values[0] == '1182'
assert values[1] == '2'
def (value1, value2) = '1128-2'.split('-') should work.
Can anyone please try this in Groovy Console?
def (v, z) = '1128-2'.split('-')
assert v == '1128'
assert z == '2'
You can also do:
Integer a = '1182-2'.split('-')[0] as Integer
Integer b = '1182-2'.split('-')[1] as Integer
//a=1182 b=2
split doesn't work that way in groovy. you have to use tokenize...
See the docs:
http://groovy-lang.org/gdk.html#split()
dependencies {
compile ('org.springframework.kafka:spring-kafka-test:2.2.7.RELEASE') { dep ->
['org.apache.kafka:kafka_2.11','org.apache.kafka:kafka-clients'].each { i ->
def (g, m) = i.tokenize( ':' )
dep.exclude group: g , module: m
}
}
}

Finding the key in a map, given the value

Hi I have a map like this :
[this:0, is:1, a:2, file:3, anotherkey:4, aa:5]
I wish I could find the key's given the value of a map. For example, if the value 5 is given I need to return aa from the map.
Is that possible?
I don't know if there's a direct method to get a key for a given value, but using Map#find to get a map entry and then get its value should be enough:
def keyForValue(map, value) {
map.find { it.value == value }?.key
}
def map = [a: 1, b: 2, c: 3]
assert keyForValue(map, 2) == 'b'
assert keyForValue(map, 42) == null
In general, maps don't need to have an order relation between their entries, but the default implementation for Groovy's literal maps is LinkedHashMap, which is ordered, so the keyForValue will always yield the first key for a value when using those maps.
There's no specific command for that.
Fortunately, as showed here, you can easily get the key(s) for a specific value in a map:
def myMap = [this:0, is:1, a:2, file:3, fix:4, aa:5]
def myValue = 5
You can do:
def myKey = myMap.find{ it.value == myValue }?.key
// 'aa'
If you want all the keys, do something like this:
def myMap = [this:0, is:1, a:2, file:3, fix:4, aa:5, bb:5]
def myValue = 5
def myKeys = []
myMap.findAll{ it.value == myValue }.each{myKeys << it?.key}
// ['aa', 'bb']
You could invert the map, like this:
Map m = [a: '1', b: '2']
Map mInvert = m.collectEntries { e -> [(e.value): e.key] }
assert mInvert == ['1':'a', '2':'b']
assert mInvert['2'] == 'b'
assert m['b'] == '2'
You'll probably have to iterate over the entry set yourself and try to find the entry with a matching value.
def expect = 5
def m = ['this':0, is:1, a:2, file:3, aa:5]
def r = m.collectMany{ k,v -> (v == expect) ? [k] : []}
// Result: [aa]

Resources