Jak sprawdzić, czy wszystkie elementy listy spełniają warunek?

208

Mam listę składającą się z około 20000 list. Używam trzeciego elementu każdej listy jako flagi. Chcę wykonać kilka operacji na tej liście, o ile flaga przynajmniej jednego elementu ma wartość 0, to jest tak:

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

Na początku wszystkie flagi mają wartość 0. Używam pętli while, aby sprawdzić, czy flaga przynajmniej jednego elementu ma wartość 0:

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

Jeśli check(my_list)wróci True, kontynuuję pracę nad moją listą:

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

Właściwie chciałem usunąć element z mojej_listy podczas iteracji, ale nie mogę usuwać elementów podczas iteracji.

Oryginalna moja lista nie miała flag:

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

Ponieważ nie mogłem usunąć elementów podczas iteracji, wymyśliłem te flagi. Ale my_listzawiera wiele elementów, a whilepętla odczytuje je wszystkie w każdej forpętli i zajmuje dużo czasu! Masz jakieś sugestie?

alwbtc
źródło
3
Wygląda na to, że struktura danych nie jest idealna dla Twojego problemu. Jeśli wyjaśnisz nieco kontekst, możemy zaproponować coś bardziej odpowiedniego.
uselpa
Być może możesz zamienić elementy na Nonelub []podczas iteracji na liście zamiast je usuwać. Sprawdzanie całej listy za pomocą „check ()” iterującego wszystkie elementy przed każdym przejściem w wewnętrznej pętli jest bardzo powolnym podejściem.
martineau,

Odpowiedzi:

402

Najlepszą odpowiedzią jest użycie all()wbudowanego w tej sytuacji. Łączymy to z wyrażeniem generatora, aby uzyskać pożądany efekt w sposób czysty i wydajny. Na przykład:

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

Zauważ, że all(flag == 0 for (_, _, flag) in items)jest to bezpośrednio równoważne all(item[2] == 0 for item in items), w tym przypadku jest tylko trochę przyjemniej czytać.

I, na przykład filtru, zrozumienie listy (oczywiście, w razie potrzeby można użyć wyrażenia generatora):

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

Jeśli chcesz sprawdzić, że przynajmniej jeden element ma wartość 0, lepszą opcją jest użycie any()bardziej czytelnego:

>>> any(flag == 0 for (_, _, flag) in items)
True
Gareth Latty
źródło
Moja wina dotycząca użycia lambda, Python nie przyjmuje funkcji jako pierwszego argumentu, takiego jak Haskell i in. al.. Zmieniłem również swoją odpowiedź na zrozumienie listy. :)
Hampus Nilsson
3
@HampusNilsson Zrozumienie listy nie jest tym samym, co wyrażenie generatora. Jak all()i any()zwarcie, jeśli, na przykład, pierwsza wartość na mojej oszacuje do False, all()zawiedzie i nie będzie sprawdzać kolejnych wartości, powracając False. Twój przykład zrobi to samo, tyle że najpierw wygeneruje całą listę porównań, co oznacza dużo przetwarzania za nic.
Gareth Latty
14

Jeśli chcesz sprawdzić, czy jakikolwiek element na liście narusza warunek, użyj 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)

Aby usunąć wszystkie niepasujące elementy, użyj filter

# Will remove all elements where x[2] is 0
listb = filter(lambda x: x[2] != 0, listb)
Hampus Nilsson
źródło
2
Można usunąć [...]w all(...)ponieważ może następnie stworzyć generator zamiast listy, która nie tylko oszczędza dwie postacie, ale także oszczędność czasu i pamięci. Używając generatorów, tylko jeden element będzie obliczany na raz (poprzednie wyniki zostaną upuszczone, ponieważ nie są już używane), a jeśli którykolwiek z nich się okaże False, generator przestanie obliczać resztę.
InQβ
7

Możesz użyć taktu itertools w taki sposób, że zatrzyma się on, gdy zostanie spełniony warunek, który nie powiedzie się w twoim oświadczeniu. Przeciwna metoda byłaby kroplowa

for x in itertools.takewhile(lambda x: x[2] == 0, list)
    print x
Hedde van der Heide
źródło
0

Kolejny sposób użycia itertools.ifilter. To sprawdza prawdziwość i proces (używanie lambda)

Próba-

for x in itertools.ifilter(lambda x: x[2] == 0, my_list):
    print x
SIslam
źródło
0

ten sposób jest nieco bardziej elastyczny niż używanie 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

lub bardziej zwięźle:

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
źródło
Czy nie możesz po prostu powiedzieć, all_zeros = False in [x[2] == 0 for x in my_list]a nawet 0 in [x[2] for x in my_list]i odpowiednio za any_zeros? Tak naprawdę nie widzę żadnej znaczącej poprawy all().
tripleee
nie, twoja wersja - all_zeros = False in [x[2] == 0 for x in my_list]ocenia False, a moja ocenia na True. Jeśli zmienisz na, all_zeros = not (False in [x[2] == 0 for x in my_list])to będzie to odpowiednik mojego. I 0 in [x[2] for x in my_list]oczywiście będzie pracował tylko dla any_zeros. Ale podoba mi się zwięzłość twojego pomysłu, dlatego zaktualizuję swoją odpowiedź
Mulllhausen