3

I don't have a specific piece of code that I want looked at; however, I do have a question that I can't seem to get a straight, clear answer.

Here's the question: if I have a set, I can iterate over it in a for loop. As I iterate over it can I remove specific numbers using .remove() or do I have to convert my set to a list first? If that is the case, why must I convert it first?

Dyland
  • 91
  • 1
  • 11
  • Did you try `set.remove(item)`. Where set is your set name and the item is the one you want to remove – Banks Apr 12 '19 at 00:58
  • 1
    In general, you should avoid modifying something you're iterating over. I would prefer using a `set` comprehension. – gmds Apr 12 '19 at 01:01
  • @Shub, yes the error returned is "Set changed size during iteration." I am unsure of why removing something from a set in a for loop is an issue for Python when it isn't a problem for a list. – Dyland Apr 12 '19 at 01:03
  • 2
    Possible duplicate of [How to loop through a set, while removing items from the set in Python 3](https://stackoverflow.com/questions/31448465/how-to-loop-through-a-set-while-removing-items-from-the-set-in-python-3) – CryptoFool Apr 12 '19 at 01:07
  • 1
    Another good post: https://stackoverflow.com/questions/16551334/delete-items-from-a-set-while-iterating-over-it – CryptoFool Apr 12 '19 at 01:10
  • The basic answer is that you should produce a new set, not modify the one you're iterating over. I like the very simple `while (myset): v = myset.pop()` approach, where if you don't want to delete the value, you add it to a new set that replaces the old set once you're done. – CryptoFool Apr 12 '19 at 01:12

3 Answers3

1

In both cases, you should avoid iterating and removing items from a list or set. It's not a good idea to modify something that you're iterating through as you can get unexpected results. For instance, lets start with a set

numbers_set = {1,2,3,4,5,6}
for num in numbers_set:
    numbers_set.remove(num)
print(numbers_set)

We attempt to iterate through and delete each number but we get this error.

Traceback (most recent call last):
  File ".\test.py", line 2, in <module>
    for num in numbers_set:
RuntimeError: Set changed size during iteration

Now you mentioned "do I have to convert my set to a list first?". Well lets test it out.

numbers_list = [1,2,3,4,5,6]
for num in numbers_list:
    print(num)
    numbers_list.remove(num)
print(numbers_list)

This is the result:

[2, 4, 6]

We would expect the list to be empty but it gave us this result. Whether you're trying to iterate through a list or a set and delete items, its generally not a good idea.

nathancy
  • 42,661
  • 14
  • 115
  • 137
1

This is how I'd do this:

myset = ...
newset = set()
while myset:
    v = myset.pop()
    if not do_i_want_to_delete_this_value(v):
        newset.add(v)
myset = newset

A list comprehension will work too:

myset = set([x for x in myset if not do_i_want_to_delete_this_value(x)])

But this gets messy if you want to do other stuff while you're iterating and you don't want to wrap all that logic in a single function call. Nothing wrong with doing that though.

myset = set([x for x in myset if process_element(x)])

process_element() just has to return True/False to say if the element should be removed from the set.

CryptoFool
  • 21,719
  • 5
  • 26
  • 44
1

@nathancy has already given a good explanation as to why deleting during iteration won't work, but I'd like to suggest an alternative: instead of doing the deletion at the same time as you iterate, do it instead as a second stage. So, you'd instead:

  1. Iterate over your set to decide what you want to delete, and store the collection of things to be deleted separately.
  2. Iterate over your to-be-deleted collection and removing each item from the original set.

For instance:

def should_be_deleted(num):
    return num % 2 == 0

my_set = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
to_delete = []
for value in my_set:
    if should_be_deleted(value):
        to_delete.append(value)

for value in to_delete:
    my_set.remove(value)

print(my_set)

Prints:

set([1, 3, 5, 7, 9])

The same pattern can be applied to delete from any collection—not just set, but also list, dict, etc.

s3cur3
  • 2,749
  • 2
  • 27
  • 42
  • Why add the complexity of creating a list of elements you want to delete? Why not just construct the new set with the items you want to keep as you go? Seems like unnecessary complication, and an extra loop you don't need. – CryptoFool Apr 12 '19 at 01:27
  • It comes down to personal preference. In my defence, the method above would be faster if the number of deleted elements were much smaller than the size of the set to start with. Obviously doesn't make a difference for the toy example. – s3cur3 Apr 12 '19 at 01:31
  • 1
    Yes, that's a good point. It would depend on if you want to delete relatively few items from a fairly large list. Your way could be more efficient. I gave you an upvote as a mea culpa :) – CryptoFool Apr 12 '19 at 01:33