Удалите первые 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)

Ответ 1

Один из способов: 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]

Ответ 2

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

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 на результат и перейдем к возвращенному генератору напрямую.

Ответ 3

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

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.

Ответ 4

Если необходима мутация:

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])

Ответ 5

Простой 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)))

Ответ 6

Использование списков:

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 элементов будут найдены благодаря булевому короткому замыканию.

Ответ 7

Начиная с Python 3.8 и введением выражений присваивания (PEP 572) (:= оператор), мы можем использовать и увеличивать переменную в пределах понимания списка:

# items = [1, 10, 2, 9, 3, 8, 4, 7]
total = 0
[x for x in items if not (x < 5 and (total := total + 1) <= 3)]
# [10, 9, 8, 4, 7]

Это:

  • Инициализирует переменную total до 0 которая будет символизировать количество ранее сопоставленных вхождений в пределах понимания списка
  • Проверяет каждый элемент, если он оба:
    • соответствует условию исключения (x < 5)
    • и если мы еще не отбросили больше, чем количество элементов, по которым мы хотели бы отфильтровать:
      • увеличение total (total := total + 1) с помощью выражения присваивания
      • и в то же время сравнивая новое total значение с максимальным количеством предметов для отбрасывания (3)