16
from collections import *
ignore = ['the','a','if','in','it','of','or']
ArtofWarCounter = Counter(ArtofWarLIST)
for word in ArtofWarCounter:
    if word in ignore:
        del ArtofWarCounter[word]

ArtofWarCounter is a Counter object containing all the words from the Art of War. I'm trying to have words in ignore deleted from the ArtofWarCounter.

Traceback:

  File "<pyshell#10>", line 1, in <module>
    for word in ArtofWarCounter:
RuntimeError: dictionary changed size during iteration
Nikana Reklawyks
  • 3,233
  • 3
  • 33
  • 49
Louis93
  • 3,843
  • 8
  • 48
  • 94

4 Answers4

19

For minimal code changes, use list, so that the object you are iterating over is decoupled from the Counter

ignore = ['the','a','if','in','it','of','or']
ArtofWarCounter = Counter(ArtofWarLIST)
for word in list(ArtofWarCounter):
    if word in ignore:
        del ArtofWarCounter[word]

In Python2, you can use ArtofWarCounter.keys() instead of list(ArtofWarCounter), but when it is so simple to write code that is futureproofed, why not do it?

It is a better idea to just not count the items you wish to ignore

ignore = {'the','a','if','in','it','of','or'}
ArtofWarCounter = Counter(x for x in ArtofWarLIST if x not in ignore)

note that I made ignore into a set which makes the test x not in ignore much more efficient

John La Rooy
  • 295,403
  • 53
  • 369
  • 502
  • Great answer, thanks. I used a tiny variation: instead of for word in list(ArtofWarCounter), I used the ArtofWarLIST since they are essentially the same thing. Thanks! – Louis93 Aug 23 '11 at 12:42
  • @Louis93, I think `ArtofWarLIST` can contain duplicates which means you would be looping extra times. I'll add a better way to my answer – John La Rooy Aug 23 '11 at 21:23
19

Don't loop over all words of a dict to find a entry, dicts are much better at lookups.

You loop over the ignore list and remove the entries that exist:

ignore = ['the','a','if','in','it','of','or']
for word in ignore:
    if word in ArtofWarCounter:
        del ArtofWarCounter[word]
Jochen Ritzel
  • 104,512
  • 31
  • 200
  • 194
1

See the following question for why your current method is not working:
Remove items from a list while iterating

Basically you should not add or remove items from a collection while you are looping over it. collections.Counter is a subclass of dict, see the following warning in the documentation for dict.iteritems():

Using iteritems() while adding or deleting entries in the dictionary may raise a RuntimeError or fail to iterate over all entries.

Community
  • 1
  • 1
Andrew Clark
  • 202,379
  • 35
  • 273
  • 306
  • Nope, runs to an error. ArtofWarCounter[:] = [word for word in ArtofWarCounter if word not in ignore] TypeError: unhashable type Also Counters are a dict subclass, so I doubt they can be sliced like lists unless I'm mistaken – Louis93 Aug 22 '11 at 22:29
  • @Louis93 - Sorry, I was a little hasty in my response. I left the explanation up since it may still be useful, one of the other answers should give you a working solution. – Andrew Clark Aug 22 '11 at 22:36
-2

Use a counter, traverse the loop backwards (last to first), remove as needed. Loop until zero.

Arun
  • 2,493
  • 15
  • 12
  • I'm not sure I get it, why would that work over doing it from beginning to end? – Louis93 Aug 22 '11 at 22:31
  • I apologize, I am not very familiar with Python - but I have dealt with this "Collection modified" issue in C# this way. I would need to use a Counter to loop through the Collection - however, I cannot start the counter from 0 to (n - 1). This is because, my end condition for looping will fail if I try to loop till (n - 1), when I would have actually deleted some between 0 to (n - 1). Traversing the loop backwords would work in such cases, because, my loop end condition (that is 0) will always work. Also, since I start with (n - 1) as my loop begin condition, I wouldn't miss any. – Arun Aug 22 '11 at 22:35
  • Thank you Louis93 for voting it down (twice), when I was merely trying to help you! – Arun Aug 23 '11 at 00:01
  • 1
    OP is unlikely to blame for either downvote, let alone both. You can only downvote a given thing once anyway. The answer is getting downvoted because it won't work. The container being modified is unordered, so you can't iterate over it backwards with an index value any more than you could with a `Dictionary<>` in C#. Also, iterating in this way is generally considered to be highly un-Pythonic. Even C++ programmers avoid it nowadays: see for example my answer here http://stackoverflow.com/questions/4383250/why-should-i-use-foreach-instead-of-for-int-i-0-ilength-i-in-loops/4383321#4383321 . – Karl Knechtel Aug 23 '11 at 01:00
  • I don't downvote as a rule of thumb, I just don't upvote if the answer doesn't seem to help :), I gave you my up since you seem worried about the score. Thanks for helping out but your method doesn't translate well to Python as Karl mentions – Louis93 Aug 23 '11 at 12:41
  • "un-Pythonic" stuff apart, if you are interested more about looping backward to safely remove from any collection, this post from "dlev" http://stackoverflow.com/questions/7193294/c-intelligent-way-of-removing-items-from-a-listt-while-enumerating/7193317#7193317 that I happened to chance upon today explains the method - specifically if you need to do anything more than just delete. – Arun Aug 25 '11 at 16:07