Remove key/value from map while iterating

17,045

Solution 1

This should be a bit more efficient than Tim's answer (because you only need to iterate over the map once). Unfortunately, it is also pretty verbose

def map = [2:1, 3:4]
def iterator = map.entrySet().iterator()

while (iterator.hasNext()) {

  if (iterator.next().value - 1 <= 0) {
    iterator.remove()
  }
}

// test that it worked
assert map == [3:4]

Solution 2

Can you do something like this:

myMap = myMap.each { it.value-- }.findAll { it.value > 0 }

That will subtract one from every value, then return you a new map of only those entries where the value is greater than zero.

You shouldn't call the remove method on a Map Entry, it is supposed to be a private method used internally by the Map (see line 325 for the Java 7 implementation), so you calling it yourself is getting the enclosing Map into all sorts of bother (it doesn't know that it is losing entries)

Groovy lets you call private methods, so you can do this sort of trickery behind the back of the Java classes

Edit -- Iterator method

Another way would be:

myMap.iterator().with { iterator ->
  iterator.each { entry ->
    entry.value--
    if( entry.value <= 0 ) iterator.remove()
  }
}
Share:
17,045
divillysausages
Author by

divillysausages

Updated on August 03, 2022

Comments

  • divillysausages
    divillysausages almost 2 years

    I'm creating a map like this:

    def myMap = [:]
    

    The map is basically an object for a key and an int for a value. When I iterate over the map, I decret the value, and if it's 0, I remove it. I already tried myMap.remove(), but I get a ConcurrentModificationError - which is fair enough. So I move on to using it.remove(), which is giving me weird results.

    Basically, my code is this:

    myMap.each {
        it.value--;
    
        if( it.value <= 0 )
            it.remove();
    }
    

    Simple enough. My problem is, if I print myMap.size() before and after the remove, they're the same. If I call myMap.containsKey( key ), it gives me true, the key is still in there.

    But, if I print out the map like this:

    myMap.each { System.out.println( "$it.key: $it.value" ); }
    

    I get nothing, and calling myMap.keySet() and myMap.values() return empty.

    Anyone know what's going on?

  • divillysausages
    divillysausages over 12 years
    ah, groovy and private... do you know why containsKey() still returns true even if the keySet() is empty? do you have a way that doesn't create a new map (I'm like that :D)?
  • divillysausages
    divillysausages over 12 years
    I have it working using for (Iterator<Map.Entry<Object, Integer>> it = myMap.entrySet().iterator(); it.hasNext();) but, well, that's reallly long :)
  • tim_yates
    tim_yates over 12 years
    HashMap keeps an internal cache of key hashes (for containsKey calls) separately to where AbstractMap keeps its Set of keys. So pulling items out of the map in this way is going to lead to some pretty odd results ;-)
  • tim_yates
    tim_yates over 12 years
    Hehe, you beat me to the iterator method ;-)
  • divillysausages
    divillysausages over 12 years
    you've an extra ) in there, and it won't actually remove anything, but that's what I was looking for, thanks :)
  • Dónal
    Dónal over 12 years
    @tim_yates even a blind squirrel finds a nut every once in a while