Hi,

I'm having trouble removing objects from a list. It seems that when I remove single objects in a loop, the loop kind of skips elements in the list, not continuing from the point where the last removal occurred. I want to remove only certain elements from a list, and there should be a way for an object of the list to remove itself, or mark itself for removal.

Example code:

class FooObj:
    def __init__(self, i, a):
        self.i = i
        self.a = a
    
    def rem(self, list): #Doesn't work correctly
        list.remove(self)
        
    def __repr__(self):
        return "(obj " + str(self.i) + ", active = " + str(self.a) + ")"
        

if __name__ == "__main__":
    o1 = FooObj(1, 1)
    o2 = FooObj(2, 0)
    o3 = FooObj(3, 0)
    o4 = FooObj(4, 1)
    o5 = FooObj(5, 1)
    
    #Test 1
    lst = [o1, o2, o3, o4, o5]
    
    #Trying to remove objects flagged as inactive (a = 0)
    for obj in lst:
        if obj.a == 0:
            lst.remove(obj)
            
    #Prints: [(obj 1, active = 1), (obj 3, active = 0), (obj 4, active = 1), (obj 5, active = 1)]
    #^obj 3 is wrong! All objs with a=0 should be removed.
    print lst 
    
    
    #Test 2
    lst = [o1, o2, o3, o4, o5]
    
    #Trying to remove all objects. I need to be able to call a function of the object for each object in the loop.
    for obj in lst:
        obj.rem(lst)
        
    #Prints: [(obj 2, active = 0), (obj 4, active = 1)]
    #completely wrong
    print lst

-Jusa

Recommended Answers

All 5 Replies

Hi,

your "error" is a very common source of confusion :)
Let's take a slightly simpler example:

In [1]: lst = ["a", "b", "b", "d"]

In [2]: for elem in lst:
  ....:     print "before: ", lst      # check the list
  ....:     print elem, lst.index(elem)   # check which element we are using
  ....:     if elem == "b":             # remove if element is a "b"
  ....:         lst.remove(elem)
  ....:     print "after: ", lst      # check list again
  ....:     print "-" * 20
  ....:     
before:  ['a', 'b', 'b', 'd']
a 0          
after:  ['a', 'b', 'b', 'd']
--------------------
before:  ['a', 'b', 'b', 'd']
b 1
after:  ['a', 'b', 'd']
--------------------
before:  ['a', 'b', 'd']
d 2
after:  ['a', 'b', 'd']
--------------------

Ok, let's go through the output step by step:

before:  ['a', 'b', 'b', 'd']
a 0          
after:  ['a', 'b', 'b', 'd']

The first element in our list as an "a", it has the index 0 (the first index). It's not a "b" so we don't remove it and our list looks the same as before. Step 2:

before:  ['a', 'b', 'b', 'd']
b 1
after:  ['a', 'b', 'd']

Yeah, some action here! We are checking element number 2, with index 1, and it's a b -> remove it. Our list has now 3 elements ... alarm bells ringing? Let's look at Step 3:

before:  ['a', 'b', 'd']
d 2
after:  ['a', 'b', 'd']

See what happened? There is still a "b" here, but it is the second element, index 1. We already did something with the second element, in step 2. Now we are checking element number 3. It's a "d", so it stays and ... the loop is finished. Because we deleted one element, we jumped over one "b".
Thats what happens in your code.

What did we learn: Never delete something from or add something to the list you are iterating over.

The solution is: iterate over your original list, and put everything which passes your test into another list:

lst2 = [ elem for elem in lst if elem != "b" ]

Regards, mawe

Thanks for the indepth demonstration. It helped and solved the problem.

Good one, mawe.

If you need to filter the list in a more complicated way, you can also do this:

for item in mylist[:]:
  if item_is_a_reject():  #whatever test you need to run goes here
     mylist.remove(item)

so we iterate through a *copy* of mylist, but remove items from the original.

Jeff

You are right, that's another way. It may be a matter of taste, but ... I don't like it :) I would rather do something like this:

In [1]: def valid( elem ):   # very complicated function
   ...:     if elem < 0: return False
   ...:     return True

and then as above

In [2]: lst = [-3, 2, -5, -10, 12]
In [3]: [ elem for elem in lst if valid(elem) ]
Out[3]: [2, 12]

or, because I'm a big fan of functional programming

In [4]: filter(valid, lst)
Out[4]: [2, 12]

But as I said, it's just a matter of taste ;)

1. Implement the linked list program with the Vectors concepts that you have learnt.
2. Take 1 Vector and store all the values in it.
3. Take another vector and store the addresses of the next value (in this context it would be the indexes of the first vector).
4. So far Example, If you want to insert an element at the end, insert it to the end of the first vector and store its index in the second vector.
5. Similarly, If you want to insert an element in the beginning of the list:
1. Insert the value at the end of the first array.
2. Now, Shuffle all the addresses in the second Vector appropriately.
6. Please click here for the example document.
I want coding for these 6 ?

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.