I have a map of elements:
elemA1: value
elemB1: value
elemC1: value
...
elemA99: value
elemB99: value
elemC99: value
...
elemA7823: value
elemB7823: value
elemD7823: value
I want to use groupBy to group each set of elements by number.
The number will always be at the end of the key, but my problem is that the number can be any number of characters.
Just have the groupBy closure extract the part of the key you want to group by. Here I'm using the regular expression /\d+$/ to get digits at the end of the key.
def map = [
elemA1: "1",
elemB1: "B1",
elemA99: "A99",
elemB99: "B99"
]
map.groupBy { ( it.key =~ /\d+$/ )[0] } // [1:[elemA1:1, elemB1:B1], 99:[elemA99:A99, elemB99:B99]]
Related
I have a list in terraform that looks something like:
array = ["a","b","c"]
Within this terraform file there are two variables called age and gender, and I want to make it so that the list called array has an extra element called "d" if age is equal to 12 and gender is equal to male (i.e. if var.age == 12 && var.gender == 'male' then array should be ["a","b","c","d"], else array should be ["a","b","c"]). Would the following going along the right path, or would I need to use another method?
array = ["a","b","c", var.age == 12 && var.gender == 'male' ? "d" : null]
There is another way to do that using flatten:
variable = flatten(["a", "b", "c", var.age == 12 ? ["d"] : []])
There are few ways you could do it. One way would be (example):
variable "array" {
default = ["a","b","c"]
}
variable "age" {
default = 12
}
variable "gender" {
default = "male"
}
locals {
array = var.age == 12 && var.gender == "male" ? concat(var.array, ["d"]) : var.array
}
output "test" {
value = local.array
}
Another approach to the problem in your particular example is removing empty elements at the end of the array.
compact is perhaps the most straightforward, but requires you to rely on using "" as a sentinel value for Emptiness.
From the documentation:
compact takes a list of strings and returns a new list with any empty string elements removed.
> compact(["a", "", "b", "c"])
[
"a",
"b",
"c",
]
I would prefer this over the other answers because it's more idiomatic. I suppose it only works for strings though.
array = compact(["a","b","c", var.age == 12 && var.gender == 'male' ? "d" : ""])`
["a","b","c"] if age != 12 and gender != male
["a","b","c", "d"] if age == 12 and gender == male
Of course the "" element could be anywhere in your list and compact would handle this optionality issue.
I would also be interested in which has the best O() performance. I don't know how compact is implemented underneath the hood, but in general you would either copy elements into a new array or you would be shifting elements into gaps left by removed elements.
I don't expect any other solution to be much better than this. Perhaps concat.
concat function pulled into terraform source code
concat source code. Just appends elements into a new slice.
Given that, it probably requires O(n) comparisons + appends, and then takes O(n) space because a new list is created.
How can I compare the dictionary values with float i.e if I had the following dictionary:
{
'F2': 0.5896643972009248,
'F3': 0.5742879655443124,
'F1': 0.5899210024965614,
'F11': 0.6086413936684749,
'F4': 0.5924462845088885,
'F6': 0.5659846155839213,
'F10': 0.6339183933852852,
'F9': 0.5597757560369959,
'F5': 0.5633086160491567,
'F7': 0.556301751221009,
'F12': 0.8346634117283984,
'F8': 0.5163509611989721
}
and I want to compare each value in this dictionary with float and if the dictionary values are greater than the given float number the output will be each key corresponding value if not block (didn't output) the key and value.
I don't what did you mean with "block the value and key", so I'm just returning the dictionary with values greater than a given float.
You can try this solution with dict comprehension:
def compare_dict_values(dictionary, given_float):
return {i: dictionary[i] for i in dictionary if dictionary[i] > given_float}
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]
Groovy split seems to be ignoring empty fields.
Here is the code:
line = abc,abc,,,
line.split(/,/)
println
prints only..
abc abc
It seems to ignore empty fields. How do I retrieve empty fields using split?
First of all, method split(regex) is not provided by Groovy, it is provided by Java.
Second, you can achieve what you need by using the generic split(regex, int limit) as below:
def line = "abc,abc,,,"
println line.split(/,/, -1) //prints [abc, abc, , , ]
println line.split(/,/, -1).size() //prints 5
Note:-
The string array you would end up in the print would throw a compilation error when asserted. But you can use the result as a normal list.
line.split(/,/, -1).each{println "Hello $it"}
I would rather use limit 0 or the overloaded split to discard unwanted empty strings.
Explanation on using -1 as limit:
Stress on the below statements from the javadoc.
The limit parameter controls the number of times the pattern is
applied and therefore affects the length of the resulting array. If
the limit n is greater than zero then the pattern will be applied at
most n - 1 times, the array's length will be no greater than n, and
the array's last entry will contain all input beyond the last matched
delimiter. If n is non-positive then the pattern will be applied as
many times as possible and the array can have any length. If n is zero
then the pattern will be applied as many times as possible, the array
can have any length, and trailing empty strings will be discarded.
Interesting. The split method works as expected provided there's a non-empty element at the end.
def list = 'abc,abc,,,abc'.split(/,/)
println list // prints [abc, abc, , ]
assert list.size() == 5
assert list[0] == 'abc'
assert list[1] == 'abc'
assert list[2] == ''
assert list[3] == ''
assert list[4] == 'abc'
Maybe you could just append a bogus character to the end of the string and sublist the result:
def list = 'abc,abc,,,X'.split(/,/) - 'X'
println list // prints [abc, abc, , ]
I have a list of strings (from documents in CouchDB).
I want to find the minimum prefix length so that all shortened strings (taking the first LEN characters) are unique.
For example:
aabb
aabc
abcd
should give: LEN is three.
Is it possible to write this as a map/reduce function ?
Doing it the brute force way:
MAP: Create for each input record "ABCDE" records with the keys
- "A"
- "AB"
- "ABC"
- "ABCD"
- "ABCDE"
REDUCE:
IF you have 1 value in the iterator output: "length(key)" "true"
If you have more than one 1 value in the iterator output: "length(key)" "false"
MAP: Identity mapper
REDUCE: Output "true" if all input values are true. Else output false (or nothing);
That should result in a true for all lengths that are "unique"