Controlla se tutti gli elementi in un elenco sono identici


390

Ho bisogno della seguente funzione:

Input : alist

Uscita :

  • True se tutti gli elementi nell'elenco di input vengono valutati uguali tra loro utilizzando l'operatore di uguaglianza standard;
  • False altrimenti.

Prestazioni : ovviamente preferisco non sostenere spese inutili.

Sento che sarebbe meglio:

  • scorrere l'elenco
  • confronta gli elementi adiacenti
  • e ANDtutti i valori booleani risultanti

Ma non sono sicuro di quale sia il modo più Pythonic per farlo.


La mancanza di funzionalità di corto circuito fa male solo su un input lungo (oltre ~ 50 elementi) che hanno elementi disuguali all'inizio. Se ciò si verifica abbastanza spesso (quanto spesso dipende da quanto potrebbero essere lunghi gli elenchi), è necessario il cortocircuito. Il miglior algoritmo di corto circuito sembra essere @KennyTM checkEqual1. Paga, tuttavia, un costo significativo per questo:

  • fino a 20x in elenchi quasi identici delle prestazioni
  • fino a 2,5 volte in termini di prestazioni su elenchi brevi

Se gli input lunghi con elementi disuguali precoci non si verificano (o accadono in modo sufficientemente raro), non è necessario il cortocircuito. Quindi, di gran lunga la più veloce è la soluzione @Ivo van der Wijk.


3
Uguale a in a == bo identico a in a is b?
kennytm,

1
La soluzione dovrebbe gestire elenchi vuoti? In tal caso, cosa dovrebbe essere restituito?
Doug,

1
Uguale a in == b. Dovrebbe gestire un elenco vuoto e restituire True.
max

2
Anche se so che è più lento di alcune delle altre raccomandazioni, sono sorpreso che functools.reduce(operator.eq, a)non sia stato suggerito.
user2846495

Risposte:


420

Metodo generale:

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

One-liner:

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

Anche una linea:

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

La differenza tra le 3 versioni è che:

  1. Nel checkEqual2contenuto deve essere hash.
  2. checkEqual1e checkEqual2può usare qualsiasi iteratore, ma checkEqual3deve accettare un input di sequenza, in genere contenitori concreti come un elenco o una tupla.
  3. checkEqual1 si interrompe non appena viene rilevata una differenza.
  4. Poiché checkEqual1contiene più codice Python, è meno efficiente quando molti degli elementi sono uguali all'inizio.
  5. Dal momento che checkEqual2e checkEqual3eseguire sempre O (n) operazioni di copia, prenderanno più a lungo se la maggior parte del vostro input restituisce false.
  6. Perché checkEqual2ed checkEqual3è più difficile adattare il confronto da a == ba a is b.

