Acorn
Acorn

Reputation: 50497

Remove element, and n following elements from list

I want to remove all elements from a list I want to iterate over a list, skipping elements that match some test, and a certain number of elements after that match. eg.

# skip 'foo' and 2 subsequent values

values = [1, 2, 3, 'foo', 'a', 'b', 6, 7, 'foo', 'x', 'y']

result = [1, 2, 3, 6, 7]

Is there a more elegant method to achieve this than iterating using a counter building a new list and skipping forwards n iteratations when the match is found? ie.

values = [1, 2, 3, 'foo', 'a', 'b', 6, 7, 'foo', 'x', 'y']

result = []

i = 0
while i < len(values):
    if values[i] == 'foo':
        i += 3
    else:
        result.append(values[i])
        i += 1

print result
[1, 2, 3, 6, 7]

Upvotes: 0

Views: 189

Answers (7)

John Machin
John Machin

Reputation: 82934

Depends on your definition of elegant, and whether you want to do what your question title says (remove from a list i.e. not making a new list).

The first function below safely mutates the existing list by iterating backwards and deleting the unwanted stuff. The second function iterates forwards using list.index until the marker is not found (IOW what Ignacio's answer suggested). The third function is a modified version of the first, assuming that the question is taken literally e.g. ['foo', 'foo', 1, 2] is reduced to [], not [2].

Code:

def inplace_munge_1(alist, query, size):
    for i in xrange(len(alist) - 1, -1, -1):
        if alist[i] == query:
            del alist[i:i+size]

def inplace_munge_2(alist, query, size):
    start = 0
    while True:
        try:
            i = alist.index(query, start)
        except ValueError:
            return
        del alist[i:i+size]
        start = i

def inplace_munge_3(alist, query, size):
    marker = len(alist)
    delpos = []
    for i in xrange(len(alist) - 1, -1, -1):
        if alist[i] == query:
            for j in xrange(min(i + size, marker) - 1, i - 1, -1):
                delpos.append(j)
            marker = i
    for j in delpos:
        del alist[j]

funcs = [inplace_munge_1, inplace_munge_2, inplace_munge_3]

tests = [
    [],
    [1],
    ['foo'],
    [1, 2, 3, 'foo', 'a', 'b', 6, 7, 'foo', 'x', 'y'],
    ['foo', 'foo', 1, 2, 3],
    ]

fmt = "%-15s: %r"    
for test in tests:
    print
    print fmt % ("Input", test)
    for func in funcs:
        values = test[:]
        func(values, 'foo', 3)
        print fmt % (func.__name__, values)

Output:

Input          : []
inplace_munge_1: []
inplace_munge_2: []
inplace_munge_3: []

Input          : [1]
inplace_munge_1: [1]
inplace_munge_2: [1]
inplace_munge_3: [1]

Input          : ['foo']
inplace_munge_1: []
inplace_munge_2: []
inplace_munge_3: []

Input          : [1, 2, 3, 'foo', 'a', 'b', 6, 7, 'foo', 'x', 'y']
inplace_munge_1: [1, 2, 3, 6, 7]
inplace_munge_2: [1, 2, 3, 6, 7]
inplace_munge_3: [1, 2, 3, 6, 7]

Input          : ['foo', 'foo', 1, 2, 3]
inplace_munge_1: []
inplace_munge_2: [2, 3]
inplace_munge_3: [3]

Upvotes: 1

user903589
user903589

Reputation:

Functional version:

It's a bit messy, though.

def unfold(f, x):
    while True:
        try:
            w, x = f(x)
        except TypeError:
            raise StopIteration
        yield w

def test(info):
    values, cur_values, n = info
    length = len(values)

    if n == length:
        return None
    elif n == length-1:
        cur_values = cur_values + [values[n]]
    elif values[n] == "foo" and n < len(values)-2:
        n += 3

    return (cur_values, (values, cur_values + [values[n]], n+1))

values = [1, 2, 3, 'a', 'b', 6, 7, 'foo', 'x', 'y', 2 , 6 , 7, "foo", 4 , 5, 6, 7]
results = list(unfold(test, (values, [], 0)))[-1]
print results

Output: [1, 2, 3, 'a', 'b', 6, 7, 2, 6, 7, 6, 7]

Upvotes: 0

Sergey Aganezov jr
Sergey Aganezov jr

Reputation: 675

Write a simple function to work with del slices of the list:

import copy
def del_sublists(list, value, length, copy_list = False):
    if copy_list:
        list = copy.deepcopy(list)
    while value in list:
        del list[list.index(value):list.index(value) + (length + 1)]
    return list

a = [1, 2, 3, 'foo', 'a', 'b', 6, 7, 'foo', 'x', 'y']
print del_sublists(a, 'foo', 2)
print a

output:

[1, 2, 3, 6, 7]
[1, 2, 3, 6, 7]

and same but not changing the variable:

a = [1, 2, 3, 'foo', 'a', 'b', 6, 7, 'foo', 'x', 'y']
print del_sublists(a, 'foo', 2, copy_list = True)
print a

output:

[1, 2, 3, 6, 7]
[1, 2, 3, 'foo', 'a', 'b', 6, 7, 'foo', 'x', 'y']

Upvotes: 1

Acorn
Acorn

Reputation: 50497

Hmm, how about a generator?

def iterskip(iterator, test, n):
    """Iterate skipping values matching test, and n following values"""
    iterator = iter(iterator)
    while 1:
        value = next(iterator)
        if test(value):
            for dummy in range(n):
                next(iterator)
        else:
            yield value

def is_foo(value):
    return value == 'foo'

print list(iterskip(values, is_foo, 2))

Upvotes: 2

Ignacio Vazquez-Abrams
Ignacio Vazquez-Abrams

Reputation: 798606

And now, a coroutine solution.

def countdown(val, count):
  curr = 0
  while True:
    now = (yield curr)
    if curr > 0:
      curr -= 1
    if now == val:
      curr = count

values = [1, 2, 3, 'foo', 'a', 'b', 6, 7, 'foo', 'x', 'y']
c = countdown('foo', 3)
c.next()
print [x for x in values if not c.send(x)]

Upvotes: 1

juliomalegria
juliomalegria

Reputation: 24921

A good solution using a defined function:

def special_remove(my_list, item, start=0):
    try:
        pos = my_list.index(item, start)
        return special_remove(my_list[:pos] + my_list[pos+3:], item, pos)
    except ValueError:
        return my_list

And using the function with your data:

>>> values = [1, 2, 3, 'foo', 'a', 'b', 6, 7, 'foo', 'x', 'y']
>>> special_remove(values, 'foo') [1, 2, 3, 6, 7]

Good thing about this code is that it won't fail even if you want to remove out-of-range elements, for example:

>>> values = [1, 'foo']
>>> special_remove(values, 'foo')
[1]

Upvotes: 0

Ignacio Vazquez-Abrams
Ignacio Vazquez-Abrams

Reputation: 798606

Just slice-delete.

>>> values = [1, 2, 3, 'foo', 'a', 'b', 6, 7, 'foo', 'x', 'y']
>>> values.index('foo')
3
>>> del values[3:3 + 3]
>>> values.index('foo')
5
>>> del values[5:5 + 3]
>>> values.index('foo')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: 'foo' is not in list
>>> values
[1, 2, 3, 6, 7]

Upvotes: 1

Related Questions