Question

I'm trying to drop items from a dictionary if the value of the key is below a certain threshold. For a simple example to what I mean:

my_dict = {'blue': 1, 'red': 2, 'yellow': 3, 'green': 4}

for color in my_dict:
    threshold_value = 3
    if my_dict[color] < threshold_value:
        del my_dict[color]

print(my_dict)

Now, I get a RuntimeError: dictionary changed size during iteration error. No big surprises there. The reason I'm posting this question is:

  1. Find out if there's an elegant solution that doesn't require creating a new dictionary (that holds only the keys with values >= threshold).

  2. Try to understand Python's rationale here. The way I read it to myself is: "go to the first key. Is the value of that key < x ? if yes - del this key:value item and continue on the the next key in the dictionary, if no - continue to next key without doing anything". In other words, what happened historically to previous keys shouldn't affect where I go next. I'm looking forward to the next items, regardless of the past. I know it's a funny (some might say stupid, I'll give you that) but what's Python's "way of thinking" about this loop? Why doesn't it work? How would Python read it out loud to itself? Just trying to get a better understanding of the language...

Was it helpful?

Solution

Due to the fact that Python dictionaries are implemented as hash tables, you shouldn't rely on them having any sort of an order. Key order may change unpredictably (but only after insertion or removal of a key). Thus, it's impossible to predict the next key. Python throws the RuntimeError to be safe, and to prevent people from running into unexpected results.

Python 2's dict.items method returns a copy of key-value pairs, so you can safely iterate over it and delete values you don't need by keys, as @wim suggested in comments. Example:

for k, v in my_dict.items():
    if v < threshold_value:
        del my_dict[k]

However, Python 3's dict.items returns a view object that reflects all changes made to the dictionary. This is the reason the solution above only works in Python 2. You may convert my_dict.items() to list (tuple etc.) to make it Python 3-compatible.

Another way to approach the problem is to select keys you want to delete and then delete them

keys = [k for k, v in my_dict.items() if v < threshold_value]
for x in keys:
    del my_dict[x]

This works in both Python 2 and Python 3.

OTHER TIPS

Dictionaries are unordered. By deleting one key nobody can say, what the next key is. So python in general disallow to add or remove keys from a dictionary, over that is iterated.

Just create a new one:

my_dict = {"blue":1,"red":2,"yellow":3,"green":4}
new_dict = {k:v for k,v in my_dict.iteritems() if v >= threshold_value}

I guess that modifying a collection while iterating over it is a hard thing to do to implement properly. Consider following exaple:

>>> list = [1, 2, 3, 4, 5, 6]
>>> for ii in range(len(list)):
  print list[ii]; 
  if list[ii] == 3:
    del list[ii]      
 1
 2
 3
 5
 6

Notice that in this example 4 was altogether omitted. It is very similat in dictionaries, deleting/adding entries might invalidate internal structures that define order of iteration (for example you deleted enough entries so hash map bucket size changed).

To solve your case --- just create new dictionary and copy items there. As to

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top