Verifique si todos los elementos en una lista son idénticos

390

Necesito la siguiente función:

Entrada : alist

Salida :

  • True si todos los elementos en la lista de entrada se evalúan como iguales entre sí utilizando el operador de igualdad estándar;
  • False de otra manera.

Rendimiento : por supuesto, prefiero no incurrir en gastos generales innecesarios.

Siento que sería mejor:

  • iterar a través de la lista
  • comparar elementos adyacentes
  • y ANDtodos los valores booleanos resultantes

Pero no estoy seguro de cuál es la forma más pitónica de hacer eso.


La falta de función de cortocircuito solo perjudica a una entrada larga (más de ~ 50 elementos) que tienen elementos desiguales desde el principio. Si esto ocurre con la suficiente frecuencia (la frecuencia depende de la duración de las listas), se requiere un cortocircuito. El mejor algoritmo de cortocircuito parece ser @KennyTM checkEqual1. Sin embargo, paga un costo significativo por esto:

  • Hasta 20 veces en listas casi idénticas de rendimiento
  • hasta 2.5x en rendimiento en listas cortas

Si las entradas largas con elementos desiguales tempranos no suceden (o suceden con suficiente frecuencia), no se requiere un cortocircuito. Entonces, con mucho, el más rápido es la solución @Ivo van der Wijk.

max
fuente
3
¿Igual que en a == bo idéntico como en a is b?
kennytm
1
¿Debería la solución manejar listas vacías? Si es así, ¿qué se debe devolver?
Doug
1
Igual que en a == b. Debe manejar la lista vacía y devolver True.
max
2
Aunque sé que es más lento que algunas de las otras recomendaciones, me sorprende que functools.reduce(operator.eq, a)no se haya sugerido.
user2846495

Respuestas:

420

Método general:

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

Un trazador de líneas:

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

También una línea:

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

La diferencia entre las 3 versiones es que:

  1. En checkEqual2el contenido debe ser hashable.
  2. checkEqual1y checkEqual2puede usar cualquier iterador, pero checkEqual3debe tomar una secuencia de entrada, típicamente contenedores concretos como una lista o tupla.
  3. checkEqual1 se detiene tan pronto como se encuentra una diferencia.
  4. Como checkEqual1contiene más código Python, es menos eficiente cuando muchos de los elementos son iguales al principio.
  5. Dado que checkEqual2y checkEqual3siempre realiza operaciones de copiado O (N), tomarán más tiempo si la mayoría de su entrada devuelve False.
  6. Para checkEqual2y checkEqual3es más difícil adaptar la comparación de a == ba a is b.

timeit resultado, para Python 2.7 y (solo s1, s4, s7, s9 deberían devolver 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 = []

obtenemos

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

Nota:

# 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
fuente
1
Gracias, esta es una explicación realmente útil de las alternativas. ¿Puede verificar su tabla de rendimiento? ¿Está todo en ms y los números en las celdas correctas?
max
77
@max: sí. Tenga en cuenta que 1 mseg = 1000 usec.
kennytm
1
No olvide el análisis del uso de memoria para arreglos muy grandes, una solución nativa que optimiza las llamadas a obj.__eq__cuándo lhs is rhsy optimizaciones fuera de orden para permitir listas ordenadas de cortocircuito más rápidamente.
Glenn Maynard
3
Ivo van der Wijk tiene una mejor solución para secuencias que es aproximadamente 5 veces más rápida que la establecida y O (1) en la memoria.
aaronasterling
2
También hay una itertoolsreceta que agregué como respuesta. Podría valer la pena incluir eso en su matriz de tiempo :-).
mgilson
301

Una solución más rápida que usar set () que funciona en secuencias (no iterables) es simplemente contar el primer elemento. Esto supone que la lista no está vacía (pero es trivial verificarla y decidir cuál es el resultado en una lista vacía)

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

