In the below code, x.test() returns [1,2].
So y = [1,2].
Yet f([1,2]) prints 1, but f(y) prints 2.
How do I write f(y) so it prints 1?
Perversely, f(z) prints 1, even though z = y.
def f = { Object... args -> println args.size(); };
class Test { Object[] test() { return [1,2]; } }
def x = new Test();
def y = x.test();
def z = [1,2];
f([1,2]); // 1
f(y); // 2
f(z); // 1
The problem is that y and z, while they appear to be the same, are actually of different types. y is an Object[] while z is an ArrayList<Integer>. Groovy handles arrays and lists differently, automatically coercing the former into a varargs parameter list, but not the latter.
println y.getClass(); // class [Ljava.lang.Object
println z.getClass(); // class java.util.ArrayList
As for a solution to your problem, either change your test() to return a List instead of an array:
class Test { List test() { return [1,2]; } }
or manually coerce the array into a list when you pass it to f:
f(y as List); // 1
The expression [1,2] in Groovy denotes an ArrayList with two members, Integer.valueOf(1) and Integer.valueOf(2). Thus when you call f([1,2]) Groovy creates a single-element array containing this ArrayList as its only item, and passes that array as the closure argument.
But x.test() is declared to return Object[] so the [1,2] ArrayList will be converted to a two element Object[] by the return. Thus y is already an Object[] and does not need to be boxed up in a varargs array to be passed to f.
You need to turn y back into a list, either by changing the return type of test() or by saying
f(y as List)
Conversely, you can use the spread operator
f(*z) // 2
which will extract the elements of the ArrayList and pass them as individual arguments to the call (which will then be packaged up into a varargs array as usual).
y is an instance of [Ljava.lang.Object whereas [1,2] and z are of instance of ArrayList
The Array has the size() == 2 and the ArrayLists count as one argument, but they contain two elements
Groovy does some type converting for you ;)
Related
I have the following list:
appList = [DevOpsApplication, 01.01.01]
I would like to create a map using collectEntries. I know that it refers to the current element of an iteration (shortcut for { it -> it }). Therefore, I tried to use the index:
def appMap = appList.collectEntries { [(it[0]):it[1]] }
However, this gives me:
[D:e, 0:1]
But I want [DevOpsApplication: 01.01.01]. Is there a way to do this?
Additionally, In future I would like this to expand to more than 2 elements (e.g. [DevOpsApplication, 01.01.01, AnotherDevOpsApplication, 02.02.02]) with the desired output of [DevOpsApplication: 01.01.01, AnotherDevOpsApplication: 02.02.02].
How will this be possible?
A very short version to do this would be:
def appList = ["DevOpsApplication", "01.01.01"]
def appMap = [appList].collectEntries() // XXX
assert appMap == [DevOpsApplication: "01.01.01"]
How does it work: the function collectEntries takes, is expected to return a map or a two element list. Your appList is already that. So put that in another list, call collectEntries on it. When no function is given to collectEntries it uses the identity function.
Bonus: what if appList has much more elements? You can use collate to build the tuples.
def appList = ["DevOpsApplication", "01.01.01", "Some", "More"]
def appMap = appList.collate(2).collectEntries() // XXX
assert appMap == [DevOpsApplication: "01.01.01", Some: "More"]
I also found another method. Groovy can convert the values of an Object array and convert them into a map with the toSpreadMap(). However, the array must have an even number of elements.
def appList = ['DevOpsApplication', '01.01.01']
def appMap = appList.toSpreadMap()
You're iterating element-by-element and (because your elements are String-typed) mapping 0 substrings to 1 substrings.
You can use this to skip one element in each iteration and map each element at even indices to the one after it:
def appList = ['DevOpsApplication', '01.01.01']
def appMap = (0..(appList.size()-1)).findAll{0 == it%2}
.collectEntries{[(appList[it]): appList[it+1]]}
That returns [DevOpsApplication:01.01.01] as expected.
I Have two maps:
def map = ['a': 3, 'b': 4, 'c':5]
def map2= ['a': 3, 'b': 4, 'c':4]
I want to take the maximum value of the map like this:
def newMap = map.max {it.value}
and my output is correct ('c':5), my problem is with the second map because there is more than one max value. On this case I want to change the key so I can know that there was more than one max value. I want my output to be:
def newMap2 = map2.max {it.value}
assert newMap2 == ['+than1': 4]
Can I alter the key of the map in this specific case using groovy functions?
Can I achieve this inside the max closure?
I do not want to alter the key if there isn't more than one max value.
Keep in mind that map.max(Closure cl) returns Map.Entry<String, Integer> and not a map. So if you expect a map with a single key, you will have to create one from the result you get.
Map.max(Closure cl) searches for the maximum value and returns it. There is no variant that allows you to modify function behavior in case of two entries holding maximum value. According to docs:
Selects an entry in the map having the maximum
calculated value as determined by the supplied closure.
If more than one entry has the maximum value,
an arbitrary choice is made between the entries having the maximum value.
In practice: the first entry found with maximum value is returned.
Groovy however offers a different collection function that can be used to achieve what you expect - Collection.inject(initialValue, closure) function. This is an equivalent of popular fold function known very well in functional programming paradigm. It starts with some initial value and it iterates a collection and applies a function to every element (this function returns a new value that replaces value passed as initial value) to reduce a list of elements to a single element in a single iteration. Collection.inject() javadoc gives a very descriptive example of summing all numbers from a list:
assert 0+1+2+3+4 == [1,2,3,4].inject(0) { acc, val -> acc + val }
Now let's take a look how we can use this function to achieve expected result. Consider following example:
def map2= ['a': 3, 'b': 4, 'c':4]
Tuple2<String, Integer> result = map2.inject(new Tuple2<String, Integer>(null, null)) { Tuple2<String, Integer> tuple, entry ->
entry.value >= tuple.second ?
new Tuple2<>(entry.value == tuple.second ? '+1than1' : entry.key, entry.value) :
tuple
}
assert result.first == '+1than1'
assert result.second == 4
Here we start with new Tuple2<String, Integer>(null, null) as an initial value - a Tuple2 represents a pair of two values. In our case it represents a key and the maximum value. The closure checks if current entry value is higher or equal the one we store in the tuple and if this is true it checks if the value is the same as the one we have already found - if this is true it uses +than1 as a key instead of a key taken from map. Otherwise it simply uses entry key without any modification. When the value is lower then the one we currently store in the tuple, existing tuple gets returned. And finally, we get a tuple where tuple.first holds a key and tuple.second holds a maximum value.
Of course to make the inject part more readable it is worth extracting a data class that represents your expected result and behavior. You can implement a function like compareAndReplace there to define specific behavior when another maximum value is found. Something like this:
import groovy.transform.Immutable
#Immutable
class Result {
static Result EMPTY = new Result(null, null)
String key
Integer value
Result compareAndReplace(Map.Entry<String, Integer> entry, String key = '+1than1') {
entry.value >= value ?
new Result(entry.value == value ? key : entry.key, entry.value) :
this
}
}
def map2 = ['a': 3, 'b': 4, 'c': 4]
Result result = map2.inject(Result.EMPTY) { Result result, entry -> result.compareAndReplace(entry) }
assert result.key == '+1than1'
assert result.value == 4
You can do it in a one-liner but it's still a little obscure:
println map2.groupBy{ it.value }.max{ it.key }.value.with{ it.size() > 1 ? ['+than1': it.values()[0]] : it }
I'd probably at least extract out the last Closure:
def mergedKey = { it.size() > 1 ? ['+than1': it.values()[0]] : it }
def newMap2 = map2.groupBy{ it.value }.max{ it.key }.value.with(mergedKey)
assert newMap2 == ['+than1': 4]
I have a JSON array (list of maps) similar to:
def listOfMap = [[TESTCASE:1, METHOD:'CLICK', RESULT:'PASS'],
[TESTCASE:2, METHOD:'CLICK', RESULT:'FAIL'],
[TESTCASE:3, METHOD:'CLICK', RESULT:'FAIL'],
[TESTCASE:4, METHOD:'TYPETEXT', RESULT:'FAIL']]
1) I want to get/filter/return all the lists that contain the key-value pairs "METHOD:CLICK" and "RESULT:FAIL"
My output should return 2 lists out of 4: [TESTCASE:2, METHOD:CLICK, RESULT:FAIL], [TESTCASE:3, METHOD:CLICK, RESULT:FAIL]
2) I want to get the count of lists that contain the key-value pairs "METHOD:CLICK" and "RESULT:FAIL"
My output should be : 2
3) From the above list of maps, i want to get all the unique/distinct values for the key "METHOD"
My output should return unique values of the key method : CLICK, TYPETEXT
Filtering
Groovy has a method called Collection.findAll(Closure closure) that filters out all values that don't satisfy a predicate (expressed as a closure):
println listOfMap.findAll { map -> map.METHOD == 'CLICK' && map.RESULT == 'FAIL' }
// Output: [[TESTCASE:2, METHOD:CLICK, RESULT:FAIL], [TESTCASE:3, METHOD:CLICK, RESULT:FAIL]]
Counting
There is also a method DefaultGroovyMethods.count(Iterable<T> self, Closure closure) that expects a predicate and count how many elements satisfy it:
println listOfMap.count { map -> map.METHOD == 'CLICK' && map.RESULT == 'FAIL' }
// Output: 2
List of unique values from list of maps
For selecting a value for given key from a list of maps you can use Groovy's spread operator:
println listOfMap*.METHOD // btw, listOfMap.METHOD will do the same
This code translates to "for each element inside listOfMap get me a value for key METHOD. In the next step you can use Collection.unique() method to remove all duplicates:
println listOfMap*.METHOD.unique()
// Output: [CLICK, TYPETEXT]
WARNING: following method will not work in Jenkins pipeline Groovy script. In this case you will have to use Collection.collect(Closure closure) explicitly:
println listOfMap.collect { map -> map.METHOD }.unique()
Here is my code:
def myclosey = {items ->
items + 1}
myclosey(1..3);
I expect 1 to be added to every element in the intrange. Instead, 1 is just added to the end of intrange,
so output is
[1,2,3,1]
Why?
IntRange is a list. So the plus operator appends an element to the list. The closure is being called on the range itself, not each element of the range.
If you wanted to add one to all of the elements, you could do (1..3).collect { it + 1 }, or use the syntax that #dmahpatro suggested.
Because you are passing in a range to the closure so you have to do:
def myclosey = {items ->
items*.plus(1) //Spread on the range
}
assert myclosey(1..3) == [2, 3, 4]
Is there a better way to do this? Note: part1, part2 and part3 are string variables defined elsewhere (they can be null).
def list = [part1, part2, part3]
list.removeAll([null])
def ans = list.join()
The desired result is a concatenated string with null values left out.
You can do this:
def ans = [part1, part2, part3].findAll({it != null}).join()
You might be able to shrink the closure down to just {it} depending on how your list items will evaluate according to Groovy Truth, but this should make it a bit tighter.
Note: The GDK javadocs are a great resource.
If you use findAll with no parameters. It will return every "truthful" value, so this should work:
def ans = [part1, part2, part3].findAll().join()
Notice that findAll will filter out empty strings (because they are evaluated as false in a boolean context), but that doesn't matter in this case, as the empty strings don't add anything to join() :)
If this is a simplified question and you want to keep empty string values, you can use findResults{ it }.
Alternatively, you can do this as a fold operation with inject:
def ans = [part1, part2, part3].inject('') { result, element ->
result + (element ?: '')
}
This iterates the whole list and concatenates each successive element to a result, with logic to use the empty string for null elements.
You could use grep:
groovy:000> list = ['a', 'b', null, 'c']
===> [a, b, null, c]
groovy:000> list.grep {it != null}.join()
===> abc