Reputation: 50497
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
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
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
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
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
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
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
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