views:

490

answers:

4

In Java I can do by using an Iterator and then using the .remove() method of the iterator to remove the last element returned by the iterator, like this:

import java.util.*;

public class ConcurrentMod {
    public static void main(String[] args) {
        List<String> colors = new ArrayList<String>(Arrays.asList("red", "green", "blue", "purple"));
        for (Iterator<String> it = colors.iterator(); it.hasNext(); ) {
            String color = it.next();
            System.out.println(color);
            if (color.equals("green"))
                it.remove();
        }
        System.out.println("At the end, colors = " + colors);
    }
}

/* Outputs:
red
green
blue
purple
At the end, colors = [red, blue, purple]
*/

How would I do this in Python? I can't modify the list while I iterate over it in a for loop because it causes stuff to be skipped (see here). And there doesn't seem to be an equivalent of the Iterator interface of Java.

+8  A: 

Best approach in Python is to make a new list, ideally in a listcomp, setting it as the [:] of the old one, e.g.:

colors[:] = [c for c in colors if c != 'green']

NOT colors = as some answers may suggest -- that only rebinds the name and will eventually leave some references to the old "body" dangling; colors[:] = is MUCH better on all counts;-).

Alex Martelli
List comprehension is the best choice.
hughdbrown
or colors=list(c for c in colors if c != 'green')
dugres
@dugres: not quite: colors = list(...) does rebind. Alex insisted on the idea that it's better not do leave useless lists dangling in memory.
EOL
well, colors[:] = ... is only better if there are other references to worry about (for some reason, this rarely turns out to be the case for me). Still, the list comprehension is definitely the way to go.
Devin Jeanpierre
@Devin, NOT just for other references. E.g. if colors is a global doing `colors=` in a fuction requires an extra `global colors`, `colors[:]=` **doesn't**. GC of the old list does't happen instantly in all versions of Python. Etc: there's NEVER any downside in assigning to `name[:]`, OFTEN many downsides to assigning to `name` (including the occasional puzzling bug where the "rarely for you" case DOES occur but you're used to the wrong way), so it's a hiding to nothing FOR the correct way, `name[:]=`, and AGAINST the wrong one, `name=`. Only one obvious way...
Alex Martelli
...though it may not be obvious unless you're Dutch;-).
Alex Martelli
+3  A: 

iterate over a copy of the list:

for c in colors[:]:
    if c == 'green':
        colors.remove(c)
scrible
Why colors[:] instead of colors?
hughdbrown
`colors[:]` is a copy (a weird but, sigh, idiomatic way to spell `list(colors)`) so it doesn't get affected by the `.remove` calls.
Alex Martelli
The only reason to call it more idiomatic is because the stdlib copy module documentation references it. Despite that, I would still use list(otherlist) for copies (or possibly copy.copy(otherthing))
Devin Jeanpierre
+1  A: 

You could use filter function:

>>> colors=['red', 'green', 'blue', 'purple']
>>> filter(lambda color: color != 'green', colors)
['red', 'blue', 'purple']
>>>
del-boy
A: 

or you also can do like this

>>> colors = ['red', 'green', 'blue', 'purple']
>>> if colors.__contains__('green'):
...     colors.remove('green')
There is no advantage in using `.__contains__()` over `'green' in colors`
Roberto Bonvallet
Plus, colors.remove() only removed the *first* occurrence instead of all the occurrences.
EOL
The solution could be made to work, via: while 'green' in colors: colors.remove('green') . Of course, this is O(n**2), while the better solutions are O(n).
Devin Jeanpierre
yes,you are correct Devin..