timeit risultato, per Python 2.7 e (solo s1, s4, s7, s9 dovrebbero restituire 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 = []

noi abbiamo

      | 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

1
Grazie, questa è una spiegazione davvero utile delle alternative. Potete per favore ricontrollare la vostra tabella delle prestazioni - è tutto in msec e i numeri sono nelle celle corrette?
max

7
@max: Sì. Si noti che 1 msec = 1000 usec.
kennytm,

1
Non dimenticare l'analisi dell'utilizzo della memoria per array molto grandi, una soluzione nativa che ottimizza le chiamate in uscita verso obj.__eq__quando lhs is rhse le ottimizzazioni fuori ordine per consentire più rapidamente gli elenchi ordinati in corto circuito.
Glenn Maynard,

3
Ivo van der Wijk ha una soluzione migliore per sequenze che è circa 5 volte più veloce di set e O (1) in memoria.
aaronasterling

2
C'è anche una itertoolsricetta che ho aggiunto come risposta. Potrebbe valere la pena buttarlo nella tua matrice dei tempi :-).
mgilson,

301

Una soluzione più veloce dell'utilizzo di set () che funziona su sequenze (non iterabili) è semplicemente contare il primo elemento. Questo presuppone che l'elenco non sia vuoto (ma è banale da controllare e decidi tu stesso quale dovrebbe essere il risultato in un elenco vuoto)

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

alcuni semplici benchmark:

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

5
OMG, questo è 6 volte più veloce della soluzione impostata! (280 milioni di elementi / sec contro 45 milioni di elementi / sec sul mio laptop). Perché??? Ed esiste un modo per modificarlo in modo che cortocircuiti (immagino di no ...)
max

2
Immagino che list.count abbia un'implementazione C altamente ottimizzata e la lunghezza dell'elenco sia memorizzata internamente, quindi anche len () è economico. Non esiste un modo per contare i cortocircuiti () poiché sarà necessario controllare davvero tutti gli elementi per ottenere il conteggio corretto.
Ivo van der Wijk,

Posso cambiarlo in: in x.count(next(x)) == len(x)modo che funzioni per qualsiasi contenitore x? Ahh .. nm, ho appena visto che .count è disponibile solo per le sequenze .. Perché non è implementato per altri container integrati? Il conteggio all'interno di un dizionario è intrinsecamente meno significativo rispetto a un elenco?
massimo

4
Un iteratore potrebbe non avere una lunghezza. Ad esempio, può essere infinito o solo generato dinamicamente. Puoi trovarne la lunghezza solo convertendolo in un elenco che toglie la maggior parte dei vantaggi degli iteratori
Ivo van der Wijk,

Spiacenti, intendevo perché countnon è implementato per gli iterabili, non perché lennon è disponibile per gli iteratori. La risposta è probabilmente che è solo una svista. Ma è irrelavante per noi perché l'impostazione predefinita .count()per le sequenze è molto lenta (pitone puro). Il motivo per cui la tua soluzione è così veloce è che si basa sull'implementazione C countfornita da list. Quindi suppongo che qualunque cosa succeda per implementare il countmetodo in C trarrà beneficio dal tuo approccio.
max

164

Il modo più semplice ed elegante è il seguente:

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

(Sì, funziona anche con la lista vuota! Questo perché questo è uno dei pochi casi in cui Python ha una semantica pigra.)

Per quanto riguarda le prestazioni, ciò fallirà il più presto possibile, quindi è asintoticamente ottimale.


Funziona, ma è un po '(1,5x) più lento di @KennyTM checkEqual1. Non sono sicuro del perché.
max

4
max: Probabilmente perché non mi sono preoccupato di eseguire l'ottimizzazione first=myList[0] all(x==first for x in myList), forse
ninjagecko,

Penso che myList [0] sia valutato con ogni iterazione. >>> timeit.timeit ('all ([y == x [0] per y in x])', 'x = [1] * 4000', numero = 10000) 2.707076672740641 >>> timeit.timeit ('x0 = x [0]; all ([y == x0 per y in x]) ',' x = [1] * 4000 ', numero = 10000) 2.0908854261426484
Matt Liberty,

1
Dovrei ovviamente chiarire che l'ottimizzazione first=myList[0]getterà un IndexErrorelenco vuoto, quindi i commentatori che stavano parlando di quell'ottimizzazione che ho citato dovranno occuparsi del caso limite di un elenco vuoto. Comunque l'originale va bene ( x==myList[0]va bene allperché non viene mai valutato se l'elenco è vuoto).
ninjagecko,

1
Questo è chiaramente il modo giusto per farlo. Se vuoi la velocità in ogni caso, usa qualcosa come numpy.
Henry Gomersall,

45

Un lavoro di confronto impostato:

len(set(the_list)) == 1

L'uso setrimuove tutti gli elementi duplicati.


26

È possibile convertire l'elenco in un set. Un set non può avere duplicati. Quindi, se tutti gli elementi nell'elenco originale sono identici, il set avrà solo un elemento.

if len(sets.Set(input_list)) == 1
// input_list has all identical elements.

questo è carino ma non mette in corto circuito e devi calcolare la lunghezza della lista risultante.
aaronasterling

15
perché non solo len(set(input_list)) == 1?
Nick Dandoulakis,

2
@codaddict. Significa che anche se i primi due elementi sono distinti, completerà comunque l'intera ricerca. utilizza anche O (k) spazio extra dove k è il numero di elementi distinti nell'elenco.
aaronasterling,

1
@Max. perché la creazione del set avviene in C e hai un'implementazione errata. Dovresti almeno farlo in un'espressione di generatore. Vedi la risposta di KennyTM per come farlo correttamente senza usare un set.
aaronasterling

