I've the following collection
def a = [
b:[
[
c: "x",
d: ["y","z"]
]
],
b1:[
[
c: "x1",
d: ["y1","z1"]
]
]
]
I want to add a new element "w" to d:["y", "z"] to have this d:["y", "z", "w"]
I've tried a.put(d:"w"), a.add(d:"w") but getting exception
groovy.lang.MissingMethodException: No signature of method: java.util.LinkedHashMap.put() is applicable for argument types: (String) values: [w]
You have take into account all your nesting here. You have a map to
list to map. The main problem is the list now, since your example makes
it not clear, how many list items b could hold. So the solution for
your exact example is:
a.b[0].d << "w"
Related
I have the following list of dictionaries, with sub dictionaries data:
data2 = [
{"dep": None},
{"dep": {
"eid": "b3ca7ddc-0d0b-4932-816b-e74040a770ec",
"nid": "fae15b05-e869-4403-ae80-6e8892a9dbde",
}
},
{"dep": None},
{"dep": {
"eid": "c3bcaef7-e3b0-40b6-8ad6-cbdb35cd18ed",
"nid": "6a79c93f-286c-4133-b620-66d35389480f",
}
},
]
And I have a match key:
match_key = "b3ca7ddc-0d0b-4932-816b-e74040a770ec"
And I want to see if any sub dictionaries of each "dep" key in data2 have an eid that matches my match_key. I'm trying the following, but I get a TypeError: string indices must be integers - where am I going wrong?
My Code
matches = [
d["eid"]
for item in data2
if item["dep"]
for d in item["dep"]
if d["eid"] == match_key
]
So matches should return:
["b3ca7ddc-0d0b-4932-816b-e74040a770ec"]
Meaning it found this id in data2.
When you iterate over a dictionary, each iteration gives you a key from the dictionary.
So d["eid"] is actually "eid"["eid"], which is an invalid expression. That's why Python raises the following exception:
TypeError: string indices must be integers
Also, the expression d["eid"] assumes that every d contains the eid key. If it doesn't, Python will raise a KeyError.
If you don't know for sure that "eid" is a valid key in the dictionary, prefer using the .get method instead.
matches = [
v
for item in data2
if item.get("dep") # Is there a key called dep, and it has a non-falsy value in it
for k, v in item["dep"].items() # Iterate over the dictionary items
if k == "eid" and v == match_key
]
You can do even better by directly accessing the value of eid key:
matches = [
d["dep"]["eid"]
for d in data2
if d.get("dep") and d["dep"].get("eid") == match_key
]
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.
This is simple in other language by using for or do while statement. Being a novice, I still can't figure out to do it in Terraform.
The real case that I need to do is to build a connection string to mongoDB replica-set service. The replication_factor is 3, 5 or 7 which means I need to create a list of 2, 4, or 6 hostnames/addresses.
I come up with the following code sofar:
locals {
uri_list = [
"#${alicloud_mongodb_instance.tyk_mongodb-test.id}1.mongodb.${var.region}.rds.aliyuncs.com:${var.db_port}",
"#${alicloud_mongodb_instance.tyk_mongodb-test.id}2.mongodb.${var.region}.rds.aliyuncs.com:${var.db_port}",
"#${alicloud_mongodb_instance.tyk_mongodb-test.id}3.mongodb.${var.region}.rds.aliyuncs.com:${var.db_port}",
"#${alicloud_mongodb_instance.tyk_mongodb-test.id}4.mongodb.${var.region}.rds.aliyuncs.com:${var.db_port}",
"#${alicloud_mongodb_instance.tyk_mongodb-test.id}5.mongodb.${var.region}.rds.aliyuncs.com:${var.db_port}",
"#${alicloud_mongodb_instance.tyk_mongodb-test.id}6.mongodb.${var.region}.rds.aliyuncs.com:${var.db_port}"
]
uri_list_out = [
for uriname in local.uri_list :
lower(uriname)
if substr(uriname,length("${alicloud_mongodb_instance.tyk_mongodb-test.id}") + 1, 1) < var.mongos_config["${var.environment}"]["replication_factor"]
]
}
What I expect from
output "uri_list_out" {
value = local.uri_list_out
}
is the first two elements of uri_list but instead I got only [1,2] for replication_factor = 3. Seems like if instruction in for also modify the output ???
I appreciate any hints to solve this problem.
Hendro
I believe what you really need is the slice(list, startindex, endindex) function:
uri_list_out = [
for uri in slice(local.uri_list, 0, var.mongos_config[var.environment]["replication_factor"] - 1) :
replace(uri, "/^#/", "") # Remove the leading '#'
]
The docs for the slice function
> slice(["a", "b", "c", "d"], 1, 3)
[
"b",
"c",
]
I want to fetch common elements from multiple arrays. The no. of arrays resulted would keep changing depending upon the no. of tags in array a[].
As a first step, my query and result I get is as shown below:
let a=["Men","Women","Accessories"]
let c=(for i in a
Let d=Concat("Tags/",i)
return d)
for i in c
let m=(for y in outbound i TC
return y._key)
return m
and result I get is:
[
[
"C1",
"C5",
"C7",
"C3"
],
[
"C2",
"C5",
"C6",
"C4"
],
[
"C7",
"C5",
"C6"
]
]
From this result, I want only common element as a result i.e "C5" (here).
How can I get that?
This question has also been asked and answered on github.
The function INTERSECTION() returns the intersection of all specified arrays and APPLY() is used to pass a dynamic amount of nested arrays.
The query
let D = [["C1","C5","C7","C3"],["C2","C5","C6","C4"],["C7","C5","C6"]]
RETURN APPLY("INTERSECTION", D)
results in:
[
[
"C5"
]
]
I need to merge to maps while perform some calculation for example having the following maps that always will be the same size
def map1 = [
[name: 'Coord1', quota: 200],
[name: 'Coord2', quota: 300]
]
def map2 = [
[name: 'Coord1', copiesToDate: 270],
[name: 'Coord2', copiesToDate: 30]
]
I want to get this map
def map3 = [
[name: 'Coord1', quota: 200, copiesToDate: 60, balance: 140],
[name: 'Coord2', quota: 300, copiesToDate: 30, balance: 270]
]
Right now i am trying with this solution and its working
def map4 = map1.collect { m1 ->
[
name: m1.name,
quota: m1.quota,
copiesToDate: map2.find { m2 ->
m1.name == m2.name
}.copiesToDate,
balanceToDate: m1.quota - map2.find { m2 ->
m1.name == m2.name
}.copiesToDate
]}
Could you please share a groovy way to do this task. Thanks
Grooviest code I could come up with:
def map3 = [map1, map2].transpose()*.sum().each { m ->
m.balance = m.quota - m.copiesToDate
}
edit: as noted by Tim, this code works as long as the two input lists (map1 and map2) are of the same size and have the maps in order. If this is not the case I would recommend Tim's answer which handles those cases.
The above returns the map as defined in your question. The following code:
def list1 = [
[name: 'Coord1', quota: 200],
[name: 'Coord2', quota: 300]
]
def list2 = [
[name: 'Coord1', copiesToDate: 60],
[name: 'Coord2', copiesToDate: 30]
]
def x = [list1, list2].transpose()*.sum().each { m ->
m.balance = m.quota - m.copiesToDate
}
x.each {
println it
}
demonstrates the idea and prints:
[name:Coord1, quota:200, copiesToDate:60, balance:140]
[name:Coord2, quota:300, copiesToDate:30, balance:270]
I have renamed map1 and map2 into list1 and list2 since they are in fact two lists containing inner maps.
The code is somewhat concise and might need a bit of explanation if you're not used to transpose and the groovy spread and map operations.
Explanation:
[list1, list2] - first we create a new list where the two existing lists are elements. So we now have a list of lists where the elements in the inner lists are maps.
.transpose() - we then call transpose which might need a bit of effort to grasp when you see it for the first time. If you have a list of lists, you can see transpose as flipping the lists "into the other direction".
In our case the two lists:
[[name:Coord1, quota:200], [name:Coord2, quota:300]]
[[name:Coord1, copiesToDate:60], [name:Coord2, copiesToDate:30]]
become:
[[name:Coord1, quota:200], [name:Coord1, copiesToDate:60]]
[[name:Coord2, quota:300], [name:Coord2, copiesToDate:30]]
i.e. after transpose, everything relating to Coord1 is in the first list and everything relating to Coord2 is in the second.
Each of the lists we have now is a list of Maps. But what we want is just one map for Coord1 and one map for Coord2. So for each of the above lists, we now need to coalesce or merge the contained maps into one map. We do this using the fact that in groovy map+map returns a merged map. Using the groovy spread operator *. we therefore call sum() on each list of maps.
i.e.:
[[name:Coord1, quota:200], [name:Coord1, copiesToDate:60]].sum()
computes into:
[name:Coord1, quota:200, copiesToDate:60]
and:
[[name:Coord2, quota:300], [name:Coord2, copiesToDate:30]].sum()
into:
[name:Coord2, quota:300, copiesToDate:30]
lastly we want to add the balance property to the maps so we iterate through what is now a list of two maps and add balance as a computation of quota - copiesToDate. The each construct returns the list it is working on which is what we assign to x.
Don't call find twice. Use the Map.plus() method to append new entries. Handle missing names from map2.
def map3 = map1.collect {m1 ->
def m2 = map2.find {it.name == m1.name} ?: [copiesToDate: 0]
m1 + m2 + [balance: m1.quota - m2.copiesToDate]
}
Another option for fun :-)
def result = (map1 + map2).groupBy { it.name }
.values()
*.sum()
.collect { it << ['balance': it.quota - it.copiesToDate] }
add the lists together
group by the name
get the grouped values and concatenate them
then for each of them, work out the balance