Bob
Bob

Reputation: 10775

Groovy modify map elements while iterating a map

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

Answers (3)

Roger Glover
Roger Glover

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.


Second, I recommend using 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.


If you follow these recommendations, your final code should look something 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.each{ k, v ->
  v.remove((Object)b)
}
println a

Upvotes: 1

tim_yates
tim_yates

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

cfrick
cfrick

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

Related Questions