Wie überprüfe ich, ob alle Elemente einer Liste einer Bedingung entsprechen?

208

Ich habe eine Liste bestehend aus etwa 20000 Listen. Ich benutze das 3. Element jeder Liste als Flag. Ich möchte einige Operationen an dieser Liste ausführen, solange mindestens ein Element-Flag 0 ist. Es ist wie folgt:

my_list = [["a", "b", 0], ["c", "d", 0], ["e", "f", 0], .....]

Am Anfang sind alle Flags 0. Ich benutze eine while-Schleife, um zu überprüfen, ob das Flag eines Elements 0 ist:

def check(list_):
    for item in list_:
        if item[2] == 0:
            return True
    return False

Wenn check(my_list)zurückkommt True, arbeite ich weiter an meiner Liste:

while check(my_list):
    for item in my_list:
        if condition:
            item[2] = 1
        else:
            do_sth()

Eigentlich wollte ich ein Element in my_list entfernen, während ich darüber iterierte, aber ich darf keine Elemente entfernen, während ich darüber iteriere.

Die ursprüngliche my_list hatte keine Flags:

my_list = [["a", "b"], ["c", "d"], ["e", "f"], .....]

Da ich beim Durchlaufen keine Elemente entfernen konnte, habe ich diese Flags erfunden. Aber das my_listenthält viele Elemente, und die whileSchleife liest alle in jeder forSchleife und es kostet viel Zeit! Hast du irgendwelche Vorschläge?

alwbtc
quelle
3
Ihre Datenstruktur ist anscheinend nicht ideal für Ihr Problem. Wenn Sie den Kontext etwas genauer erläutern würden, könnten wir vielleicht etwas Passenderes vorschlagen.
Uselpa
Vielleicht könnten Sie die Elemente durch Noneoder []beim Durchlaufen der Liste ersetzen, anstatt sie zu entfernen. Das Überprüfen der gesamten Liste mit 'check ()', das alle Elemente vor jedem Durchlauf der inneren Schleife durchläuft, ist ein sehr langsamer Ansatz.
Martineau

Antworten:

402

Die beste Antwort hier ist die Verwendung all(), die für diese Situation eingebaut ist. Wir kombinieren dies mit einem Generatorausdruck , um das gewünschte Ergebnis sauber und effizient zu erzielen. Beispielsweise:

>>> items = [[1, 2, 0], [1, 2, 0], [1, 2, 0]]
>>> all(flag == 0 for (_, _, flag) in items)
True
>>> items = [[1, 2, 0], [1, 2, 1], [1, 2, 0]]
>>> all(flag == 0 for (_, _, flag) in items)
False

Beachten Sie, dass dies all(flag == 0 for (_, _, flag) in items)direkt gleichbedeutend all(item[2] == 0 for item in items)ist. In diesem Fall ist es nur ein wenig schöner zu lesen.

Und für das Filterbeispiel ein Listenverständnis (natürlich können Sie gegebenenfalls einen Generatorausdruck verwenden):

>>> [x for x in items if x[2] == 0]
[[1, 2, 0], [1, 2, 0]]

Wenn Sie überprüfen möchten, ob mindestens ein Element 0 ist, ist es besser, das besser any()lesbare zu verwenden:

>>> any(flag == 0 for (_, _, flag) in items)
True
Gareth Latty
quelle
Mein Fehler bei der Verwendung von Lambda, Pythons All akzeptiert keine Funktion als erstes Argument wie Haskell et. al. habe ich meine Antwort auch in ein Listenverständnis geändert. :)
Hampus Nilsson
3
@HampusNilsson Ein Listenverständnis ist nicht dasselbe wie ein Generatorausdruck. Wie all()und any()Kurzschluss, wenn zum Beispiel auf Mine der erste Wert ausgewertet wird False, all()wird fehlschlagen und keine weiteren Werte überprüfen, Rückkehr False. Ihr Beispiel wird dasselbe tun, außer dass es zuerst die gesamte Liste der Vergleiche generiert, was viel Verarbeitung für nichts bedeutet.
Gareth Latty
14

Wenn Sie überprüfen möchten, ob ein Element in der Liste eine Bedingung verletzt, verwenden Sie all:

if all([x[2] == 0 for x in lista]):
    # Will run if all elements in the list has x[2] = 0 (use not to invert if necessary)

Verwenden Sie, um alle nicht übereinstimmenden Elemente zu entfernen filter

# Will remove all elements where x[2] is 0
listb = filter(lambda x: x[2] != 0, listb)
Hampus Nilsson
quelle
2
Sie können entfernen [...]in all(...)da er dann einen Generator statt einer Liste erstellen, die spart Ihnen nicht nur zwei Zeichen , sondern auch Speicher und spart Zeit. Bei Verwendung von Generatoren wird jeweils nur ein Element berechnet (frühere Ergebnisse werden gelöscht, da sie nicht mehr verwendet werden). Wenn sich herausstellt False, dass eines davon nicht mehr berechnet wird , beendet der Generator die Berechnung des Restelements.
InQβ
7

Sie könnten itertools so lange verwenden, es wird gestoppt, sobald eine Bedingung erfüllt ist, die Ihre Aussage nicht erfüllt. Die entgegengesetzte Methode wäre in der Zwischenzeit

for x in itertools.takewhile(lambda x: x[2] == 0, list)
    print x
Hedde van der Heide
quelle
0

Eine andere Art zu verwenden itertools.ifilter. Dies überprüft die Wahrhaftigkeit und den Prozess (mit lambda)

Stichprobe-

for x in itertools.ifilter(lambda x: x[2] == 0, my_list):
    print x
Islam
quelle
0

Dieser Weg ist etwas flexibler als die Verwendung von all():

my_list = [[1, 2, 0], [1, 2, 0], [1, 2, 0]]
all_zeros = False if False in [x[2] == 0 for x in my_list] else True
any_zeros = True if True in [x[2] == 0 for x in my_list] else False

oder prägnanter:

all_zeros = not False in [x[2] == 0 for x in my_list]
any_zeros = 0 in [x[2] for x in my_list]
mulllhausen
quelle
Könnten Sie nicht einfach all_zeros = False in [x[2] == 0 for x in my_list]oder sogar 0 in [x[2] for x in my_list]und entsprechend dafür sagen any_zeros? Ich sehe keine bemerkenswerte Verbesserung gegenüber all().
Tripleee
Nein, Ihre Version wird all_zeros = False in [x[2] == 0 for x in my_list]ausgewertet False, während meine ausgewertet wird True. Wenn Sie es ändern, all_zeros = not (False in [x[2] == 0 for x in my_list])entspricht es meinem. Und 0 in [x[2] for x in my_list]wird offensichtlich nur für arbeiten any_zeros. Aber ich mag die Prägnanz Ihrer Idee, deshalb werde ich meine Antwort aktualisieren
Mulllhausen