1
sets.Set è "Obsoleto dalla versione 2.6: I tipi set / frozenset integrati sostituiscono questo modulo." (da docs.python.org/2/library/sets.html )
Moberg

21

Per quello che vale, questo è apparso recentemente nella mailing list di Python-Ideas . Si scopre che esiste già una ricetta itertools per fare questo: 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)

Presumibilmente si comporta molto bene e ha alcune belle proprietà.

  1. Cortocircuiti: smetterà di consumare oggetti dall'iterabile non appena trova il primo oggetto non uguale.
  2. Non richiede articoli per essere hash.
  3. È pigro e richiede solo O (1) memoria aggiuntiva per eseguire il controllo.

1 In altre parole, non posso prendermi il merito per aver trovato la soluzione, né posso prendermi il merito anche per averla trovata .


3
Molto più veloce della risposta più veloce elencata qui nello scenario peggiore.
ChaimG

return next(g, f := next(g, g)) == f(da py3.8 ovviamente)
Chris_Rands il

17

Ecco due semplici modi per farlo

usando set ()

Quando si converte l'elenco in un set, gli elementi duplicati vengono rimossi. Quindi, se la lunghezza dell'insieme convertito è 1, ciò implica che tutti gli elementi sono uguali.

len(set(input_list))==1

Ecco un esempio

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

usando all ()

Ciò confronterà (equivalenza) il primo elemento dell'elenco di input con ogni altro elemento dell'elenco. Se tutti sono equivalenti, True verrà restituito, altrimenti False verrà restituito.

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

Ecco un esempio

>>> 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 Se stai verificando se l'intero elenco è equivalente a un determinato valore, puoi sostituire il valore in input_list [0].


1
Per le persone interessate al runtime, l'esecuzione len(set(a))su un elenco di 10.000.000 di elementi ha richiesto 0,09 s, mentre l'esecuzione allha richiesto 0,9 s (10 volte più a lungo).
Elliptica,

2
Mi piace anche questa risposta per la sua semplicità pitonica, oltre al punteggio delle prestazioni menzionato da @Elliptica
NickBraunagel il

11

Questa è un'altra opzione, più veloce rispetto len(set(x))==1alle liste lunghe (usa il corto circuito)

def constantList(x):
    return x and [x[0]]*len(x) == x

È 3 volte più lento della soluzione impostata sul mio computer, ignorando il corto circuito. Quindi, se l'elemento disuguale si trova in media nel primo terzo dell'elenco, in media è più veloce.
massimo

9

Questo è un modo semplice per farlo:

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

Questo è leggermente più complicato, comporta un overhead di chiamata di funzione, ma la semantica è spiegata più chiaramente:

def all_identical(seq):
    if not seq:
        # empty list is False.
        return False
    first = seq[0]
    return all(first == elem for elem in seq)

È possibile evitare un confronto ridondante qui utilizzando for elem in mylist[1:]. Dubbio, però, migliora molto la velocità, dato che credo che elem[0] is elem[0]l'interprete possa probabilmente fare quel confronto molto rapidamente.
Brendan,

5

Controlla se tutti gli elementi sono uguali al primo.

np.allclose(array, array[0])


Richiede modulo di terze parti.
Bachsau,

4

Dubbio, questo è il "più Pythonic", ma qualcosa del genere:

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

farebbe il trucco.


1
Il tuo forloop può essere reso più Pythonic if any(item != list[0] for item in list[1:]): return False, con esattamente la stessa semantica.
musiphil,

4

Se sei interessato a qualcosa di un po 'più leggibile (ma ovviamente non altrettanto efficiente), potresti provare:

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

In realtà sto cercando di vedere se tutti gli elementi in un elenco sono identici; non se due liste separate sono identiche.
max

4

Converti l'elenco nel set e trova il numero di elementi nel set. Se il risultato è 1, ha elementi identici e, in caso contrario, gli elementi nell'elenco non sono identici.

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

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

4

Per quanto riguarda l'utilizzo reduce()con lambda. Ecco un codice funzionante che personalmente ritengo molto più bello di alcune delle altre risposte.

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

