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?


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

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)

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

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

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]

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.