A quick way to return list without a specific element in Python

Question:

If I have a list of card suits in arbitrary order like so:

suits = ["h", "c", "d", "s"]

and I want to return a list without the 'c'

noclubs = ["h", "d", "s"]

is there a simple way to do this?

Asked By: fox

||

Answers:

suits = ["h","c", "d", "s"]

noclubs = [x for x in suits if x != "c"]
Answered By: avasal
>>> suits = ["h","c", "d", "s"]
>>> noclubs = list(suits)
>>> noclubs.remove("c")
>>> noclubs
['h', 'd', 's']

If you don’t need a seperate noclubs

>>> suits = ["h","c", "d", "s"]
>>> suits.remove("c")
Answered By: jamylak

If it is important that you want to remove a specific element (as opposed to just filtering), you’ll want something close to the following:

noclubs = [x for i, x in enumerate(suits) if i != suits.index('c')]

You may also consider using a set here to be more semantically correct if indeed your problem is related to playing cards.

Answered By: Ceasar Bautista

If order doesn’t matter, a set operation can be used:

suits = ["h", "c", "d", "s"]
noclubs = list(set(suits) - set(["c"]))
# note no order guarantee, the following is the result here:
# noclubs -> ['h', 's', 'd']
Answered By: user166390

you can use filter (or ifilter from itertools)

suits = ["h","c", "d", "s"]
noclubs = filter(lambda i: i!='c', suits)

you can also filter using list construct

suits = ["h","c", "d", "s"]
noclubs = [ i for i in suits if i!='c' ]
Answered By: Muayyad Alsadi

This question has been answered but I wanted to address the comment that using list comprehension is much slower than using .remove().

Some profiles from my machine (notebook using Python 3.6.9).

x = ['a', 'b', 'c', 'd']

%%timeit
y = x[:]  # fastest way to copy
y.remove('c')

1000000 loops, best of 3: 203 ns per loop

%%timeit
y = list(x)  # not as fast copy
y.remove('c')

1000000 loops, best of 3: 274 ns per loop

%%timeit
y = [n for n in x if n != 'c']  # list comprehension

1000000 loops, best of 3: 362 ns per loop

%%timeit
i = x.index('c')
y = x[:i] + x[i + 1:]

1000000 loops, best of 3: 375 ns per loop

If you use the fastest way to copy a list (which isn’t very readable), you will be about 45% faster than using list comprehension. But if you copy the list by using the list() class (which is much more common and Pythonic), then you’re going to be 25% slower than using list comprehension.

Really, it’s all pretty fast. I think the argument could be made that .remove() is more readable than list a list comprehension technique, but it’s not necessarily faster unless you’re interested in giving up readability in the duplication.

The big advantage of list comprehension in this scenario is that it’s much more succinct (i.e. if you had a function that was to remove an element from a given list for some reason, it could be done in 1 line, whilst the other method would require 3 lines.) There are times in which one-liners can be very handy (although they typically come at the cost of some readability). Additionally, using list comprehension excels in the case when you don’t actually know if the element to be removed is actually in the list to begin with. While .remove() will throw a ValueError, list comprehension will operate as expected.

Answered By: alukach

Without using for loops or lambda functions and preserving order:

suits = ["h","c", "d", "s"]
noclubs = suits[:suits.index("c")]+suits[suits.index("c")+1:]

I’m aware that internally it’ll still use loops, but at least you don’t have to use them externally.

Answered By: ierdna

There doesn’t seem to be anything like this built into Python by default unfortunately.

There are several answers but I though I’d add one using iterators. If changing in place is acceptable, that’s going to be fastest. If you don’t want to change the original and just want to loop over a filtered set, this should be pretty fast:

Implementation:

def without(iterable, remove_indices):
    """
    Returns an iterable for a collection or iterable, which returns all items except the specified indices.
    """
    if not hasattr(remove_indices, '__iter__'):
        remove_indices = {remove_indices}
    else:
        remove_indices = set(remove_indices)
    for k, item in enumerate(iterable):
        if k in remove_indices:
            continue
        yield item

Usage:

li = list(range(5))
without(li, 3)             
# <generator object without at 0x7f6343b7c150>
list(without(li, (0, 2)))  
# [1, 3, 4]
list(without(li, 3))       
# [0, 1, 2, 4]

So it is a generator – you’ll need to call list or something to make it permanent.

If you only ever want to remove a single index, you can of course make it even faster by using k == remove_index instead of a set.

Answered By: Mark

One possibility would be to use filter:

>>> import operator
>>> import functools

>>> suits = ["h", "c", "d", "s"]

>>> # Python 3.x
>>> list(filter(functools.partial(operator.ne, 'c'), suits))
['h', 'd', 's']

>>> # Python 2.x
>>> filter(functools.partial(operator.ne, 'c'), suits)
['h', 'd', 's']

Instead of the partial one could also use the __ne__ method of 'c' here:

>>> list(filter('c'.__ne__, suits))
['h', 'd', 's']

However, the latter approach is not considered very pythonic (normally you shouldn’t use special methods – starting with double underscores – directly) and it could give weird results if the list contains mixed types but it could be a bit faster than the partial approach.

suits = ["h", "c", "d", "s"]*200   # more elements for more stable timings
%timeit list(filter('c'.__ne__, suits))
# 164 µs ± 5.98 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit list(filter(functools.partial(operator.ne, 'c'), suits))
# 337 µs ± 13.3 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit list(filter(lambda x: x != 'c', suits))
# 410 µs ± 13.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit [x for x in suits if x != "c"]
181 µs ± 465 ns per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Python 3.5.2 tested with IPythons magic %timeit command.

Answered By: MSeifert
Categories: questions Tags:
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.