Vérifiez si tous les éléments d'une liste sont identiques

390

J'ai besoin de la fonction suivante:

Entrée : alist

Sortie :

  • True si tous les éléments de la liste d'entrée sont évalués comme égaux les uns aux autres à l'aide de l'opérateur d'égalité standard;
  • False autrement.

Performance : bien sûr, je préfère ne pas encourir de frais généraux inutiles.

Je pense qu'il serait préférable de:

  • parcourir la liste
  • comparer les éléments adjacents
  • et ANDtoutes les valeurs booléennes résultantes

Mais je ne sais pas quelle est la façon la plus Pythonique de le faire.


L'absence de fonction de court-circuit ne fait mal que sur une longue entrée (plus de ~ 50 éléments) qui a des éléments inégaux dès le début. Si cela se produit assez souvent (la fréquence dépend de la durée des listes), le court-circuit est nécessaire. Le meilleur algorithme de court-circuit semble être @KennyTM checkEqual1. Il paie cependant un coût important pour cela:

  • jusqu'à 20x dans des listes de performances presque identiques
  • jusqu'à 2,5 fois les performances sur les listes restreintes

Si les entrées longues avec des éléments inégaux précoces ne se produisent pas (ou se produisent suffisamment rarement), un court-circuit n'est pas nécessaire. La solution @Ivo van der Wijk est de loin la plus rapide.

max
la source
3
Égal comme dans a == bou identique à a is b?
kennytm
1
La solution doit-elle gérer des listes vides? Si oui, que faut-il retourner?
Doug
1
Égal comme dans a == b. Devrait gérer une liste vide et retourner True.
max
2
Bien que je sache que c'est plus lent que certaines des autres recommandations, je suis étonné de functools.reduce(operator.eq, a)ne pas avoir été suggéré.
user2846495

Réponses:

420

Méthode générale:

def checkEqual1(iterator):
    iterator = iter(iterator)
    try:
        first = next(iterator)
    except StopIteration:
        return True
    return all(first == rest for rest in iterator)

Bon mot:

def checkEqual2(iterator):
   return len(set(iterator)) <= 1

Également une ligne:

def checkEqual3(lst):
   return lst[1:] == lst[:-1]

La différence entre les 3 versions est que:

  1. Dans checkEqual2le contenu doit être lavable.
  2. checkEqual1et checkEqual2peut utiliser n'importe quel itérateur, mais checkEqual3doit prendre une entrée de séquence, généralement des conteneurs concrets comme une liste ou un tuple.
  3. checkEqual1 s'arrête dès qu'une différence est trouvée.
  4. Puisqu'il checkEqual1contient plus de code Python, il est moins efficace lorsque de nombreux éléments sont égaux au début.
  5. Étant donné que checkEqual2et checkEqual3toujours effectuer des opérations de copie O (N), elles prendront plus de temps si la plupart de vos entrées renvoient False.
  6. Pour checkEqual2et checkEqual3il est plus difficile d'adapter la comparaison de a == bà a is b.

timeit résultat, pour Python 2.7 et (seuls s1, s4, s7, s9 devraient renvoyer True)

s1 = [1] * 5000
s2 = [1] * 4999 + [2]
s3 = [2] + [1]*4999
s4 = [set([9])] * 5000
s5 = [set([9])] * 4999 + [set([10])]
s6 = [set([10])] + [set([9])] * 4999
s7 = [1,1]
s8 = [1,2]
s9 = []

on a

      | checkEqual1 | checkEqual2 | checkEqual3  | checkEqualIvo | checkEqual6502 |