Algunos puntos de referencia 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
fuente
55
¡Dios mío, esto es 6 veces más rápido que la solución establecida! (280 millones de elementos / segundo frente a 45 millones de elementos / segundo en mi computadora portátil). ¿¿¿Por qué??? ¿Y hay alguna forma de modificarlo para que provoque un cortocircuito (supongo que no ...)
Max
2
Supongo que list.count tiene una implementación C altamente optimizada, y la longitud de la lista se almacena internamente, por lo que len () también es barato. No hay una forma de contar el cortocircuito () ya que necesitará verificar realmente todos los elementos para obtener el conteo correcto.
Ivo van der Wijk
¿Puedo cambiarlo a: x.count(next(x)) == len(x)para que funcione para cualquier contenedor x? Ahh ... nm, acabo de ver que .count solo está disponible para secuencias ... ¿Por qué no está implementado para otros contenedores integrados? ¿Cuenta dentro de un diccionario inherentemente menos significativo que dentro de una lista?
max
44
Un iterador puede no tener una longitud. Por ejemplo, puede ser infinito o simplemente generado dinámicamente. Solo puede encontrar su longitud convirtiéndola en una lista que elimine la mayoría de las ventajas de los iteradores
Ivo van der Wijk
Lo siento, lo que quise decir es por qué countno está implementado para iterables, no por qué lenno está disponible para iteradores. La respuesta es probablemente que es solo un descuido. Pero es irrelevante para nosotros porque el valor predeterminado .count()para las secuencias es muy lento (Python puro). La razón por la que su solución es tan rápida es que se basa en el C implementado countprovisto por list. Por lo tanto, supongo que lo que ocurra para implementar el countmétodo en C se beneficiará de su enfoque.
max
164

La forma más simple y elegante es la siguiente:

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

(¡Sí, esto incluso funciona con la lista vacía! Esto se debe a que este es uno de los pocos casos en que Python tiene una semántica perezosa).

En cuanto al rendimiento, esto fallará lo antes posible, por lo que es asintóticamente óptimo.

ninjagecko
fuente
Esto funciona, pero es un poco (1.5x) más lento que @KennyTM checkEqual1. No estoy seguro de por qué.
máximo
44
max: Probablemente porque no me molesté en realizar la optimización first=myList[0] all(x==first for x in myList), tal vez
ninjagecko
Creo que myList [0] se evalúa con cada iteración. >>> timeit.timeit ('all ([y == x [0] para y en x])', 'x = [1] * 4000', número = 10000) 2.707076672740641 >>> timeit.timeit ('x0 = x [0]; todos ([y == x0 para y en x]) ',' x = [1] * 4000 ', número = 10000) 2.0908854261426484
Matt Liberty
1
Por supuesto, debo aclarar que la optimización first=myList[0]arrojará una IndexErrorlista vacía, por lo que los comentaristas que estaban hablando de esa optimización que mencioné tendrán que lidiar con el caso de una lista vacía. Sin embargo, el original está bien ( x==myList[0]está bien dentro del allporque nunca se evalúa si la lista está vacía).
ninjagecko
1
Esta es claramente la forma correcta de hacerlo. Si quieres velocidad en todos los casos, usa algo como numpy.
Henry Gomersall
45

Un trabajo de comparación establecido:

len(set(the_list)) == 1

El uso setelimina todos los elementos duplicados.

cbalawat
fuente
26

Puede convertir la lista a un conjunto. Un conjunto no puede tener duplicados. Entonces, si todos los elementos en la lista original son idénticos, el conjunto tendrá solo un elemento.

if len(sets.Set(input_list)) == 1
// input_list has all identical elements.
codictorio
fuente
Esto es bueno, pero no hace un corto circuito y tienes que calcular la longitud de la lista resultante.
aaronasterling
15
¿Por qué no solo len(set(input_list)) == 1?
Nick Dandoulakis
2
@codaddict. Significa que incluso si los dos primeros elementos son distintos, aún completará la búsqueda completa. también usa O (k) espacio extra donde k es el número de elementos distintos en la lista.
aaronasterling
1
@max. porque construir el conjunto ocurre en C y tienes una mala implementación. Al menos deberías hacerlo en una expresión generadora. Vea la respuesta de KennyTM para saber cómo hacerlo correctamente sin usar un conjunto.
aaronasterling
1
sets.Set es "Desaprobado desde la versión 2.6: los tipos de set / frozenset incorporados reemplazan este módulo". (de docs.python.org/2/library/sets.html )
Moberg
21

