merlin2011
merlin2011

Reputation: 75649

Can Python slicing be used to skip one specific element by index?

Suppose I am writing a recursive function where I want to pass a list to a function with a single element missing, as part of a loop. Here is one possible solution:

def Foo(input):
    if len(input) == 0: return
    for j in input:
       t = input[:]
       t.remove(j)
       Foo(t)

Is there a way to abuse the slice operator to pass the list minus the element j without explicitly copying the list and removing the item from it?

Upvotes: 9

Views: 18363

Answers (3)

steveha
steveha

Reputation: 76765

If your lists are small, I recommend using the approach in the answer from @satoru.

If your lists are very large, and you want to avoid the "churn" of creating and deleting list instances, how about using a generator?

import itertools as it
def skip_i(seq, i):
    return it.chain(it.islice(seq, 0, i), it.islice(seq, i+1, None))

This pushes the work of skipping the i'th element down into the C guts of itertools, so this should be faster than writing the equivalent in pure Python.

To do it in pure Python I would suggest writing a generator like this:

def gen_skip_i(seq, i):
    for j, x in enumerate(seq):
        if i != j:
            yield x

EDIT: Here's an improved version of my answer, thanks to @Blckknght in comments below.

import itertools as it
def skip_i(iterable, i):
    itr = iter(iterable)
    return it.chain(it.islice(itr, 0, i), it.islice(itr, 1, None))

This is a big improvement over my original answer. My original answer only worked properly on indexable things like lists, but this will work correctly for any iterable, including iterators! It makes an explicit iterator from the iterable, then (in a "chain") pulls the first i values, and skips only a single value before pulling all remaining values.

Thank you very much @Blckknght!

Upvotes: 5

Valentin Lorentz
Valentin Lorentz

Reputation: 9763

Here is a code equivalent to satoru's, but is faster, as it makes one copy of the list per iteration instead of two:

before = []
after = list_[:]

for x in range(0, len(list_)):
    v = after.pop(0)
    Foo(before + after)
    before.append(v)

(11ms instead of 18ms on my computer, for a list generated with list(range(1000)))

Upvotes: 1

satoru
satoru

Reputation: 33235

What about this?

for i in range(len(list_)):
    Foo(list_[:i] + list_[i+1:])

You are stilling copying items, though you ignore the element at index i while copying.

BTW, you can always try to avoid overriding built-in names like list by appending underscores.

Upvotes: 7

Related Questions