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.
Related
I have three arrays as below and need to find the maching/duplicate in all of these
`def Ids_AS =[04-04350, 21-005676, REGU-132644681]
def Ids_AO= [ 04-04350, 04-04356, REGU-132644681]
def Ids_AV= [ 04-04350, AB-132644681, REGU-132644681]`
println(IdsResultMissingOnSolrOutPut_AS.intersect(IdsResultMissingOnSolrOutPut_AV))
I used intersect but it is getting applies on 2 arrays/list only
Another Case: Need to handle empty array like below and it should match the rest of remaining instead of returning null or error
`def Ids_AS =[04-04350, 21-005676, REGU-132644681]
def Ids_AO= [ 04-04350, 04-04356, REGU-132644681]
def Ids_AV= []`
is there way to find duplicates on multiple arrays? Please help
Just do the intersection for the third array
def duplicates = Ids_AS.intersect(Ids_AO).intersect(Ids_AV)
If you want to get clever, and you have many, you can make a list of your lists, and then use inject (fold) to intersect them all against each other
def all = [Ids_AS, Ids_AO, Ids_AV]
def duplicates = all.inject { a, b -> a.intersect(b) }
Both methods will result in
['04-04350', 'REGU-132644681']
For the second question, sort the list of lists so the longest one is first, and then ignore empty lists
def duplicates = all.sort { -it.size() }.inject { a, b -> b.empty ? a : a.intersect(b) }
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()
I am trying to add a map to a list using the each closure. The sample is like this
def list = [1, 2, 3, 4]
def titles = []
def map = [:]
list.each{
map.put('index', xxxx)
map.put('title', xxxxx)
titles.add(map)
}
If I print the value of the final tiles list I notice that the map items repeat only the values from the first item in list. It looks like the map is getting overwritten with only the value of the first item in the original list.
Why does this happen and what is the way to get the correct value for the map
You're adding the same map instance every time then changing all those instances each loop
Move def map = [:] inside the each closure
As the other poster commented, you need to instantiate a new map inside the each closure. But, you can do this much more 'Groovier' like this:
def list = [1, 2, 3, 4]
def titles = []
list.each{ value -> titles << ['index':'xxx', 'title':'xxxx'] }
In your example you are not even using the value from the list as you iterate. If you are just trying to force it to process a specific number of iterations, you could change it to:
def titles = []
list.each{ value -> titles << ['index':'xxx', 'title':'xxxx'] }
Or more simply, use a range:
(1..4)*.collect{ idx -> ['index':idx, 'title':"$idx:xxxx"] }.flatten()
Which produces a List of Map:
[[index:1, title:1:xxxx], [index:2, title:2:xxxx], [index:3, title:3:xxxx], [index:4, title:4:xxxx]]