views:

976

answers:

7

Is it possible to delete multiple elements from a list at the same time? If I want to delete elements at index 0 and 2, and try something like del somelist[0], followed by del somelist[2], the second statement will actually delete somelist[3].

I suppose I could always delete the higher numbered elements first but I'm hoping there is a better way.

+4  A: 

If you're deleting multiple non-adjacent items, then what you describe is the best way (and yes, be sure to start from the highest index).

If your items are adjacent, you can use the slice assignment syntax:

a[2:10] = []
Greg Hewgill
You can also say `del a[2:10]` with the same effect.
sth
+6  A: 

prob not the best, but here it goes:

indices = 0, 2
somelist = [i for j, i in enumerate(somelist) if j not in indices]
SilentGhost
I would say that this has a quadratic complexity.
piro
Almost, only if you delete the entire list. it'll be len(indices) * len(somelist). It also creates a copy, which may or may not be desired
Richard Levasseur
look up is not linear
SilentGhost
if you're checking for a value in a list, it is. the 'in' operator works on the values of a list, whereas it works on the keys of a dict. If i'm wrong, please point me to the pep/reference
Richard Levasseur
the reason that i've chosen tuple for indices was only simplicity of the record. it would be a perfect job for set() giving O(n)
SilentGhost
+1  A: 

As a function:

def multi_delete(list_, *args):
    indexes = sorted(list(args), reverse=True)
    for index in indexes:
        del list_[index]
    return list_

Runs in n log(n) time, which should make it the fastest correct solution yet.

Nikhil Chelliah
The version with args.sort().reverse() is definitely better. It also happens to work with dicts instead of throwing or, worse, silently corrupting.
Roger Pate
sort() is not defined for tuple, you'd have to convert to list first. sort() returns None, so you couldn't use reverse() on it.
SilentGhost
@ R. Pate: I removed the first version for that reason. Thanks.@ SilentGhost: Fixed it.
Nikhil Chelliah
@Nikhil: no you didn't ;)args = list(args)args.sort()args.reverse()but better option would be: args = sorted(args, reverse=True)
SilentGhost
oops, formatting wasn't preserved
SilentGhost
Oh, I see what you meant - you can't chain the methods, which seems silly, even for immutable objects.I don't like changing a variable's type as in "args = list(args)" . I know it's done, but it can be confusing.
Nikhil Chelliah
The list argument overrides the builtin list used inside the method.
iny
Yeah, I'd give this solution a +1 if it actually worked as written (c.f. what @iny said).
Carl Meyer
why did you do `list(args)`, couldn't you use `args` as is?
João Portela
+1  A: 

You can do that way on a dict, not on a list. In a list elements are in sequence. In a dict they depend only on the index.

Simple code just to explain it by doing:

>>> lst = ['a','b','c']
>>> dct = {0: 'a', 1: 'b', 2:'c'}
>>> lst[0]
'a'
>>> dct[0]
'a'
>>> del lst[0]
>>> del dct[0]
>>> lst[0]
'b'
>>> dct[0]
Traceback (most recent call last):
  File "<pyshell#19>", line 1, in <module>
    dct[0]
KeyError: 0
>>> dct[1]
'b'
>>> lst[1]
'c'

A way to "convert" a list in a dict is:

>>> dct = {}
>>> for i in xrange(0,len(lst)): dct[i] = lst[i]

The inverse is:

lst = [dct[i] for i in sorted(dct.keys())]

Anyway I think it's better to start deleting from the higher index as you said.

Andrea Ambu
Does Python guarantee [dct[i] for i in dct] will always use increasing values of i? If so, list(dct.values()) is surely better.
Roger Pate
I was not thinking about that. You're right. There is not guarantee as I read [here][1] that the items will be picked in order, or at least the expected order. I edited. [1]: http://docs.python.org/library/stdtypes.html#dict.items
Andrea Ambu
A: 

I can actually think of two ways to do it:

  1. slice the list like (this deletes the 1st,3rd and 8th elements)

    somelist = somelist[1:2]+somelist[3:7]+somelist[8:]

  2. do that in place, but one at a time:

    somelist.pop(2) somelist.pop(0)

Bartosz Radaczyński
A: 

So, you essentially want to delete multiple elements in one pass? In that case, the position of the next element to delete will be offset by however many were deleted previously.

Our goal is to delete all the vowels, which are precomputed to be indices 1, 4, and 7. Note that its important the to_delete indices are in ascending order, otherwise it won't work.

to_delete = [1, 4, 7]
target = list("hello world")
for offset, index in enumerate(to_delete):
  index -= offset
  del target[index]

It'd be a more complicated if you wanted to delete the elements in any order. IMO, sorting to_delete might be easier than figuring out when you should or shouldn't subtract from index.

Richard Levasseur
A: 

As a specialisation of Greg's answer, you can even use extended slice syntax. eg. If you wanted to delete items 0 and 2:

>>> a= [0, 1, 2, 3, 4]
>>> del a[0:3:2]
>>> a
[1, 3, 4]

This doesn't cover any arbitrary selection, of course, but it can certainly work for deleting any two items.

bobince