Verifique se todos os elementos em uma lista são idênticos

389

Eu preciso da seguinte função:

Entrada : alist

Saída :

  • True se todos os elementos na lista de entrada forem avaliados iguais entre si usando o operador de igualdade padrão;
  • False de outra forma.

Desempenho : é claro, prefiro não incorrer em custos indiretos desnecessários.

Eu acho que seria melhor:

  • percorrer a lista
  • comparar elementos adjacentes
  • e ANDtodos os valores booleanos resultantes

Mas não tenho certeza de qual é a maneira mais pitônica de fazer isso.


A falta de recurso de curto-circuito afeta apenas uma entrada longa (mais de 50 elementos) que possui elementos desiguais desde o início. Se isso ocorrer com bastante frequência (quantas vezes depende de quanto tempo as listas podem ser), é necessário um curto-circuito. O melhor algoritmo de curto-circuito parece ser o @KennyTM checkEqual1. Paga, no entanto, um custo significativo por isso:

  • até 20x em listas quase idênticas de desempenho
  • desempenho de até 2,5x em listas curtas

Se as entradas longas com elementos desiguais iniciais não ocorrerem (ou ocorrerem suficientemente raramente), não será necessário um curto-circuito. Então, de longe, o mais rápido é a solução @Ivo van der Wijk.

max
fonte
3
Igual como em a == bou idêntico como em a is b?
Kennytm 2/10
11
A solução deve lidar com listas vazias? Se sim, o que deve ser devolvido?
Doug
11
Igual a em a == b. Deve lidar com lista vazia e retornar True.
max
2
Embora eu saiba que é mais lento do que algumas das outras recomendações, estou surpreso por functools.reduce(operator.eq, a)não ter sido sugerido.
user2846495 5/04

Respostas:

420

Método geral:

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

Uma linha:

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

Também uma linha:

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

A diferença entre as 3 versões é a seguinte:

  1. No checkEqual2conteúdo deve ser lavável.
  2. checkEqual1e checkEqual2pode usar quaisquer iteradores, mascheckEqual3 deve receber uma entrada de sequência, geralmente contêineres concretos, como uma lista ou tupla.
  3. checkEqual1 pára assim que uma diferença é encontrada.
  4. Como checkEqual1contém mais código Python, é menos eficiente quando muitos dos itens são iguais no começo.
  5. Como checkEqual2e checkEqual3sempre executa operações de cópia O (N), elas levarão mais tempo se a maior parte da sua entrada retornar Falso.
  6. Para checkEqual2e checkEqual3é mais difícil adaptar a comparação de a == bpara a is b.

timeit resultado, para Python 2.7 e (somente s1, s4, s7, s9 devem retornar 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 = []

Nós temos

      | 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
fonte
11
Obrigado, esta é uma explicação realmente útil das alternativas. Você pode verificar sua tabela de desempenho - está tudo em ms, e os números estão nas células corretas?
max
7
@max: Sim. Observe que 1 ms = 1000 usec.
Kennytm 2/10
11
Não se esqueça da análise de uso de memória para matrizes muito grandes, uma solução nativa que otimiza chamadas ausentes para obj.__eq__quando lhs is rhse otimizações fora de ordem para permitir um curto-circuito nas listas classificadas mais rapidamente.
Glenn Maynard
3
Ivo van der Wijk tem uma solução melhor para seqüências cerca de 5 vezes mais rápidas que o set e O (1) na memória.
aaronasterling
2
Há também uma itertoolsreceita que eu adicionei como resposta. Pode valer a pena jogar isso na sua matriz de tempo :-).
mgilson
299

Uma solução mais rápida do que usar set () que funciona em seqüências (não iteráveis) é simplesmente contar o primeiro elemento. Isso pressupõe que a lista não está vazia (mas isso é trivial para verificar e decidir qual o resultado em uma lista vazia)

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

