Checking multiple lists for emptiness in Groovy - groovy

Let's say I've got three lists with objects
List<String> list1, list2, list3
What is the best way to do check whether any of them is not empty and do some action?
So far I came up with
if ([list1, list2, list3].any()) {
// do some action
}
But is there a way to omit if block at all?

I don't think there can be anything better than
if (list1 || list2 || list3) {
}
You want some kind of NotEmptyPredicate(l1, l2, l3).ifMatch { println 'hi' }, but it does not exist in standard library. Creating one is not worth.

One objective part of your question is about omitting the if block. This answer pertains to that. I don't recommend this for production code, nor do I claim this is the best way, which is subjective.
Generally, if statements can be "hidden" by using maps. (The context is a new static method on List, via Groovy's meta-programming):
List.metaClass.static.ifNotEmpty = { List<String>... lists ->
def resultMap = [:]
resultMap[true] = { Closure c -> c.call() }
resultMap[false] = { Closure c -> }
return resultMap[lists.any()]
}
Here are example usages... See this Q&A to understand the unusual syntax of ({ })
List<String> list1, list2, list3
list1 = []
list2 = null
list3 = []
list3 << "HELLO"
List.ifNotEmpty(list1, list2, list3) ({ println "test 1" })
list1 = []
list2 = null
list3 = []
List.ifNotEmpty(list1, list2, list3) ({ println "should not see this" })

Related

Appending Elements to a List Creates List of List in Groovy

I am parsing each element of a list one by one.
def List1 = (String[]) Data[2].split(',')
Part of this list gives me a list with elements that contain a delimiter !.
List1 = [TEST1!01.01.01, TEST2!02.02.02]
I tried to iterate each element of this list and obtain a comma separated list.
def List2 = []
List1.each { List2.add(it.split('!'))}
However, the result was a list of list.
[[TEST1, 01.01.01], [TEST2, 02.02.02]]
Instead of [TEST1, 01.01.01, TEST2, 02.02.02].
How do I avoid this and obtain a list as shown above?
How about this?
def list1 = ['TEST1!01.01.01', 'TEST2!02.02.02']
println list1.collect{it.split('!')}.flatten()
When you do List2.add(it.split('!')), you are adding list to List2 instead of single string because .split() creates a list from string.
You should firstly create list by using .split() and than add each member of list to List2.
Here is solution:
def List1 = ["TEST1!01.01.01", "TEST2!02.02.02"]
​def List2 = []
List1.each { List1member ->
def subList = List1member.split('!')
subList.each { subListMember ->
List2.add(subListMember)
}
}
println(List2)​
split() returns a list. That is the reason why I got a list of list. I found that split() can carry process multiple delimiters as well when applied with an operator.
The following returns the desired output.
def List1 = (String[]) Data[2].split(',|!')

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.

Create a list of lists from two maps with values of the common keys

There are two different maps. One is bigger than the other. The keys of small map is always a subset of keys of the bigger map. I want to take the values for keys that are common to both maps and create a list of lists using Groovy features. The objective is to achieve it with as little code as possible with Groovy features.
Map big = ['FirstName':'first_name', 'LastName':'last_name', 'FullName':'full_name']
Map small = ['FirstName':'John', 'FullName':'John Williams']
println Output
[[first_name, John], [full_name, John Williams]]
//works even if the small map is not a subset completely
def result = []
big.keySet().intersect(small.keySet()).each {
result << [big[it], small[it]]
}
assert [['first_name', 'John'], ['full_name', 'John Williams']] == result
EDIT: Added two variations to the solution suggested by #taiyebur
// VARIATION 2
def result2 = big.subMap(small.keySet()).collect([]) {
[it.value, small.get(it.key)]
}
assert [['first_name', 'John'], ['full_name', 'John Williams']] == result2
// VARIATION 3
def result3 = small.collect([]) {
[big[it.key], it.value]
}
assert [['first_name', 'John'], ['full_name', 'John Williams']] == result3

Groovy's "in" operator for lists with String and GString elements

The following piece of Groovy code prints an empty list:
List<String> list = ["test-${1+2}", "test-${2+3}", "test-${3+4}"]
List<String> subl = ["test-1", "test-2", "test-3"]
println subl.findAll { it in list }
Output:
[]
However, this modification leads to the correct output:
List<String> list = ["test-${1+2}" as String, "test-${2+3}" as String, "test-${3+4}" as String]
List<String> subl = ["test-1", "test-2", "test-3"]
println subl.findAll { it in list }
Output:
[test-3]
But this "solution" feels very clunky.
What is the correct Groovy way to achieve this?
You can use the *. spread operator to get Strings easily (see list2 example below). But your check there can be done alot easier with intersect.
List<String> list = ["test-${1+2}", "test-${2+3}", "test-${3+4}"]
List<String> subl = ["test-1", "test-2", "test-3"]
assert subl.findAll{ it in list }==[] // wrong
// use intersect for a shorter version, which uses equals
assert subl.intersect(list)==['test-3']
// or with sets...
assert subl.toSet().intersect(list.toSet())==['test-3'].toSet()
// spread to `toString()` on your search
List<String> list2 = ["test-${1+2}", "test-${2+3}", "test-${3+4}"]*.toString()
assert subl.findAll{ it in list2 }==['test-3']

groovy removeAll closure not removing elements in list

I'm not sure how removeAll in groovy works, but I expected this to return [40289454470ea94601470ea977d00018]
def list = ['40289454470ea94601470ea977b20014', '40289454470ea94601470ea977d00018']
def list2 = ['40289454470ea94601470ea977b20014']
list.removeAll {
list2
}
println list
but instead it returns []
please enlighten :(
removeAll with a Closure removes every element that the closure returns true for
list2 coerces to true under groovy truth as it isn't empty so your code removes everything
Try
list1 -= list2
Instead of using removeAll closure, you should simply use the removeAll method.
list.removeAll {
list2
}
The way with closure to go :
list.removeAll {
list2.contains(it)
}
If list2 contains this element, then it's removed from list

Resources