Por lo que vale, esto apareció recientemente en la lista de correo de ideas de python . Resulta que ya hay una receta de itertools para hacer esto: 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)

Supuestamente funciona muy bien y tiene algunas buenas propiedades.

  1. Cortocircuitos: dejará de consumir elementos del iterable tan pronto como encuentre el primer elemento no igual.
  2. No requiere que los elementos sean hashable.
  3. Es vago y solo requiere O (1) memoria adicional para hacer la verificación.

1 En otras palabras, no puedo tomar el crédito por encontrar la solución, ni puedo tomar el crédito por encontrarla .

mgilson
fuente
3
Mucho más rápido que la respuesta más rápida enumerada aquí en el peor de los casos.
ChaimG
return next(g, f := next(g, g)) == f(desde py3.8, por supuesto)
Chris_Rands
17

Aquí hay dos formas simples de hacer esto

usando set ()

Al convertir la lista en un conjunto, se eliminan los elementos duplicados. Entonces, si la longitud del conjunto convertido es 1, entonces esto implica que todos los elementos son iguales.

len(set(input_list))==1

Aquí hay un ejemplo

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

usando all ()

Esto comparará (equivalencia) el primer elemento de la lista de entrada con cualquier otro elemento de la lista. Si todos son equivalentes, se devolverá True, de lo contrario se devolverá False.

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

Aquí hay un ejemplo

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

PD Si está verificando si toda la lista es equivalente a un cierto valor, puede suplantar el valor en input_list [0].

Christopher Nuccio
fuente
1
Para las personas interesadas en el tiempo de ejecución, el desempeño len(set(a))en una lista de 10,000,000 de elementos tomó 0.09 s mientras que el desempeño alltomó 0.9 s (10 veces más).
Elliptica
2
También me gusta esta respuesta por su simplicidad pitónica, además del puntaje de rendimiento mencionado por @Elliptica
NickBraunagel
11

Esta es otra opción, más rápida que len(set(x))==1para listas largas (usa cortocircuito)

def constantList(x):
    return x and [x[0]]*len(x) == x
6502
fuente
Es 3 veces más lento que la solución establecida en mi computadora, ignorando el cortocircuito. Entonces, si el elemento desigual se encuentra en promedio en el primer tercio de la lista, es más rápido en promedio.
max
9

Esta es una manera simple de hacerlo:

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

Esto es un poco más complicado, incurre en una sobrecarga de llamadas de función, pero la semántica se explica más claramente:

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
fuente
Puede evitar una comparación redundante aquí utilizando for elem in mylist[1:]. Sin embargo, dudo que mejore mucho la velocidad, ya que supongo elem[0] is elem[0]que el intérprete probablemente pueda hacer esa comparación muy rápidamente.
Brendan
5

Compruebe si todos los elementos son iguales al primero.

np.allclose(array, array[0])

Gusev Slava
fuente
Necesita módulo de terceros.
Bachsau
4

Dudo que este sea el "más pitónico", pero algo así como:

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

Haría el truco.

maquina
fuente
1
Su forbucle se puede hacer más pitónico if any(item != list[0] for item in list[1:]): return False, con exactamente la misma semántica.
musiphil
4

Si está interesado en algo un poco más legible (pero, por supuesto, no tan eficiente), puede probar:

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
fuente
Realmente estoy tratando de ver si todos los elementos en una lista son idénticos; no si dos listas separadas son idénticas.
max
4

Convierta la lista en el conjunto y luego encuentre el número de elementos en el conjunto. Si el resultado es 1, tiene elementos idénticos y si no, los elementos en la lista no son idénticos.

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

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

En cuanto a usar reduce()con lambda. Aquí hay un código de trabajo que personalmente creo que es mucho mejor que algunas de las otras respuestas.

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

Devuelve una tupla donde el primer valor es el booleano si todos los elementos son iguales o no.