alguns benchmarks 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
fonte
5
OMG, isso é 6 vezes mais rápido que a solução definida! (280 milhões de elementos / s contra 45 milhões de elementos / s no meu laptop). Por quê??? E existe alguma maneira de modificá-lo para que ele provoque um curto-circuito (acho que não ...)
max
2
Eu acho que list.count tem uma implementação C altamente otimizada, e o comprimento da lista é armazenado internamente, então len () também é barato. Não existe uma maneira de causar um curto-circuito na contagem (), pois você precisará realmente verificar todos os elementos para obter a contagem correta.
Ivo van der Wijk
Posso alterá-lo para: x.count(next(x)) == len(x)para que funcione em qualquer contêiner x? Ahh .. nm, acabei de ver que .count está disponível apenas para seqüências. Por que não é implementado para outros contêineres embutidos? A contagem dentro de um dicionário é inerentemente menos significativa do que dentro de uma lista?
max
4
Um iterador pode não ter um comprimento. Por exemplo, pode ser infinito ou apenas gerado dinamicamente. Você só pode encontrar seu comprimento por convertê-lo para uma lista que tira a maioria das vantagens iteradores
Ivo van der Wijk
Desculpe, o que eu quis dizer foi por que countnão é implementado para iterables, e não por que lennão está disponível para iteradores. A resposta é provavelmente que é apenas uma supervisão. Mas é irrelavante para nós, porque o padrão .count()para sequências é muito lento (python puro). A razão pela qual sua solução é tão rápida é que ela depende do C-implementado countpor list. Portanto, suponho que qualquer iterável que implemente o countmétodo em C se beneficiará da sua abordagem.
max
164

A maneira mais simples e elegante é a seguinte:

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

(Sim, isso funciona mesmo com a lista vazia! Isso ocorre porque este é um dos poucos casos em que python possui semântica lenta.)

Em relação ao desempenho, isso falhará o mais rápido possível, portanto é assintoticamente ideal.

ninjagecko
fonte
Isso funciona, mas é um pouco (1.5x) mais lento que o @KennyTM checkEqual1. Não sei por que.
Max
4
máx: Provavelmente porque eu não me incomodei para executar a otimização first=myList[0] all(x==first for x in myList), talvez
ninjagecko
Eu acho que myList [0] é avaliado com cada iteração. >>> timeit.timeit ('todos ([y == x [0] para y em x])', 'x = [1] * 4000', número = 10000) 2.707076672740641 >>> timeit.timeit ('x0 = x [0]; all ([y == x0 para y em x]) ',' x = [1] * 4000 ', número = 10000) 2.0908854261426484
Matt Liberty
11
É claro que devo esclarecer que a otimização first=myList[0]colocará uma IndexErrorem uma lista vazia, para que os comentaristas que estavam falando sobre a otimização que mencionei tenham que lidar com o caso extremo de uma lista vazia. No entanto, o original é bom ( x==myList[0]é bom dentro do allporque nunca é avaliado se a lista estiver vazia).
Ninjagecko
11
Este é claramente o caminho certo para isso. Se você quer velocidade em todos os casos, use algo como numpy.
Henry Gomersall
45

Um trabalho de comparação de conjuntos:

len(set(the_list)) == 1

Usar setremove todos os elementos duplicados.

cbalawat
fonte
26

Você pode converter a lista em um conjunto. Um conjunto não pode ter duplicatas. Portanto, se todos os elementos na lista original forem idênticos, o conjunto terá apenas um elemento.

if len(sets.Set(input_list)) == 1
// input_list has all identical elements.
codaddict
fonte
isso é bom, mas não provoca um curto-circuito e você deve calcular o comprimento da lista resultante.
aaronasterling
15
por que não apenas len(set(input_list)) == 1?
Nick Dandoulakis
2
@codaddict. Isso significa que, mesmo que os dois primeiros elementos sejam distintos, ele ainda concluirá toda a pesquisa. ele também usa O (k) espaço extra em que k é o número de elementos distintos na lista.
aaronasterling
11
@max. porque a criação do conjunto acontece em C e você tem uma implementação ruim. Você deve pelo menos fazê-lo em uma expressão geradora. Consulte a resposta do KennyTM para saber como fazê-lo corretamente sem usar um conjunto.
Aaronasterling
11
sets.Set é "Descontinuado desde a versão 2.6: Os tipos embutidos de conjunto / frozenset substituem este módulo." (from docs.python.org/2/library/sets.html )
Moberg
21

