Create Map with CollectEntries in Groovy - groovy

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.

Related

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.

issue with adding a map to a list using each closure

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]]

Shallow copy all but one entry from groovy map

I need to shallow copy all entries in a Groovy map except for one, for which I already know the key. I prefer immutable and succinct approaches, and the minus() method is a pretty good fit except that providing the key isn't sufficient, and I would have to do something like this:
def map = [a:"aa", b:"bb"]
def knownKey = "a"
def result = map - [(knownKey):map[knownKey]]
assert result == [b:"bb"]
Alternatively I could give up (temporarily) on immutability and call the remove() method with the key as argument.
Is there a groovy'er approach I could take?
You should use findAll as below:
def map = [a:"aa", b:"bb"]
def knownKey = "a"
def result = map.findAll { it.key != knownKey }
assert result == [b:"bb"]

Safe range operator in Groovy?

Is there a safe range operator for Groovy?
For instance if I have,
[1,2,3][0..10]
Groovy will throw a java.lang.IndexOutOfBoundsException:
Is there a index safe way to access this range? Or do I always have to check the collection size prior to running a range?
You can use take(n), which allows you to take up to a specific number of items, without error if there's too few in the collection:
def input = [1,2,3]
def result = input.take(10)
assert result == [1,2,3]
input = [1,2,3,4,5]
result = input.take(4)
assert result == [1,2,3,4]
If you need to start at an offset, you can use drop(n), which does not modify the original collection:
def input = [1,2,3,4,5]
def result = input.drop(2).take(2)
assert result == [3,4]
These are both safe against the size of the collection. If the list is too small in the last example, you may only have one or zero items in the collection.

Is there any way to leverage Groovy's collect method in combination with another iterator function?

For example, the groovy File class has a nice iterator that will filter out just directories and not files:
void eachDir(Closure closure)
When I use eachDir, I have to use the verbose method of creating the collection first and appending to it:
def collection = []
dir1.eachDir { dir ->
collection << dir
}
Any way to get it back to the nice compact collect syntax?
I don't know of any "idiomatic" way of doing this, nice riddle! =D
You can try passing the eachDir, or any similar function, to a function that will collect its iterations:
def collectIterations(fn) {
def col = []
fn {
col << it
}
col
}
And now you can use it as:
def dir = new File('/path/to/some/dir')
def subDirs = collectIterations(dir.&eachDir)
def file = new File('/path/to/some/file')
def lines = collectIterations(file.&eachLine)
(that last example is equivalent to file.readLines())
And only for bonus points, you may define this function as a method in the Closure class:
Closure.metaClass.collectIterations = {->
def col = []
delegate.call {
col << it
}
col
}
def dir = new File('/path/to/some/dir')
def subDirs = dir.&eachDir.collectIterations()
def file = new File('/path/to/some/file')
def lines = file.&eachLine.collectIterations()
Update: On the other hand, you might also do:
def col = []
someDir.eachDir col.&add
Which I think is quite less convoluted, but it's not leveraging the collect method as you requested :)
Not for the specific example that you're talking about. File.eachDir is sort of a weird implementation IMO. It would have been nice if they implemented iterator() on File so that you could use the normal iterator methods on them rather than the custom built ones that just execute a closure.
The easiest way to get a clean one liner that does what you're looking for is to use listFiles instead combined with findAll:
dir1.listFiles().findAll { it.directory }
If you look at the implementation of eachDir, you'll see that it's doing this (and a whole lot more that you don't care about for this instance) under the covers.
For many similar situations, inject is the method that you'd be looking for to have a starting value that you change as you iterate through a collection:
def sum = [1, 2, 3, 4, 5].inject(0) { total, elem -> total + elem }
assert 15 == sum

Resources