How to Delete Items from a Dictionary While Iterating Over It

How do I delete items from a dictionary while iterating over it?

For Python 3+:

>>> mydict
{'four': 4, 'three': 3, 'one': 1}

>>> for k in list(mydict.keys()):
... if mydict[k] == 3:
... del mydict[k]

>>> mydict
{'four': 4, 'one': 1}

The other answers work fine with Python 2 but raise a RuntimeError for Python 3:

RuntimeError: dictionary changed size during iteration.

This happens because mydict.keys() returns an iterator not a list.
As pointed out in comments simply convert mydict.keys() to a list by list(mydict.keys()) and it should work.


For Python 2:

A simple test in the console shows you cannot modify a dictionary while iterating over it:

>>> mydict = {'one': 1, 'two': 2, 'three': 3, 'four': 4}

>>> for k, v in mydict.iteritems():
... if k == 'two':
... del mydict[k]

------------------------------------------------------------
Traceback (most recent call last):
File "<ipython console>", line 1, in <module>
RuntimeError: dictionary changed size during iteration

As stated in delnan's answer, deleting entries causes problems when the iterator tries to move onto the next entry. Instead, use the keys() method to get a list of the keys and work with that:

>>> for k in mydict.keys():
... if k == 'two':
... del mydict[k]

>>> mydict
{'four': 4, 'three': 3, 'one': 1}

If you need to delete based on the items value, use the items() method instead:

>>> for k, v in mydict.items():
... if v == 3:
... del mydict[k]

>>> mydict
{'four': 4, 'one': 1}

Remove element from dictionary by key while iterating

Here is a version modifying your first example, you will need to "copy" your list to iterate with it and deleting at the same time. After you're iterating with the copied list, you can delete from the original list as necessary.

import copy

qr = [{'A': 'B', 'C': '3', 'EE': None, 'P': '343', 'AD': ' ', 'B': ''},
{'A': 'B', 'C': '3', 'EE': None, 'P': '343', 'AD': ' ', 'B': ''}]

for i, row in enumerate(copy.deepcopy(qr)):
for key, value in row.items():
if value in {' ', None, ''}:
del qr[i][key]

print(qr)

Other than that, usually you want to create a new list than to delete from the original list. A simple list comprehension will do the trick:

qr = [{k:v for k, v in row.items() if v not in {' ', None, ''}} for row in qr]

print(qr) # same result

Output for both:

[{'A': 'B', 'C': '3', 'P': '343'},
{'A': 'B', 'C': '3', 'P': '343'}]

Deleting items from a dictionary with a for loop

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.

delete item from dictionary while iterating over it

The use of ElementAt(i) is not the ideal way to get a particular item and will perform poorly. Its usage suggests that you want a collection with an indexer, such as IList<T>.

Using your current setup, you could use this approach:

foreach(var key in buzzCompaignsPerUserIntersets.Keys)
{
var list = buzzCompaignsPerUserIntersets[key];
var query = list.Where(o => o.MayaMembership
.MayaProfile.MayaProfileId == profile_id)
.ToArray();
foreach (var item in query)
{
list.Remove(item);
}
}

Alternately, if you can change the ICollection<T> to an IList<T> you could use the indexer and the RemoveAt method. That would look like this:

foreach(var key in buzzCompaignsPerUserIntersets.Keys)
{
var list = buzzCompaignsPerUserIntersets[key];
for (int i = list.Count - 1; i >= 0; i--)
{
if (list[i].MayaMembership.MayaProfile.MayaProfileId == profile_id)
{
list.RemoveAt(i);
}
}
}

A List<T> would let you use the RemoveAll method. If you're interested in how that works take a look at my answer to another question.

custom dict that allows delete during iteration

As you note, you can store the items to delete somewhere and defer the deletion of them until later. The problem then becomes when to purge them and how to make sure that the purge method eventually gets called. The answer to this is a context manager which is also a subclass of dict.

class dd_dict(dict):    # the dd is for "deferred delete"
_deletes = None
def __delitem__(self, key):
if key not in self:
raise KeyError(str(key))
dict.__delitem__(self, key) if self._deletes is None else self._deletes.add(key)
def __enter__(self):
self._deletes = set()
def __exit__(self, type, value, tb):
for key in self._deletes:
try:
dict.__delitem__(self, key)
except KeyError:
pass
self._deletes = None

Usage:

# make the dict and do whatever to it
ddd = dd_dict(a=1, b=2, c=3)

# now iterate over it, deferring deletes
with ddd:
for k, v in ddd.iteritems():
if k is "a":
del ddd[k]
print ddd # shows that "a" is still there

print ddd # shows that "a" has been deleted

If you're not in a with block, of course, deletes are immediate; as this is a dict subclass, it works just like a regular dict outside of a context manager.

You could also implement this as a wrapper class for a dictionary:

class deferring_delete(object):
def __init__(self, d):
self._dict = d
def __enter__(self):
self._deletes = set()
return self
def __exit__(self, type, value, tb):
for key in self._deletes:
try:
del self._dict[key]
except KeyError:
pass
del self._deletes
def __delitem__(self, key):
if key not in self._dict:
raise KeyError(str(key))
self._deletes.add(key)

d = dict(a=1, b=2, c=3)

with deferring_delete(d) as dd:
for k, v in d.iteritems():
if k is "a":
del dd[k] # delete through wrapper

print d

It's even possible to make the wrapper class fully functional as a dictionary, if you want, though that's a fair bit more code.

Performance-wise, this is admittedly not such a win, but I like it from a programmer-friendliness standpoint. The second method should be very slightly faster since it's not testing a flag on each delete.

Deleting while iterating over a dictionary

Keep a list of the keys you wish to remove as you find them. Then, when you are done, iterate over this list, calling myDictionary.Remove(key) on each key you stored.

delete items from a set while iterating over it

First, using a set, as Zero Piraeus told us, you can

myset = set([3,4,5,6,2])
while myset:
myset.pop()
print(myset)

I added a print method giving these outputs

>>> 
set([3, 4, 5, 6])
set([4, 5, 6])
set([5, 6])
set([6])
set([])

If you want to stick to your choice for a list, I suggest you deep copy the list using a list comprehension, and loop over the copy, while removing items from original list. In my example, I make length of original list decrease at each loop.

l = list(myset)
l_copy = [x for x in l]
for k in l_copy:
l = l[1:]
print(l)

gives

>>> 
[3, 4, 5, 6]
[4, 5, 6]
[5, 6]
[6]
[]

How to remove items from a list while iterating?

You can use a list comprehension to create a new list containing only the elements you don't want to remove:

somelist = [x for x in somelist if not determine(x)]

Or, by assigning to the slice somelist[:], you can mutate the existing list to contain only the items you want:

somelist[:] = [x for x in somelist if not determine(x)]

This approach could be useful if there are other references to somelist that need to reflect the changes.

Instead of a comprehension, you could also use itertools. In Python 2:

from itertools import ifilterfalse
somelist[:] = ifilterfalse(determine, somelist)

Or in Python 3:

from itertools import filterfalse
somelist[:] = filterfalse(determine, somelist)


Related Topics



Leave a reply



Submit