Pelo que vale a pena, isso apareceu recentemente na lista de discussão python-ideas . Acontece que já existe uma receita de ferramentas para fazer isso: 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)

Supostamente, ele executa muito bem e tem algumas propriedades agradáveis.

  1. Curto-circuito: ele irá parar de consumir itens do iterável assim que encontrar o primeiro item diferente.
  2. Não requer itens para serem laváveis.
  3. É preguiçoso e requer apenas O (1) memória adicional para fazer a verificação.

1 Em outras palavras, não posso receber o crédito por ter encontrado a solução - nem posso ter o crédito por encontrá- la.

mgilson
fonte
3
Muito mais rápido que a resposta mais rápida listada aqui no pior cenário.
ChaimG
return next(g, f := next(g, g)) == f(from py3.8 of course)
Chris_Rands
17

Aqui estão duas maneiras simples de fazer isso

usando set ()

Ao converter a lista em um conjunto, os elementos duplicados são removidos. Portanto, se o comprimento do conjunto convertido for 1, isso implica que todos os elementos são iguais.

len(set(input_list))==1

Aqui está um exemplo

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

usando all ()

Isso comparará (equivalência) o primeiro elemento da lista de entrada com todos os outros elementos da lista. Se todos forem equivalentes, True será retornado, caso contrário, False será retornado.

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

Aqui está um exemplo

>>> 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 você está verificando se a lista inteira é equivalente a um determinado valor, você pode subitituir o valor para input_list [0].

Christopher Nuccio
fonte
11
Para as pessoas interessadas em tempo de execução, o desempenho len(set(a))em uma lista de 10.000.000 de elementos levou 0,09 s, enquanto o desempenho alldemorou 0,9 s (10 vezes mais).
Elliptica
2
Eu também gosto desta resposta para sua simplicidade pythônico, além de pontuação de desempenho mencionado por @Elliptica
NickBraunagel
11

Essa é outra opção, mais rápida do que len(set(x))==1para listas longas (usa curto-circuito)

def constantList(x):
    return x and [x[0]]*len(x) == x
6502
fonte
É 3 vezes mais lento que a solução definida no meu computador, ignorando o curto-circuito. Portanto, se o elemento desigual for encontrado em média no primeiro terço da lista, será mais rápido em média.
max
9

Esta é uma maneira simples de fazer isso:

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

Isso é um pouco mais complicado, pois gera sobrecarga de chamada de função, mas a semântica é mais claramente explicada:

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
fonte
Você pode evitar uma comparação redundante aqui usando for elem in mylist[1:]. Duvido que melhora muito a velocidade, já que eu acho elem[0] is elem[0]que o intérprete provavelmente pode fazer essa comparação muito rapidamente.
Brendan
5

Verifique se todos os elementos são iguais ao primeiro.

np.allclose(array, array[0])

Gusev Slava
fonte
Precisa de um módulo de terceiros.
Bachsau 27/02/19
4

Duvido que este seja o "mais pitonico", mas algo 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

faria o truque.

machineghost
fonte
11
Seu forloop pode ser transformado em Pythonic if any(item != list[0] for item in list[1:]): return False, com exatamente a mesma semântica.
Musiphil
4

Se você estiver interessado em algo um pouco mais legível (mas é claro que não tão eficiente), tente:

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
fonte
Na verdade, estou tentando ver se todos os elementos em uma lista são idênticos; não se duas listas separadas forem idênticas.
Max
4

Converta a lista no conjunto e encontre o número de elementos no conjunto. Se o resultado for 1, ele possui elementos idênticos e, se não, os elementos na lista não são idênticos.

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

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

Em relação ao uso reduce()com lambda. Aqui está um código funcional que, pessoalmente, acho muito melhor do que algumas das outras respostas.

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

Retorna uma tupla em que o primeiro valor é o booleano se todos os itens forem iguais ou não.

Marcus Lind
fonte
Há um pequeno erro no código, como está escrito (tente [1, 2, 2]): ele não leva em consideração o valor booleano anterior. Isso pode ser corrigido substituindo x[1] == ypor x[0] and x[1] == y.
schot 02/03
3