Marcus Lind
fuente
Hay un pequeño error en el código tal como está escrito (intente [1, 2, 2]): no tiene en cuenta el valor booleano anterior. Esto se puede solucionar reemplazando x[1] == ycon x[0] and x[1] == y.
Schot
3

Lo haría:

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

como anydeja de buscar el iterable tan pronto como encuentra una Truecondición.

Robert Rossney
fuente
No necesita los paréntesis adicionales alrededor de la expresión del generador si es el único argumento.
ninjagecko
Entonces all(), ¿por qué no usar all(x == seq[0] for x in seq)? parece más pitónico y debería realizar lo mismo
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
fuente
2
def allTheSame(i):
    j = itertools.groupby(i)
    for k in j: break
    for k in j: return False
    return True

Funciona en Python 2.4, que no tiene "todo".

itertool
fuente
1
for k in j: breakes equivalente a next(j). También podría haberlo hecho def allTheSame(x): return len(list(itertools.groupby(x))<2)si no le importara la eficiencia.
ninjagecko
2

Puede usar mapa y lambda

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

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

O utilice el diffmétodo de numpy:

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

Y para llamar:

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

Salida:

True
U10-Adelante
fuente
Creo que not np.any(np.diff(l))podría ser un poco más rápido.
GZ0
2

O use el método diff de numpy:

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

Y para llamar:

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

Salida:

Cierto

Luis B
fuente
Esta respuesta es idéntica a una respuesta de U9-Forward del año pasado.
mhwombat
¡Buen ojo! Usé la misma estructura / API, pero mi método usa np.unique y shape. La función de U9 usa np.all () y np.diff () - No uso ninguna de esas funciones.
Luis B
1

Tu puedes hacer:

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

Es bastante molesto que Python te haga importar los operadores como operator.and_. A partir de python3, también deberá importar functools.reduce.

(No debe usar este método porque no se romperá si encuentra valores no iguales, pero continuará examinando la lista completa. Solo se incluye aquí como una respuesta para completar).

ninjagecko
fuente
Esto no cortocircuitaría. ¿Por qué lo preferirías a tu otra solución?
max
@max: no lo harías, precisamente por esa razón; Lo incluí en aras de la integridad. Probablemente debería editarlo para mencionar eso, gracias.
ninjagecko
1
lambda lst: reduce(lambda a,b:(b,b==a[0] and a[1]), lst, (lst[0], True))[1]

El siguiente cortocircuitará:

all(itertools.imap(lambda i:yourlist[i]==yourlist[i+1], xrange(len(yourlist)-1)))
usuario3015260
fuente
Su primer código obviamente estaba equivocado: reduce(lambda a,b:a==b, [2,2,2])rendimientos False... Lo
edité
@berdario Entonces deberías haber escrito tu propia respuesta, en lugar de cambiar lo que alguien más escribió. Si cree que esta respuesta fue incorrecta, puede comentarla y / o rechazarla.
Gorpik
3
Es mejor arreglar algo mal, de dejarlo allí por toda la gente que lo lea, posiblemente perdiendo los comentarios que explican por qué lo que estaba mal
berdario
3
"¿Cuándo debo editar las publicaciones?" "Cada vez que sientas que puedes mejorar la publicación y te inclinas a hacerlo. ¡Se recomienda editar!"
berdario
1

Cambia la lista a un conjunto. Entonces, si el tamaño del conjunto es solo 1, deben haber sido los mismos.

if len(set(my_list)) == 1:
Lumo5
fuente
1

También hay una opción recursiva de Python pura:

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

Sin embargo, por alguna razón, en algunos casos es dos órdenes de magnitud más lento que otras opciones. Viniendo de la mentalidad del lenguaje C, esperaba que esto fuera más rápido, ¡pero no lo es!

La otra desventaja es que hay un límite de recurrencia en Python que debe ajustarse en este caso. Por ejemplo usando esto .

Foad
fuente
0

Puede utilizar .nunique()para encontrar el número de elementos únicos en una lista.

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

puedes usar set. Hará un conjunto y eliminará elementos repetitivos. Luego verifique que no tenga más de 1 elemento.

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

Ejemplo:

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