Como verificar se todos os elementos de uma lista correspondem a uma condição?

208

Eu tenho uma lista que consiste em como 20000 listas. Eu uso o terceiro elemento de cada lista como uma bandeira. Quero fazer algumas operações nesta lista, desde que pelo menos o sinalizador de um elemento seja 0, é como:

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

No começo, todos os sinalizadores são 0. Uso um loop while para verificar se pelo menos o sinalizador de um elemento é 0:

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

Se check(my_list)retornar True, continuarei trabalhando na minha lista:

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

Na verdade, eu queria remover um elemento em my_list enquanto iterava sobre ele, mas não tenho permissão para remover itens enquanto iteramos sobre ele.

My_list original não tinha sinalizadores:

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

Como não consegui remover elementos enquanto iterava sobre ele, inventei esses sinalizadores. Mas o my_listcontém muitos itens, e o whileloop lê todos eles em cada forloop e consome muito tempo! Você tem alguma sugestão?

alwbtc
fonte
3
Parece que sua estrutura de dados não é ideal para o seu problema. Se você explicou um pouco mais o contexto, talvez possamos sugerir algo mais apropriado.
Uselpa
Talvez você possa substituir os itens por Noneou à []medida que itera na lista, em vez de removê-los. Verificar a lista inteira com 'check () `repetindo todos os itens antes de cada passagem no loop interno é uma abordagem muito lenta.
22419 martineau

Respostas:

402

A melhor resposta aqui é usar all(), que é a base para esta situação. Combinamos isso com uma expressão de gerador para produzir o resultado desejado de forma limpa e eficiente. Por exemplo:

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

Observe que all(flag == 0 for (_, _, flag) in items)é diretamente equivalente a all(item[2] == 0 for item in items), é apenas um pouco mais agradável de ler neste caso.

E, para o exemplo de filtro, uma compreensão da lista (é claro, você pode usar uma expressão geradora, quando apropriado):

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

Se você deseja verificar pelo menos um elemento é 0, a melhor opção é usar o any()que for mais legível:

>>> any(flag == 0 for (_, _, flag) in items)
True
Gareth Latty
fonte
Minha culpa no uso de lambda, tudo do Python, não aceita uma função como o primeiro argumento como Haskell et. al., mudei minha resposta para uma compreensão de lista também. :)
Hampus Nilsson
3
@HampusNilsson Uma compreensão de lista não é a mesma que uma expressão de gerador. Como all()e any()curto-circuito, se, por exemplo, o primeiro valor na mina for avaliado False, all()falhará e não verificará mais nenhum valor, retornando False. Seu exemplo fará o mesmo, exceto que ele gerará a lista inteira de comparações primeiro, o que significa muito processamento para nada.
Gareth Latty
14

Se você deseja verificar se algum item da lista viola uma condição, use 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 remover todos os elementos que não correspondem, use filter

# Will remove all elements where x[2] is 0
listb = filter(lambda x: x[2] != 0, listb)
Hampus Nilsson
fonte
2
Você pode remover [...], all(...)pois ele pode criar um gerador em vez de uma lista, que não apenas economiza dois caracteres, mas também economiza tempo e memória. Ao usar geradores, apenas um item será calculado por vez (os resultados anteriores serão descartados porque não são mais usados) e, se algum deles ocorrer False, o gerador interromperá o cálculo do restante.
InQβ
7

Você pode usar as ferramentas do itertools desse jeito, ele será interrompido assim que uma condição for atendida e falhar na sua declaração. O método oposto seria suspenso

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

Outra maneira de usar itertools.ifilter. Isso verifica a veracidade e o processo (usandolambda )

Amostra-

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

dessa maneira é um pouco mais flexível do 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

ou mais 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
fonte
Você não poderia simplesmente dizer all_zeros = False in [x[2] == 0 for x in my_list]ou mesmo 0 in [x[2] for x in my_list]e correspondentemente any_zeros? Realmente não vejo nenhuma melhoria notável all().
Tripleee 02/07/19
não, sua versão - all_zeros = False in [x[2] == 0 for x in my_list]avalia para False, enquanto a minha avalia para True. Se você mudar para, all_zeros = not (False in [x[2] == 0 for x in my_list])então é equivalente ao meu. E 0 in [x[2] for x in my_list]obviamente só vai funcionar any_zeros. Mas eu gosto da concisão da sua ideia, por isso vou atualizar a minha resposta
mulllhausen