Eu faria:

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

como anyparadas pesquisar na iterable assim que encontra uma Truecondição.

Robert Rossney
fonte
Você não precisa de parênteses extras em torno da expressão do gerador, se esse for o único argumento.
Ninjagecko
então all(), por que não usar all(x == seq[0] for x in seq)? parece mais Python e deve executar a mesma
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
fonte
2
def allTheSame(i):
    j = itertools.groupby(i)
    for k in j: break
    for k in j: return False
    return True

Funciona no Python 2.4, que não possui "todos".

itertool
fonte
11
for k in j: breaké equivalente a next(j). Você também poderia ter feito def allTheSame(x): return len(list(itertools.groupby(x))<2)se não se importasse com eficiência.
Ninjagecko
2

Pode usar mapa e lambda

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

print all(map(lambda x: x == lst[0], lst[1:]))
Super Nova
fonte
2

Ou use o diffmétodo numpy:

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

E para ligar para:

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

Resultado:

True
Sub-10
fonte
Eu acho que not np.any(np.diff(l))poderia ser um pouco mais rápido.
GZ0
2

Ou use o método diff de numpy:

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

E para ligar para:

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

Resultado:

Verdade

Luis B
fonte
Esta resposta é idêntica à resposta do U9-Forward do ano passado.
Mhwombat 28/02/19
Bom olho! Eu usei a mesma estrutura / API, mas meu método usa np.unique e shape. A função do U9 usa np.all () e np.diff () - eu não uso nenhuma dessas funções.
Luis B
1

Você pode fazer:

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

É bastante irritante que o python faça com que você importe os operadores operator.and_. A partir do python3, você precisará também importar functools.reduce.

(Você não deve usar esse método, pois ele não será interrompido se encontrar valores não iguais, mas continuará examinando a lista inteira. Ele está incluído aqui apenas como resposta para a integridade.)

ninjagecko
fonte
Isso não causaria um curto-circuito. Por que você prefere a sua outra solução?
Max
@max: você não precisaria exatamente por esse motivo; Incluí-o por uma questão de completude. Eu provavelmente deveria editá-lo para mencionar isso, obrigado.
Ninjagecko
1
lambda lst: reduce(lambda a,b:(b,b==a[0] and a[1]), lst, (lst[0], True))[1]

O próximo irá causar um curto-circuito:

all(itertools.imap(lambda i:yourlist[i]==yourlist[i+1], xrange(len(yourlist)-1)))
user3015260
fonte
Seu primeiro código era obviamente errado: reduce(lambda a,b:a==b, [2,2,2])os rendimentos False... Eu editei, mas desta forma não é muito mais
berdario
@berdario Então você deveria ter escrito sua própria resposta, em vez de mudar o que outra pessoa escreveu. Se você acha que essa resposta está errada, você pode comentar e / ou fazer voto negativo.
Gorpik 27/03
3
É melhor para corrigir algo errado, do que deixá-lo lá para todas as pessoas a lê-lo, possivelmente perdendo os comentários que explicam por que isso estava errado
berdario
3
"Quando devo editar as postagens?" "A qualquer momento que você achar que pode melhorar a publicação e está inclinado a fazê-lo. A edição é incentivada!"
Berdario 27/03
1

Mude a lista para um conjunto. Então, se o tamanho do conjunto for apenas 1, eles deverão ter sido os mesmos.

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

Há também uma opção recursiva Python pura:

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

No entanto, por algum motivo, em alguns casos, é duas ordens de magnitude mais lenta que outras opções. Vindo da mentalidade da linguagem C, esperava que isso fosse mais rápido, mas não é!

A outra desvantagem é que há um limite de recursão no Python que precisa ser ajustado neste caso. Por exemplo, usando isso .

Foad
fonte
0

Você pode usar .nunique()para encontrar o número de itens exclusivos em uma 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
fonte
0

você pode usar set. Ele fará um conjunto e removerá elementos repetitivos. Depois verifique se não possui mais de 1 elemento.

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

Exemplo:

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