Restituisce una tupla in cui il primo valore è il valore booleano se tutti gli elementi sono uguali o meno.


C'è un piccolo errore nel codice scritto (provare [1, 2, 2]): non tiene conto del valore booleano precedente. Questo può essere risolto sostituendolo x[1] == ycon x[0] and x[1] == y.
schota il

3

Farei:

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

come anysi ferma la ricerca sul iterabile non appena trova una Truecondizione.


Non hai bisogno delle parentesi extra attorno all'espressione del generatore se è l'unico argomento.
ninjagecko,

così all(), perché non usare all(x == seq[0] for x in seq)? sembra più pitonico e dovrebbe funzionare allo stesso modo
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"

2
def allTheSame(i):
    j = itertools.groupby(i)
    for k in j: break
    for k in j: return False
    return True

Funziona in Python 2.4, che non ha "tutto".


1
for k in j: breakè equivalente a next(j). Avresti potuto farlo anche def allTheSame(x): return len(list(itertools.groupby(x))<2)se non ti interessasse l'efficienza.
ninjagecko,

2

Può usare map e lambda

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

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

2

O usa il diffmetodo di numpy:

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

E chiamare:

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

Produzione:

True

Penso che not np.any(np.diff(l))potrebbe essere un po 'più veloce.
GZ0

2

O usa il metodo diff di numpy:

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

E chiamare:

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

Produzione:

Vero


Questa risposta è identica a una risposta di U9-Forward dello scorso anno.
mhwombat,

Buon occhio! Ho usato la stessa struttura / API, ma il mio metodo usa np.unique e shape. La funzione di U9 usa np.all () e np.diff () - Non uso nessuna di queste funzioni.
Luis B

1

Tu puoi fare:

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

È abbastanza fastidioso che Python ti faccia importare gli operatori come operator.and_. A partire da python3, dovrai anche importare functools.reduce.

(Non dovresti usare questo metodo perché non si romperà se troverà valori non uguali, ma continuerai a esaminare l'intero elenco. È solo incluso qui come risposta per completezza.)


Questo non sarebbe in corto circuito. Perché lo preferiresti alla tua altra soluzione?
max

@max: non lo faresti, proprio per questo motivo; L'ho incluso per completezza. Probabilmente dovrei modificarlo per menzionarlo, grazie.
ninjagecko,

1
lambda lst: reduce(lambda a,b:(b,b==a[0] and a[1]), lst, (lst[0], True))[1]

Il prossimo metterà in corto circuito:

all(itertools.imap(lambda i:yourlist[i]==yourlist[i+1], xrange(len(yourlist)-1)))

Il tuo primo codice era ovviamente sbagliato: reduce(lambda a,b:a==b, [2,2,2])rese False... l'ho modificato, ma in questo modo non è più carino
berdario

@berdario Allora avresti dovuto scrivere la tua risposta, piuttosto che cambiare ciò che qualcun altro ha scritto. Se ritieni che questa risposta sia errata, puoi commentarla e / o ridimensionarla.
Gorpik,

3
È meglio riparare qualcosa di sbagliato, piuttosto che lasciarlo lì perché tutti lo leggano, magari perdendo i commenti che spiegano perché era sbagliato
berdario

3
"Quando devo modificare i post?" "Ogni volta che ritieni di poter migliorare il post e sei propenso a farlo. La modifica è incoraggiata!"
Berdario,

1

Cambia l'elenco in un set. Quindi se la dimensione del set è solo 1, devono essere stati gli stessi.

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

1

C'è anche un'opzione ricorsiva Python pura:

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

Tuttavia, per qualche motivo è in alcuni casi due ordini di grandezza più lenti rispetto ad altre opzioni. Venendo dalla mentalità del linguaggio C, mi aspettavo che questo fosse più veloce, ma non lo è!

L'altro svantaggio è che esiste un limite di ricorsione in Python che deve essere modificato in questo caso. Ad esempio usando questo .


0

È possibile utilizzare .nunique()per trovare il numero di elementi univoci in un elenco.

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

0

puoi usare set. Farà un set e rimuoverà elementi ripetitivi. Quindi controlla che non abbia più di 1 elemento.

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

Esempio:

>>> len(set([5, 5])) <= 1
True
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.