|-----|-------------|-------------|--------------|---------------|----------------|
| s1  | 1.19   msec | 348    usec | 183     usec | 51.6    usec  | 121     usec   |
| s2  | 1.17   msec | 376    usec | 185     usec | 50.9    usec  | 118     usec   |
| s3  | 4.17   usec | 348    usec | 120     usec | 264     usec  | 61.3    usec   |
|     |             |             |              |               |                |
| s4  | 1.73   msec |             | 182     usec | 50.5    usec  | 121     usec   |
| s5  | 1.71   msec |             | 181     usec | 50.6    usec  | 125     usec   |
| s6  | 4.29   usec |             | 122     usec | 423     usec  | 61.1    usec   |
|     |             |             |              |               |                |
| s7  | 3.1    usec | 1.4    usec | 1.24    usec | 0.932   usec  | 1.92    usec   |
| s8  | 4.07   usec | 1.54   usec | 1.28    usec | 0.997   usec  | 1.79    usec   |
| s9  | 5.91   usec | 1.25   usec | 0.749   usec | 0.407   usec  | 0.386   usec   |

Remarque:

# http://stackoverflow.com/q/3844948/
def checkEqualIvo(lst):
    return not lst or lst.count(lst[0]) == len(lst)

# http://stackoverflow.com/q/3844931/
def checkEqual6502(lst):
    return not lst or [lst[0]]*len(lst) == lst
kennytm
la source
1
Merci, c'est une explication très utile des alternatives. Pouvez-vous s'il vous plaît vérifier votre tableau de performances - est-ce que tout est en msec, et les chiffres sont-ils dans les bonnes cellules?
max
7
@max: Oui. Notez que 1 msec = 1000 usec.
kennytm
1
N'oubliez pas l'analyse de l'utilisation de la mémoire pour les très grandes baies, une solution native qui optimise les appels à distance vers obj.__eq__quand lhs is rhs, et des optimisations dans le désordre pour permettre de court-circuiter les listes triées plus rapidement.
Glenn Maynard
3
Ivo van der Wijk a une meilleure solution pour les séquences qui est environ 5 fois plus rapide que set et O (1) en mémoire.
aaronasterling
2
Il y a aussi une itertoolsrecette que j'ai ajoutée comme réponse. Cela vaut peut-être la peine de le jeter dans votre matrice de chronométrage :-).
mgilson
301

