web-dev-qa-db-fra.com

Supprimer les premiers N éléments correspondant à une condition dans une liste Python

Si j'ai une fonction matchCondition(x), comment puis-je supprimer les premiers éléments n d'une liste Python qui correspondent à cette condition?

Une solution consiste à parcourir chaque élément, à le marquer pour suppression (par exemple, en le définissant sur None), puis à filtrer la liste avec une compréhension. Cela nécessite de parcourir deux fois la liste et de transformer les données. Existe-t-il un moyen plus idiomatique ou plus efficace de procéder?

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)
60
Thomas Johnson

Une manière utilisant itertools.filterfalse et 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)

Alors list(output), vous donne:

[10, 9, 8, 4, 7]
63
Jon Clements

Ecrivez un générateur qui prend l'itérable, une condition et un montant à supprimer. Parcourez les données et renvoyez les éléments qui ne répondent pas à la condition. Si la condition est remplie, incrémentez un compteur et ne renvoyez pas la valeur. Cédez toujours les objets une fois que le compteur atteint le montant que vous souhaitez déposer.

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

Cela ne nécessite pas de copie supplémentaire de la liste, il suffit de parcourir la liste une seule fois et d'appeler la condition une seule fois pour chaque élément. À moins que vous ne souhaitiez voir toute la liste, laissez l'appel list sur le résultat et parcourez directement le générateur retourné.

29
davidism

La réponse acceptée était un peu trop magique à mon goût. En voici un où le flux est, espérons-le, un peu plus clair à suivre:

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

Cela ressemble à la logique dans davidism answer, mais au lieu de vérifier que le nombre de gouttes est dépassé à chaque étape, nous ne faisons que court-circuiter le reste de la boucle.

Remarque: Si vous n'avez pas yield from , remplacez-le simplement par une autre boucle for sur les éléments restants dans iterator

24
wim

Si une mutation est requise:

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])
4
ferhat elmas

Python simple:

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)

Cela peut facilement être transformé en générateur si vous le souhaitez:

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)))
2
CoDEmanX

À partir de Python 3.8 et de l'introduction des expressions d'affectation (PEP 572) (opérateur :=), nous pouvons utiliser et incrémenter une variable dans une liste de compréhension:

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

Ce:

  • Initialise une variable total à 0 qui symbolisera le nombre d'occurrences précédemment appariées dans la compréhension de la liste
  • Vérifie chaque article s'il est à la fois:
    • correspond à la condition d'exclusion (x < 5)
    • et si nous n'avons pas encore jeté plus que le nombre d'éléments que nous voulions filtrer par:
      • incrémenter total (total := total + 1) via une expression d'affectation
      • et en même temps en comparant la nouvelle valeur de total au nombre maximal d'éléments à ignorer (3)
0
Xavier Guihot

Utilisation de la compréhension de liste:

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

Cela arrêtera également de vérifier la condition après que n éléments aient été trouvés grâce à un court-circuit booléen.

0
tpbarron