¿Cómo verificar si todos los elementos de una lista coinciden con una condición?

208

Tengo una lista que consta de como 20000 listas. Yo uso el tercer elemento de cada lista como una bandera. Quiero hacer algunas operaciones en esta lista siempre que al menos el indicador de un elemento sea 0, es como:

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

Al principio, todas las banderas son 0. Uso un ciclo while para verificar si al menos la bandera de un elemento es 0:

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

Si check(my_list)regresa True, entonces sigo trabajando en mi lista:

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

En realidad, quería eliminar un elemento en my_list mientras iteraba sobre él, pero no se me permite eliminar elementos mientras itero sobre él.

My_list original no tenía banderas:

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

Como no podía eliminar elementos mientras lo repetía, inventé estas banderas. Pero my_listcontiene muchos elementos, y el whileciclo los lee todos en cada forciclo, ¡y consume mucho tiempo! ¿Tienes alguna sugerencia?

alwbtc
fuente
3
Parece que su estructura de datos no es ideal para su problema. Si explicaste el contexto un poco más, tal vez podríamos sugerirte algo más apropiado.
uselpa
Tal vez podría reemplazar los elementos con Noneo []mientras itera sobre la lista en lugar de eliminarlos. Verificar la lista completa con 'check ()' iterando sobre todos los elementos antes de cada pasada en el bucle interno es un enfoque muy lento.
Martineau

Respuestas:

403

La mejor respuesta aquí es usar all(), que es el incorporado para esta situación. Combinamos esto con una expresión generadora para producir el resultado que desea de forma limpia y eficiente. Por ejemplo:

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

Tenga en cuenta que all(flag == 0 for (_, _, flag) in items)es directamente equivalente a all(item[2] == 0 for item in items), es un poco más agradable de leer en este caso.

Y, para el ejemplo de filtro, una comprensión de la lista (por supuesto, podría usar una expresión generadora cuando corresponda):

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

Si desea verificar que al menos un elemento es 0, la mejor opción es usar el any()que sea más legible:

>>> any(flag == 0 for (_, _, flag) in items)
True
Gareth Latty
fuente
Mi culpa sobre el uso de lambda, Python no acepta una función como primer argumento como Haskell et. al., cambié mi respuesta a una lista de comprensión también. :)
Hampus Nilsson
3
@HampusNilsson Una comprensión de lista no es lo mismo que una expresión generadora. Como all()y any()cortocircuito, si, por ejemplo, el primer valor en la mina evalúa False, all()fallará y no verificará más valores, regresando False. Su ejemplo hará lo mismo, excepto que generará la lista completa de comparaciones primero, lo que significa mucho procesamiento para nada.
Gareth Latty
14

Si desea verificar si algún elemento de la lista infringe una condición, utilice 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)

Para eliminar todos los elementos que no coinciden, use filter

# Will remove all elements where x[2] is 0
listb = filter(lambda x: x[2] != 0, listb)
Hampus Nilsson
fuente
2
Se puede quitar [...]en el all(...)puesto que se puede crear un generador en lugar de una lista, que no sólo le ahorra dos personajes, sino que también ahorra memoria y el tiempo. Al usar generadores, solo se calculará un elemento a la vez (los resultados anteriores se eliminarán porque ya no se usan) y si alguno de ellos resulta False, el generador dejará de calcular el resto.
InQβ
7

Puede usar el tiempo de itertools de esta manera, se detendrá una vez que se cumpla una condición que falle su declaración. El método opuesto sería droptime

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

Otra forma de usar itertools.ifilter. Esto verifica la veracidad y el proceso (usandolambda )

Muestra-

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

de esta manera es un poco más flexible que usar 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 más sucintamente:

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
fuente
¿No podrías simplemente decir all_zeros = False in [x[2] == 0 for x in my_list] o incluso 0 in [x[2] for x in my_list]y correspondientemente para any_zeros? Realmente no veo ninguna mejora notable all().
tripleee
no, tu versión se all_zeros = False in [x[2] == 0 for x in my_list]evalúa como False, mientras que la mía se evalúa como True. Si lo cambia a, all_zeros = not (False in [x[2] == 0 for x in my_list])entonces es equivalente al mío. Y 0 in [x[2] for x in my_list]obviamente solo va a funcionar any_zeros. Pero me gusta la concisión de su idea, así que voy a actualizar mi respuesta
mulllhausen