Une solution plus rapide que l'utilisation de set () qui fonctionne sur des séquences (et non sur des itérables) consiste à simplement compter le premier élément. Cela suppose que la liste n'est pas vide (mais c'est trivial à vérifier, et décidez vous-même quel devrait être le résultat sur une liste vide)

x.count(x[0]) == len(x)

quelques repères simples:

>>> timeit.timeit('len(set(s1))<=1', 's1=[1]*5000', number=10000)
1.4383411407470703
>>> timeit.timeit('len(set(s1))<=1', 's1=[1]*4999+[2]', number=10000)
1.4765670299530029
>>> timeit.timeit('s1.count(s1[0])==len(s1)', 's1=[1]*5000', number=10000)
0.26274609565734863
>>> timeit.timeit('s1.count(s1[0])==len(s1)', 's1=[1]*4999+[2]', number=10000)
0.25654196739196777
Ivo van der Wijk
la source
5
OMG, c'est 6 fois plus rapide que la solution définie! (280 millions d'éléments / s contre 45 millions d'éléments / s sur mon ordinateur portable). Pourquoi??? Et existe-t-il un moyen de le modifier pour qu'il court-circuite (je suppose que non ...)
max
2
Je suppose que list.count a une implémentation C hautement optimisée, et la longueur de la liste est stockée en interne, donc len () est également bon marché. Il n'y a aucun moyen de court-circuiter count () car vous devrez vraiment vérifier tous les éléments pour obtenir le nombre correct.
Ivo van der Wijk
Puis-je le changer pour: x.count(next(x)) == len(x)afin qu'il fonctionne pour n'importe quel conteneur x? Ahh .. nm, je viens de voir que .count n'est disponible que pour les séquences .. Pourquoi n'est-il pas implémenté pour d'autres conteneurs intégrés? Compter dans un dictionnaire est-il intrinsèquement moins significatif que dans une liste?
max
4
Un itérateur peut ne pas avoir de longueur. Par exemple, il peut être infini ou simplement généré dynamiquement. Vous ne pouvez trouver sa longueur qu'en le convertissant en une liste qui enlève la plupart des avantages des itérateurs
Ivo van der Wijk
Désolé, je voulais dire pourquoi countn'est pas implémenté pour les itérables, pas pourquoi lenn'est pas disponible pour les itérateurs. La réponse est probablement que ce n'est qu'un oubli. Mais c'est inutile pour nous car le défaut .count()pour les séquences est très lent (pur python). La raison pour laquelle votre solution est si rapide est qu'elle repose sur l'implémentation C countfournie par list. Donc, je suppose que ce qui se produit pour implémenter la countméthode en C bénéficiera de votre approche.
max
164

La manière la plus simple et la plus élégante est la suivante:

all(x==myList[0] for x in myList)

(Oui, cela fonctionne même avec la liste vide! C'est parce que c'est l'un des rares cas où python a une sémantique paresseuse.)

En ce qui concerne les performances, cela échouera le plus tôt possible, donc c'est asymptotiquement optimal.

ninjagecko
la source
Cela fonctionne, mais c'est un peu (1,5x) plus lent que @KennyTM checkEqual1. Je ne sais pas pourquoi.
max
4
max: Probablement parce que je n'ai pas pris la peine d'effectuer l'optimisation first=myList[0] all(x==first for x in myList), peut
ninjagecko
Je pense que myList [0] est évalué à chaque itération. >>> timeit.timeit ('tous ([y == x [0] pour y dans x])', 'x = [1] * 4000', nombre = 10000) 2.707076672740641 >>> timeit.timeit ('x0 = x [0]; tous ([y == x0 pour y dans x]) ',' x = [1] * 4000 ', nombre = 10000) 2.0908854261426484
Matt Liberty
1
Je devrais bien sûr clarifier que l'optimisation first=myList[0]mettra un IndexErrorsur une liste vide, donc les commentateurs qui parlaient de cette optimisation que j'ai mentionnée devront faire face au cas de bord d'une liste vide. Cependant l'original est très bien ( x==myList[0]est bien dans le allcar il n'est jamais évalué si la liste est vide).
ninjagecko
1
C'est clairement la bonne façon d'y parvenir. Si vous voulez de la vitesse dans tous les cas, utilisez quelque chose comme numpy.
Henry Gomersall
45

Un travail de comparaison défini:

len(set(the_list)) == 1

L'utilisation setsupprime tous les éléments en double.

cbalawat
la source
26

Vous pouvez convertir la liste en un ensemble. Un ensemble ne peut pas avoir de doublons. Donc, si tous les éléments de la liste d'origine sont identiques, l'ensemble n'aura qu'un seul élément.

if len(sets.Set(input_list)) == 1
// input_list has all identical elements.
codaddict
la source
c'est bien mais ça ne court-circuite pas et il faut calculer la longueur de la liste résultante.
aaronasterling
15
pourquoi pas juste len(set(input_list)) == 1?
Nick Dandoulakis du
2
@codaddict. Cela signifie que même si les deux premiers éléments sont distincts, il terminera toujours la recherche entière. il utilise également O (k) espace supplémentaire où k est le nombre d'éléments distincts dans la liste.
aaronasterling
1
@max. car la construction de l'ensemble se fait en C et vous avez une mauvaise implémentation. Vous devriez au moins le faire dans une expression de générateur. Voir la réponse de KennyTM pour savoir comment le faire correctement sans utiliser un ensemble.
aaronasterling
1
sets.Set est "Déconseillé depuis la version 2.6: les types set / frozenset intégrés remplacent ce module." (extrait de docs.python.org/2/library/sets.html )
Moberg
21

Pour ce que ça vaut, cela est apparu sur la liste de diffusion python-ideas récemment. Il s'avère qu'il existe déjà une recette itertools pour le faire: 1

def all_equal(iterable):
    "Returns True if all the elements are equal to each other"
    g = groupby(iterable)
    return next(g, True) and not next(g, False)

Soi-disant, il fonctionne très bien et a quelques belles propriétés.

  1. Court-circuits: il cessera de consommer des éléments de l'itérable dès qu'il trouvera le premier élément non égal.
  2. N'exige pas que les articles soient lavables.
  3. Il est paresseux et ne nécessite que O (1) de mémoire supplémentaire pour effectuer la vérification.

1 En d'autres termes, je ne peux pas m'attribuer le mérite d'avoir trouvé la solution, ni même l' avoir trouvée .

mgilson
la source
3
Beaucoup plus rapide que la réponse la plus rapide répertoriée ici dans le pire des cas.
ChaimG
return next(g, f := next(g, g)) == f(à partir de py3.8 bien sûr)
Chris_Rands
17

Voici deux façons simples de procéder

en utilisant set ()

Lors de la conversion de la liste en un ensemble, les éléments en double sont supprimés. Donc, si la longueur de l'ensemble converti est 1, cela implique que tous les éléments sont identiques.

len(set(input_list))==1

Voici un exemple

>>> a = ['not', 'the', 'same']
>>> b = ['same', 'same', 'same']
>>> len(set(a))==1  # == 3
False
>>> len(set(b))==1  # == 1
True

en utilisant tout ()

Cela comparera (équivalence) le premier élément de la liste d'entrée à tous les autres éléments de la liste. Si tous sont équivalents, True sera retourné, sinon False sera retourné.

all(element==input_list[0] for element in input_list)

Voici un exemple

>>> a = [1, 2, 3, 4, 5]
>>> b = [1, 1, 1, 1, 1]
>>> all(number==a[0] for number in a)
False
>>> all(number==b[0] for number in b)
True

PS Si vous vérifiez si la liste entière est équivalente à une certaine valeur, vous pouvez remplacer la valeur dans pour input_list [0].

Christopher Nuccio
la source
1
Pour les personnes intéressées par l'exécution, l'exécution len(set(a))sur une liste de 10 000 000 d'éléments a pris 0,09 s tandis que l'exécution a allpris 0,9 s (10 fois plus).
Elliptica
2
J'aime aussi cette réponse pour sa simplicité pythonique, en plus du score de performance mentionné par @Elliptica
NickBraunagel
11

Ceci est une autre option, plus rapide que len(set(x))==1pour les longues listes (utilise un court-circuit)

def constantList(x):
    return x and [x[0]]*len(x) == x
6502
la source
Elle est 3 fois plus lente que la solution définie sur mon ordinateur, en ignorant les courts-circuits. Donc, si l'élément inégal se trouve en moyenne dans le premier tiers de la liste, c'est plus rapide en moyenne.
max
9

C'est une manière simple de le faire:

result = mylist and all(mylist[0] == elem for elem in mylist)

C'est un peu plus compliqué, cela entraîne une surcharge d'appels de fonction, mais la sémantique est plus clairement énoncée:

def all_identical(seq):
    if not seq:
        # empty list is False.
        return False
    first = seq[0]
    return all(first == elem for elem in seq)
Jerub
la source
Vous pouvez éviter ici une comparaison redondante en utilisant for elem in mylist[1:]. Je doute que cela améliore beaucoup la vitesse, car je suppose elem[0] is elem[0]que l'interprète peut probablement faire cette comparaison très rapidement.
Brendan
5

Vérifiez si tous les éléments sont égaux au premier.

np.allclose(array, array[0])

Gusev Slava
la source
Nécessite un module tiers.
Bachsau
4

Je doute que ce soit le "plus Pythonique", mais quelque chose comme:

>>> falseList = [1,2,3,4]
>>> trueList = [1, 1, 1]
>>> 
>>> def testList(list):
...   for item in list[1:]:
...     if item != list[0]:
...       return False
...   return True
... 
>>> testList(falseList)
False
>>> testList(trueList)
True

ferait l'affaire.

machineghost
la source
1
Votre forboucle peut être rendue plus Pythonique if any(item != list[0] for item in list[1:]): return False, avec exactement la même sémantique.
musiphil
4

Si vous êtes intéressé par quelque chose d'un peu plus lisible (mais bien sûr pas aussi efficace), vous pouvez essayer:

def compare_lists(list1, list2):
    if len(list1) != len(list2): # Weed out unequal length lists.
        return False
    for item in list1:
        if item not in list2:
            return False
    return True

a_list_1 = ['apple', 'orange', 'grape', 'pear']
a_list_2 = ['pear', 'orange', 'grape', 'apple']

b_list_1 = ['apple', 'orange', 'grape', 'pear']
b_list_2 = ['apple', 'orange', 'banana', 'pear']

c_list_1 = ['apple', 'orange', 'grape']
c_list_2 = ['grape', 'orange']

print compare_lists(a_list_1, a_list_2) # Returns True
print compare_lists(b_list_1, b_list_2) # Returns False
print compare_lists(c_list_1, c_list_2) # Returns False
Joshua Burns
la source
J'essaie en fait de voir si tous les éléments d'une liste sont identiques; pas si deux listes distinctes sont identiques.
max
4

Convertissez la liste en ensemble, puis recherchez le nombre d'éléments dans l'ensemble. Si le résultat est 1, il a des éléments identiques et sinon, les éléments de la liste ne sont pas identiques.

list1 = [1,1,1]
len(set(list1)) 
>1

list1 = [1,2,3]
len(set(list1)
>3
DePP
la source
4

Concernant l'utilisation reduce()avec lambda. Voici un code de travail qui, à mon avis, est bien plus agréable que certaines des autres réponses.

reduce(lambda x, y: (x[1]==y, y), [2, 2, 2], (True, 2))

Renvoie un tuple dont la première valeur est le booléen si tous les éléments sont identiques ou non.

Marcus Lind
la source
Il y a une petite erreur dans le code tel qu'il est écrit (essayez [1, 2, 2]): il ne prend pas en compte la valeur booléenne précédente. Cela peut être corrigé en remplaçant x[1] == ypar x[0] and x[1] == y.
schot
3

Je ferais:

not any((x[i] != x[i+1] for i in range(0, len(x)-1)))

as anyarrête de rechercher l'itérable dès qu'il trouve une Truecondition.

Robert Rossney
la source
Vous n'avez pas besoin des parenthèses supplémentaires autour de l'expression du générateur si c'est le seul argument.
ninjagecko
alors all()pourquoi ne pas utiliser all(x == seq[0] for x in seq)? semble plus pythonique et devrait effectuer la même chose
Chen A.
2
>>> a = [1, 2, 3, 4, 5, 6]
>>> z = [(a[x], a[x+1]) for x in range(0, len(a)-1)]
>>> z
[(1, 2), (2, 3), (3, 4), (4, 5), (5, 6)]
# Replacing it with the test
>>> z = [(a[x] == a[x+1]) for x in range(0, len(a)-1)]
>>> z
[False, False, False, False, False]
>>> if False in z : Print "All elements are not equal"
pyfunc
la source
2
def allTheSame(i):
    j = itertools.groupby(i)
    for k in j: break
    for k in j: return False
    return True

Fonctionne en Python 2.4, qui n'a pas "tout".

itertool
la source
1
for k in j: breakest équivalent à next(j). Vous auriez également pu le faire def allTheSame(x): return len(list(itertools.groupby(x))<2)si vous ne vous souciez pas de l'efficacité.
ninjagecko
2

Peut utiliser la carte et lambda

lst = [1,1,1,1,1,1,1,1,1]

print all(map(lambda x: x == lst[0], lst[1:]))
SuperNova
la source
2

Ou utilisez la diffméthode de numpy:

import numpy as np
def allthesame(l):
    return np.all(np.diff(l)==0)

Et pour appeler:

print(allthesame([1,1,1]))

Production:

True
U10-Forward
la source
Je pense que cela not np.any(np.diff(l))pourrait être un peu plus rapide.
GZ0
2

Ou utilisez la méthode diff de numpy:

import numpy as np
def allthesame(l):
    return np.unique(l).shape[0]<=1

Et pour appeler:

print(allthesame([1,1,1]))

Production:

Vrai

Luis B
la source
Cette réponse est identique à une réponse de U9-Forward de l'année dernière.
mhwombat
Bon oeil! J'ai utilisé la même structure / API, mais ma méthode utilise np.unique et shape. La fonction de U9 utilise np.all () et np.diff () - je n'utilise aucune de ces fonctions.
Luis B
1

Tu peux faire:

reduce(and_, (x==yourList[0] for x in yourList), True)

Il est assez ennuyeux que python vous fasse importer les opérateurs comme operator.and_. À partir de python3, vous devrez également importer functools.reduce.

(Vous ne devez pas utiliser cette méthode car elle ne se cassera pas si elle trouve des valeurs non égales, mais continuera à examiner la liste entière. Elle est juste incluse ici comme réponse pour être complète.)

ninjagecko
la source
Ce ne serait pas un court-circuit. Pourquoi le préférez-vous à votre autre solution?
max
@max: vous ne le feriez pas, précisément pour cette raison; Je l'ai inclus par souci d'exhaustivité. Je devrais probablement le modifier pour le mentionner, merci.
ninjagecko
1
lambda lst: reduce(lambda a,b:(b,b==a[0] and a[1]), lst, (lst[0], True))[1]

Le prochain court-circuitera:

all(itertools.imap(lambda i:yourlist[i]==yourlist[i+1], xrange(len(yourlist)-1)))
user3015260
la source
Votre premier code était manifestement faux: reduce(lambda a,b:a==b, [2,2,2])cède False... Je l'ai édité, mais de cette façon il n'est plus joli
berdario
@berdario Vous devriez alors avoir écrit votre propre réponse, plutôt que de changer ce que quelqu'un d'autre a écrit. Si vous pensez que cette réponse était erronée, vous pouvez la commenter et / ou la décliner.
Gorpik
3
Il vaut mieux réparer quelque chose de mal, que de le laisser là pour que tout le monde le lise, en
oubliant
3
"Quand dois-je modifier les articles?" "Chaque fois que vous sentez que vous pouvez améliorer la publication et que vous êtes enclin à le faire. La modification est encouragée!"
berdario
1

Modifiez la liste en un ensemble. Ensuite, si la taille de l'ensemble n'est que de 1, elles doivent être identiques.

if len(set(my_list)) == 1:
Lumo5
la source
1

Il existe également une option récursive Python pure:

 def checkEqual(lst):
    if len(lst)==2 :
        return lst[0]==lst[1]
    else:
        return lst[0]==lst[1] and checkEqual(lst[1:])

Cependant, pour une raison quelconque, il est dans certains cas deux ordres de grandeur plus lent que les autres options. Venant de la mentalité du langage C, je m'attendais à ce que ce soit plus rapide, mais ce n'est pas le cas!

L'autre inconvénient est qu'il existe une limite de récursivité en Python qui doit être ajustée dans ce cas. Par exemple en utilisant ceci .

Foad
la source
0

Vous pouvez utiliser .nunique()pour rechercher le nombre d'éléments uniques dans une liste.

def identical_elements(list):
    series = pd.Series(list)
    if series.nunique() == 1: identical = True
    else:  identical = False
    return identical



identical_elements(['a', 'a'])
Out[427]: True

identical_elements(['a', 'b'])
Out[428]: False
Saeed
la source
0

vous pouvez utiliser set. Il fera un ensemble et supprimera les éléments répétitifs. Vérifiez ensuite qu'il ne contient pas plus d'un élément.

if len(set(your_list)) <= 1:
    print('all ements are equal')

Exemple:

>>> len(set([5, 5])) <= 1
True
MHB
la source