Comment vérifier si tous les éléments d'une liste correspondent à une condition?

208

J'ai une liste composée de 20000 listes. J'utilise le troisième élément de chaque liste comme indicateur. Je veux faire quelques opérations sur cette liste tant que le drapeau d'au moins un élément est 0, c'est comme:

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

Au début, tous les drapeaux sont à 0. J'utilise une boucle while pour vérifier si au moins le drapeau d'un élément est 0:

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

Si je check(my_list)reviens True, je continue à travailler sur ma liste:

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

En fait, je voulais supprimer un élément de ma_liste au fur et à mesure que je le répétais, mais je ne suis pas autorisé à supprimer des éléments au fur et à mesure que je le répète.

Ma liste d'origine n'avait pas de drapeaux:

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

Étant donné que je ne pouvais pas supprimer des éléments pendant que je le répétais, j'ai inventé ces drapeaux. Mais le my_listcontient de nombreux éléments, et la whileboucle les lit tous à chaque forboucle, et cela prend beaucoup de temps! Avez-vous des suggestions?

alwbtc
la source
3
Il semble que votre structure de données ne soit pas idéale pour votre problème. Si vous expliquez un peu plus le contexte, nous pourrions peut-être suggérer quelque chose de plus approprié.
uselpa
Vous pouvez peut-être remplacer les éléments par Noneou au []fur et à mesure que vous parcourez la liste au lieu de les supprimer. Vérifier toute la liste avec «check ()» en itérant sur tous les éléments avant chaque passage sur la boucle intérieure est une approche très lente.
martineau

Réponses:

403

La meilleure réponse ici est d'utiliser all(), qui est la fonction intégrée à cette situation. Nous combinons cela avec une expression de générateur pour produire le résultat souhaité de manière propre et efficace. Par exemple:

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

Notez que all(flag == 0 for (_, _, flag) in items)c'est directement équivalent à all(item[2] == 0 for item in items), c'est juste un peu plus agréable à lire dans ce cas.

Et, pour l'exemple de filtre, une compréhension de liste (bien sûr, vous pouvez utiliser une expression de générateur le cas échéant):

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

Si vous voulez vérifier qu'au moins un élément est 0, la meilleure option est d'utiliser any()ce qui est plus lisible:

>>> any(flag == 0 for (_, _, flag) in items)
True
Gareth Latty
la source
Ma faute à l'utilisation de lambda, Python's all n'accepte pas une fonction comme premier argument comme Haskell et. al., j'ai également changé ma réponse en une liste de compréhension. :)
Hampus Nilsson
3
@HampusNilsson Une compréhension de liste n'est pas la même chose qu'une expression de générateur. Au fur all()et à mesure des any()courts-circuits, si, par exemple, la première valeur sur la mienne est évaluée False, all()échouera et ne vérifiera plus de valeurs, en revenant False. Votre exemple fera de même, sauf qu'il générera d'abord la liste complète des comparaisons, ce qui signifie beaucoup de traitement pour rien.
Gareth Latty
14

Si vous souhaitez vérifier si un élément de la liste ne respecte pas une condition, utilisez 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)

Pour supprimer tous les éléments qui ne correspondent pas, utilisez filter

# Will remove all elements where x[2] is 0
listb = filter(lambda x: x[2] != 0, listb)
Hampus Nilsson
la source
2
Vous pouvez supprimer [...]en all(...)car il peut alors créer un générateur au lieu d'une liste, non seulement vous permet d' économiser deux personnages , mais enregistre également la mémoire et le temps. En utilisant des générateurs, un seul élément sera calculé à la fois (les anciens résultats seront supprimés car ils ne sont plus utilisés) et si l'un d'eux s'avère False, le générateur cessera de calculer le reste.
InQβ
7

Vous pouvez utiliser le protocole itertools comme ceci, il s'arrêtera dès qu'une condition est remplie qui échoue à votre déclaration. La méthode inverse serait abandonnée

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

Une autre façon d'utiliser itertools.ifilter. Cela vérifie la véracité et le processus (en utilisant lambda)

Échantillon-

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

cette façon est un peu plus flexible que d'utiliser 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 plus succinctement:

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
la source
Ne pourriez-vous pas simplement dire all_zeros = False in [x[2] == 0 for x in my_list]ou même 0 in [x[2] for x in my_list]et en conséquence pour any_zeros? Je ne vois vraiment aucune amélioration remarquable all().
tripleee
non, votre version est all_zeros = False in [x[2] == 0 for x in my_list]évaluée False, tandis que la mienne est évaluée True. Si vous le changez en, all_zeros = not (False in [x[2] == 0 for x in my_list])il est équivalent au mien. Et 0 in [x[2] for x in my_list]ne va évidemment que travailler pour any_zeros. Mais j'aime la brièveté de votre idée, donc je
mettrai à