128

I'm iterating over a list of elements in Python, do some action on it, and then remove them if they meet certain criteria.

for element in somelist:
    do_action(element)
    if check(element):
        remove_element_from_list

What should I use in place of remove_element? I have seen similar questions asked, but notice the presence of the do_action part that is to be executed for all elements and thus eliminates the solution of using filters.

7
  • 2
    Can't you split it in two steps i.e. for all elements do the action, then remove elements ? Commented May 16, 2011 at 20:16
  • 24
    You should NEVER delete an element from a list while iterating over it in a for loop. You could use a while loop instead. Or, record the indices of all the elements you want to remove and then delete them after the iteration is complete Commented May 16, 2011 at 20:17
  • For all: I need to modify the list in place, no copies. Actually the list is passed to a function and the list needs to be modified by that function. Commented May 16, 2011 at 20:28
  • @Scrontch, it's still possible (and better) to loop through the list and then replace it's contents as a second pass . That is why I use [:] in my answer Commented May 16, 2011 at 20:39
  • 3
    @inspectorG4dget: Your first sentence is untenable -- see my answer. Commented May 16, 2011 at 23:53

9 Answers 9

217

You could always iterate over a copy of the list, leaving you free to modify the original:

for item in list(somelist):
  ...
  somelist.remove(item)
Sign up to request clarification or add additional context in comments.

8 Comments

@Scrontch Given you additional criteria of "modify the list in place", this looks like the cleanest solution. As some have mentioned, you don't want to iterate over the same list you're modifying.
Ok, but it seems not to be performing well at all. Won't this be O(n^2)? (And that is not counting the initial list copy). Removing the element on the fly would be O(n).
using map (or list comprehensions) for side effects and throwing away the result is not very pythonic
The alternative implementation is wrong. You will need to reverse 'toremove' before the map function. Otherwise later indices point to wrong objects.
This seems pretty broken, list.remove removes the first occurrence of a value by equality, if you were trying to remove all float values from [0, 1, 1.0, 0] just by doing .remove(0.0) etc. you would end up with [1.0, 0.0] which is definitely not the result with all float removed.
|
149
+50

To meet these criteria: modify original list in situ, no list copies, only one pass, works, a traditional solution is to iterate backwards:

for i in xrange(len(somelist) - 1, -1, -1):
    element = somelist[i]
    do_action(element)
    if check(element):
        del somelist[i]

Bonus: Doesn't do len(somelist) on each iteration. Works on any version of Python (at least as far back as 1.5.2) ... s/xrange/range/ for 3.X.

Update: If you want to iterate forwards, it's possible, just trickier and uglier:

i = 0
n = len(somelist)
while i < n:
    element = somelist[i]
    do_action(element)
    if check(element):
        del somelist[i]
        n = n - 1
    else:
        i = i + 1

5 Comments

You could also use reversed(range(len(somelist))) to make it look a bit nicer
it is quadratic algorithm (O(n**2)). Here's a linear solution if there are many elements to remove and you don't want to use list comprehension for any reason.
Is this documented/defined behavior, or is it undefined behavior that happens to work (for now)?
@Maëlan: there are O(n) del L[i] operations. Removing an element from a middle of a Python list is O(n) therefore the whole algorithm is quadratic O(n*n).
@jfs Oh my bad, you are right of course.
14

List comp:

results = [x for x in (do_action(element) for element in somelist) if check(element)]

1 Comment

but the OP wants to call the "check" function after calling do_action
11
for element in somelist:
    do_action(element)
somelist[:] = (x for x in somelist if not check(x))

If you really need to do it in one pass without copying the list

i=0
while i < len(somelist):
    element = somelist[i] 
    do_action(element)
    if check(element):
        del somelist[i]
    else:
        i+=1

3 Comments

Also you could use a generator expression, then no temporary list is built: somelist[:] = (x for x in somelist if not check(element))
-1 Elementary code review says "Doesn't work".
@John, ah well the idea was there. That's what I get for writing untested code at 6:30 before my coffee
7

You can still use filter, moving to an outside function the element modification (iterating just once)

def do_the_magic(x):
    do_action(x)
    return check(x)

# you can get a different filtered list
filter(do_the_magic,yourList)

# or have it modified in place (as suggested by Steven Rumbalski, see comment)
yourList[:] = itertools.ifilter(do_the_magic, yourList)

2 Comments

Your arguments to filter are in the wrong order. Also, he wants the list modified in place, so use itertools.ifilter and assign to a slice: yourList[:] = itertools.ifilter(do_the_magic, yourList)
thanks, fixed the order. I didn't notice the "in place" requirement
4

Another way of doing so is:

while i<len(your_list):
    if #condition :
        del your_list[i]
    else:
        i+=1

So, you delete the elements side by side while checking

Comments

2

You can make a generator that returns everything that isn't removed:

def newlist(somelist):
    for element in somelist:
        do_action(element)
        if not check(element):
            yield element

4 Comments

Except the OP want the list modified in place, presumably there are other references to it, so creating a filtered copy doesn't solve the whole problem.
@Paul, it's a little unfair to judge by criteria that weren't part of the original question. The hard requirement to do the update in-place came in a comment after I gave this answer.
true, I see this all the time with the parsing questions. There is a process of discovery, since the original question usually leaves out 1 or 12 (or 12!) vital bits of information.
To make this in-place assign to a slice: somelist[:] = (x for x in newlist(someList))
0

Why not rewrite it to be

for element in somelist: 
   do_action(element)  

if check(element): 
    remove_element_from_list

See this question for how to remove from the list, though it looks like you've already seen that Remove items from a list while iterating

Another option is to do this if you really want to keep this the same

newlist = [] 
for element in somelist: 
   do_action(element)  

   if not check(element): 
      newlst.append(element)

Comments

0

Not exactly in-place, but some idea to do it:

a = ['a', 'b']

def inplace(a):
    c = []
    while len(a) > 0:
        e = a.pop(0)
        if e == 'b':
            c.append(e)
    a.extend(c)

You can extend the function to call you filter in the condition.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.