Удалите первые N элементов, которые соответствуют условию в списке Python


если у меня есть функция matchCondition(x), Как я могу удалить первый n элементы в списке Python, соответствующие этому условию?

одним из решений является итерация по каждому элементу, отметьте его для удаления (например, установив его в None), а затем фильтровать список с пониманием. Это требует повторения списка дважды и изменяет данные. Есть ли более идиоматические или эффективный способ сделать это?

n = 3

def condition(x):
    return x < 5

data = [1, 10, 2, 9, 3, 8, 4, 7]
out = do_remove(data, n, condition)
print(out)  # [10, 9, 8, 4, 7] (1, 2, and 3 are removed, 4 remains)
6 59

6 ответов:

один из способов, используя itertools.filterfalse и itertools.count:

from itertools import count, filterfalse

data = [1, 10, 2, 9, 3, 8, 4, 7]
output = filterfalse(lambda L, c=count(): L < 5 and next(c) < 3, data)

затем list(output), дает вам:

[10, 9, 8, 4, 7]

напишите генератор, который принимает итерацию, условие и сумму для удаления. Повторите данные и получите элементы, которые не соответствуют условию. Если условие выполнено, увеличьте счетчик и не выдавайте значение. Всегда отдавайте предметы, как только счетчик достигнет суммы, которую вы хотите сбросить.

def iter_drop_n(data, condition, drop):
    dropped = 0

    for item in data:
        if dropped >= drop:
            yield item
            continue

        if condition(item):
            dropped += 1
            continue

        yield item

data = [1, 10, 2, 9, 3, 8, 4, 7]
out = list(iter_drop_n(data, lambda x: x < 5, 3))

это не требует дополнительной копии списка, только повторяет список один раз, и только вызывает условие один раз для каждого элемента. Если вы действительно хотите видеть весь список, оставьте list вызовите результат и выполните итерацию по возвращенному генератору напрямую.

принятый ответ был слишком волшебным на мой вкус. Вот один, где поток, надеюсь, немного яснее, чтобы следовать:

def matchCondition(x):
    return x < 5


def my_gen(L, drop_condition, max_drops=3):
    count = 0
    iterator = iter(L)
    for element in iterator:
        if drop_condition(element):
            count += 1
            if count >= max_drops:
                break
        else:
            yield element
    yield from iterator


example = [1, 10, 2, 9, 3, 8, 4, 7]

print(list(my_gen(example, drop_condition=matchCondition)))

Это похоже на логику в davidism ответ, но вместо того, чтобы проверять количество падений превышается на каждом шаге, мы просто закорачиваем остальную часть цикла.

Примечание: если у вас нет yield from доступный, как раз замените его с другим для петли над остальными деталями внутри iterator.

если требуется мутация:

def do_remove(ls, N, predicate):
    i, delete_count, l = 0, 0, len(ls)
    while i < l and delete_count < N:
        if predicate(ls[i]):
           ls.pop(i) # remove item at i
           delete_count, l = delete_count + 1, l - 1 
        else:
           i += 1
    return ls # for convenience

assert(do_remove(l, N, matchCondition) == [10, 9, 8, 4, 7])

Простой Python:

N = 3
data = [1, 10, 2, 9, 3, 8, 4, 7]

def matchCondition(x):
    return x < 5

c = 1
l = []
for x in data:
    if c > N or not matchCondition(x):
        l.append(x)
    else:
        c += 1

print(l)

Это можно легко превратить в генератор при желании:

def filter_first(n, func, iterable):
    c = 1
    for x in iterable:
        if c > n or not func(x):
            yield x
        else:
            c += 1

print(list(filter_first(N, matchCondition, data)))

использовать список осмысленностей:

n = 3
data = [1, 10, 2, 9, 3, 8, 4, 7]
count = 0
def counter(x):
    global count
    count += 1
    return x

def condition(x):
    return x < 5

filtered = [counter(x) for x in data if count < n and condition(x)]

это также прекратит проверку состояния после n элементы найдены благодаря логическому короткому замыканию.