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)
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]
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.
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
.
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])
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)))
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:
total
zu 0
, die die Anzahl der zuvor übereinstimmenden Vorkommen innerhalb des Listenverständnisses symbolisiertx < 5
)total
(total := total + 1
) über einen Zuweisungsausdruck total
mit der maximalen Anzahl zu verwerfender Elemente vergleichen (3
)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.