Come verificare se tutti gli elementi di un elenco corrispondono a una condizione?


208

Ho un elenco composto da circa 20000 elenchi. Uso il 3 ° elemento di ogni lista come una bandiera. Voglio fare alcune operazioni su questo elenco fino a quando almeno un flag di elemento è 0, è come:

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

All'inizio, tutti i flag sono 0. Uso un ciclo while per verificare se almeno un flag di elemento è 0:

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

Se check(my_list)ritorna True, quindi continuo a lavorare sul mio elenco:

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

In realtà, volevo rimuovere un elemento in my_list come ho iterato su di esso, ma non mi è permesso rimuovere gli elementi mentre eseguo iterazioni su di esso.

My_list originale non aveva flag:

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

Dal momento che non riuscivo a rimuovere gli elementi mentre scorrevo su di esso, ho inventato queste bandiere. Ma my_listcontiene molti elementi e whileloop li legge tutti in ogni forloop e richiede molto tempo! Hai qualche suggerimento?


3
Sembra che la struttura dei dati non sia l'ideale per il tuo problema. Se spiegassi un po 'di più il contesto forse potremmo suggerire qualcosa di più appropriato.
uselpa,

Forse potresti sostituire gli elementi con Noneo []mentre fai scorrere l'elenco invece di rimuoverli. Controllare l'intero elenco con 'check () `che scorre su tutti gli elementi prima di ogni passaggio sul ciclo interno è un approccio molto lento.
martineau,

Risposte:


403

La migliore risposta qui è usare all(), che è il presupposto per questa situazione. Combiniamo questo con un'espressione del generatore per produrre il risultato desiderato in modo pulito ed efficiente. Per esempio:

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

Nota che all(flag == 0 for (_, _, flag) in items)è direttamente equivalente a all(item[2] == 0 for item in items), è solo un po 'più bello da leggere in questo caso.

E, per l'esempio di filtro, una comprensione dell'elenco (ovviamente, potresti usare un'espressione del generatore dove appropriato):

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

Se si desidera verificare che almeno un elemento sia 0, l'opzione migliore è utilizzare any()quale è più leggibile:

>>> any(flag == 0 for (_, _, flag) in items)
True

Colpa mia sull'uso di lambda, tutto di Python non accetta una funzione come primo argomento come Haskell et. al., ho cambiato anche la mia risposta a una comprensione della lista. :)
Hampus Nilsson,

3
@HampusNilsson La comprensione di un elenco non è la stessa di un'espressione del generatore. Come all()e any()corto circuito, se, ad esempio, il primo valore sul mio viene valutato False, all()fallirà e non controllerà più valori, ritornando False. Il tuo esempio farà lo stesso, tranne per il fatto che prima genererà l'intero elenco di confronti, il che significa un sacco di elaborazioni per nulla.
Gareth Latty,

14

Se si desidera verificare se un elemento nell'elenco viola una condizione, utilizzare 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)

Per rimuovere tutti gli elementi non corrispondenti, utilizzare filter

# Will remove all elements where x[2] is 0
listb = filter(lambda x: x[2] != 0, listb)

2
È possibile rimuovere [...]in all(...)quanto può quindi creare un generatore invece di una lista, che non solo consente di risparmiare due personaggi, ma anche di risparmiare memoria e il tempo. Usando i generatori, verrà calcolato un solo oggetto alla volta (i risultati precedenti verranno eliminati poiché non più utilizzati) e se uno di essi risulta False, il generatore smetterà di calcolare il resto.
InQβ,

7

Potresti usare il tempo di itertools in questo modo, si fermerà quando viene soddisfatta una condizione che non soddisfa la tua affermazione. Il metodo opposto sarebbe droptime

for x in itertools.takewhile(lambda x: x[2] == 0, list)
    print x

0

Un altro modo di usare itertools.ifilter. Questo verifica la veridicità e il processo (usando lambda)

Campione-

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

0

in questo modo è un po 'più flessibile rispetto all'utilizzo di 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

o più succintamente:

all_zeros = not False in [x[2] == 0 for x in my_list]
any_zeros = 0 in [x[2] for x in my_list]

Non potresti semplicemente dire all_zeros = False in [x[2] == 0 for x in my_list]o anche 0 in [x[2] for x in my_list]e corrispondentemente per any_zeros? Non vedo davvero alcun miglioramento notevole all().
Tripleee

no, la tua versione - all_zeros = False in [x[2] == 0 for x in my_list]valuta False, mentre la mia valuta True. Se lo cambi in all_zeros = not (False in [x[2] == 0 for x in my_list])allora è equivalente al mio. E 0 in [x[2] for x in my_list]ovviamente funzionerà solo per any_zeros. Ma mi piace la succinta della tua idea, quindi aggiornerò la mia risposta
Mulllhausen,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.