Reputation: 10775
I need to modify map elements while iterating a map, in my case subtract some elements (list). Like this:
def a = [
1: [1, 2, 3],
2: [3, 2, 4],
3: [3, 2, 4],
4: [5, 2, 1],
]
def b = [3]
println a
a.values().each{ tr ->
tr = tr - b
}
println a
The a
map has not changed. Result is:
[1:[1, 2, 3], 2:[3, 2, 4], 3:[5, 3, 1], 4:[5, 2, 1]]
[1:[1, 2, 3], 2:[3, 2, 4], 3:[5, 3, 1], 4:[5, 2, 1]]
However, I want the result to be [1:[1, 2], 2:[2, 4], 3:[5, 1], 4:[5, 2, 1]]
. What I am doing wrong? Due to the fact that the initial map is rather big I do not want to construct another map with less elements (result map).
Upvotes: 4
Views: 13226
Reputation: 3256
First, I recommend using the plain, vanilla Java List.remove(Object)
instead of Groovy's List.minus(List)
. That, is change the lines:
def b = [3]
and
tr = tr - b
into the following:
def b = 3
and
tr.remove((Object)b)
The problem with the the former pair of statements is that the expression tr - b
creates a new list object. This list is assigned to the local variable tr
. Meanwhile the original list, the one residing in the Map, the original value of tr
before the assignment, remains unchanged.
The explicit List.remove(Object)
method, though not as "Groovy" as the subtraction, operates on the original list without creating a new List. Besides producing the right result this technique also has the advantage of not instantiating a new list for each map entry.
Note that it is necessary to cast b
to Object
explicitly. Groovy performs dynamic dispatch to resolve operator overloading. Without the cast, even though the static type of b
is already Object
, Groovy would detect that b
is an integer and invoke List.remove(int)
instead of List.remove(Object)
, which would fail. With the cast, Groovy has enough of a hint to do the right thing.
Map.each
instead of Map.values()
chained to List.each
. Thus replacing this line:
a.values().each { tr ->
with the following:
a.each { key, tr ->
The former statement causes the map to instantiate a list containing all the values, while the latter statement does not. Although the actual "values" in the first case are the same as the values from the map, you had said that the map was so large that you did not want to create a whole new map. So I assume likewise that you would rather not create an entire list out of the value objects of the map.
def a = [
1: [1, 2, 3],
2: [3, 2, 4],
3: [3, 2, 4],
4: [5, 2, 1],
]
def b = 3
println a
a.each{ k, v ->
v.remove((Object)b)
}
println a
Upvotes: 1
Reputation: 171054
Or you could use collectEntries
:
def a = [
1: [1, 2, 3],
2: [3, 2, 4],
3: [3, 2, 4],
4: [5, 2, 1],
]
def b = [3]
a = a.collectEntries { k, v -> [k, v - b] }
Upvotes: 4
Reputation: 37008
work directly on the map instead:
a.keySet().each{
a[it]-=b
}
In your code you are assigning the result to the local variable.
If you have concerns about the amount of changes you might look into use of removeAll()
or by iterating over b
and remove()
each one. Those change the list in place.
Upvotes: 8