wake-up-neo.com

Entfernen Sie die ersten N Elemente, die einer Bedingung in einer Python-Liste entsprechen

Wenn ich eine Funktion matchCondition(x) habe, wie kann ich die ersten n-Elemente in einer Python-Liste entfernen, die dieser Bedingung entsprechen?

Eine Lösung besteht darin, über jedes Element zu iterieren, es zum Löschen zu markieren (z. B. durch Festlegen von None) und dann die Liste mit einem Verständnis zu filtern. Dies erfordert ein zweimaliges Durchlaufen der Liste und mutiert die Daten. Gibt es einen idiomatischeren oder effizienteren Weg, dies zu tun?

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

Einweg mit itertools.filterfalse und 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)

Dann gibt dir list(output):

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

Schreiben Sie einen Generator, der die iterierbare Bedingung, eine Bedingung und einen Betrag, der abfällt, annimmt. Iterieren Sie über die Daten und geben Sie Elemente ein, die die Bedingung nicht erfüllen. Wenn die Bedingung erfüllt ist, erhöhen Sie einen Zähler und geben Sie den Wert nicht zurück. Geben Sie immer Gegenstände, wenn der Zähler den Betrag erreicht hat, den Sie fallen lassen möchten.

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

Dazu ist keine zusätzliche Kopie der Liste erforderlich, die Liste wird nur einmal durchlaufen und die Bedingung wird für jedes Element nur einmal aufgerufen. Wenn Sie nicht die gesamte Liste anzeigen möchten, lassen Sie den Aufruf list für das Ergebnis stehen und iterieren Sie den zurückgegebenen Generator direkt.

29
davidism

Die akzeptierte Antwort war für meinen Geschmack etwas zu magisch. Hier ist einer, bei dem der Fluss hoffentlich etwas klarer zu folgen ist:

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

Es ist der Logik in davidism answer ähnlich, aber anstatt zu überprüfen, ob die Drop-Anzahl bei jedem Schritt überschritten wird, schließen wir nur den Rest der Schleife kurz.

Hinweis: Wenn Sie yield from nicht zur Verfügung haben, ersetzen Sie es einfach durch eine andere for-Schleife über die verbleibenden Elemente in iterator

24
wim

Wenn eine Mutation erforderlich ist:

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

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

Dies kann auf Wunsch leicht in einen Generator umgewandelt werden:

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

Ab Python 3.8 und der Einführung von Zuweisungsausdrücken (PEP 572) (Operator :=) können wir eine Variable innerhalb eines Listenverständnisses verwenden und inkrementieren:

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

Diese:

  • Initialisiert eine Variable total zu 0, die die Anzahl der zuvor übereinstimmenden Vorkommen innerhalb des Listenverständnisses symbolisiert
  • Prüft für jedes Objekt, ob es beides ist:
    • entspricht der Ausschlussbedingung (x < 5)
    • und wenn wir nicht bereits mehr als die Anzahl der Elemente verworfen haben, nach denen wir filtern wollten:
      • inkrementieren von total (total := total + 1) über einen Zuweisungsausdruck
      • und gleichzeitig den neuen Wert von total mit der maximalen Anzahl zu verwerfender Elemente vergleichen (3)
0
Xavier Guihot

Verwenden von Listenverständnissen:

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

Dadurch wird auch die Überprüfung des Zustands gestoppt, nachdem n Elemente durch booleschen Kurzschluss gefunden wurden.

0
tpbarron