Проверьте, все ли элементы в списке идентичны

390

Мне нужна следующая функция:

Вход : аlist

Выход :

  • True если все элементы входного списка оцениваются как равные друг другу, используя стандартный оператор равенства;
  • False в противном случае.

Производительность : конечно, я предпочитаю не подвергаться ненужным накладным расходам.

Я чувствую, что было бы лучше:

  • перебрать список
  • сравнить соседние элементы
  • и ANDвсе полученные логические значения

Но я не уверен, какой самый Pythonic способ сделать это.


Отсутствие функции короткого замыкания сказывается только на длинном входе (более ~ 50 элементов), который имеет неравные элементы в начале. Если это происходит достаточно часто (как часто зависит от длины списков), короткое замыкание не требуется. Лучший алгоритм короткого замыкания, кажется, @KennyTM checkEqual1. Это платит, однако, значительную стоимость для этого:

  • почти в 20 раз по производительности почти одинаковые списки
  • производительность до 2,5 раз в коротких списках

Если длинные входы с ранними неравными элементами не происходят (или случаются достаточно редко), короткое замыкание не требуется. Тогда, безусловно, самым быстрым является решение @Ivo van der Wijk.

Максимум
источник
3
Равный как в a == bили идентичный как в a is b?
Kennytm
1
Должно ли решение обрабатывать пустые списки? Если так, что должно быть возвращено?
Даг
1
Равен как в == b. Должен обрабатывать пустой список и возвращать True.
максимум
2
Хотя я знаю, что это медленнее, чем некоторые другие рекомендации, я удивлен, functools.reduce(operator.eq, a)что не было предложено.
user2846495

Ответы:

420

Общий метод:

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

Один лайнер:

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

Также однострочник:

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

Разница между 3 версиями заключается в том, что:

  1. По checkEqual2содержанию должен быть хэш
  2. checkEqual1и checkEqual2может использовать любые итераторы, но checkEqual3должен принимать последовательность ввода, обычно это конкретные контейнеры, такие как список или кортеж.
  3. checkEqual1 останавливается, как только разница обнаружена.
  4. Так как checkEqual1содержит больше кода Python, он менее эффективен, когда многие элементы равны в начале.
  5. Так как checkEqual2и checkEqual3всегда выполняйте O (N) операции копирования, они займут больше времени, если большая часть вашего ввода вернет False.
  6. Для checkEqual2и checkEqual3сложнее адаптировать сравнение a == bк a is b.

timeit результат, для Python 2.7 и (только s1, s4, s7, s9 должны возвращать 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 = []

мы получили

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

Замечания:

# 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
источник
1
Спасибо, это действительно полезное объяснение альтернатив. Не могли бы вы еще раз проверить вашу таблицу производительности - все это в мсек, и числа в правильных ячейках?
максимум
7
@max: да. Обратите внимание, что 1 мсек = 1000 мксек.
Kennytm
1
Не забывайте анализ использования памяти для очень больших массивов, нативное решение, которое оптимизирует вызовы « obj.__eq__когда» lhs is rhs, и неупорядоченные оптимизации для более быстрого замыкания отсортированных списков.
Гленн Мейнард
3
Ivo van der Wijk предлагает лучшее решение для последовательностей, которое примерно в 5 раз быстрее, чем установлено, и O (1) в памяти.
aaronasterling
2
Существует также itertoolsрецепт, который я добавил в качестве ответа. Возможно, стоит добавить это в вашу временную матрицу :-).
Мгилсон
301

Решение, которое быстрее, чем использование set (), которое работает с последовательностями (а не итерациями), состоит в простом подсчете первого элемента. Предполагается, что список не пуст (но это легко проверить и решить, каким должен быть результат в пустом списке).

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

несколько простых тестов:

>>> 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
О боже, это в 6 раз быстрее, чем заданное решение! (280 миллионов элементов / сек против 45 миллионов элементов / сек на моем ноутбуке). Почему??? И есть ли способ изменить его так, чтобы он замыкался (я думаю, нет ...)
макс
2
Я предполагаю, что list.count имеет высоко оптимизированную реализацию на C, а длина списка хранится внутри, поэтому len () также дешев. Нет способа короткого замыкания count (), так как вам нужно действительно проверить все элементы, чтобы получить правильное количество.
Иво ван дер Вейк
Могу ли я изменить его на: x.count(next(x)) == len(x)так, чтобы он работал для любого контейнера х? Аааа .. НМ, только что увидел, что .count доступен только для последовательностей. Почему он не реализован для других встроенных контейнеров? Является ли подсчет внутри словаря по сути менее значимым, чем внутри списка?
максимум
4
Итератор может не иметь длины. Например, оно может быть бесконечным или просто динамически генерируемым. Вы можете найти его длину, только преобразовав его в список, который лишает большинство преимуществ итераторов
Ivo van der Wijk
Извините, я имел в виду, почему countне реализован для итераторов, а не почему lenне доступен для итераторов. Ответ, вероятно, в том, что это просто недосмотр. Но это неважно для нас, потому что по умолчанию .count()для последовательностей очень медленно (чистый питон). Причина, по которой ваше решение настолько быстрое, заключается в том, что оно опирается на C-внедренный, countпредоставленный list. Так что я полагаю, что любая итеративная реализация countметода в C выиграет от вашего подхода.
максимум
164

Самый простой и элегантный способ заключается в следующем:

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

(Да, это работает даже с пустым списком! Это потому, что это один из немногих случаев, когда у python ленивая семантика.)

Что касается производительности, то это не удастся в самое ближайшее время, поэтому она асимптотически оптимальна.

ninjagecko
источник
Это работает, но это немного (в 1,5 раза) медленнее, чем @KennyTM checkEqual1. Я не уверен почему.
максимум
4
Макс: вероятно, потому что я не удосужился выполнить оптимизацию first=myList[0] all(x==first for x in myList), возможно
ninjagecko
Я думаю, что myList [0] оценивается с каждой итерацией. >>> timeit.timeit ('all ([y == x [0] для y в x])', 'x = [1] * 4000', number = 10000) 2.707076672740641 >>> timeit.timeit ('x0 = x [0]; все ([y == x0 для y в x]) ',' x = [1] * 4000 ', число = 10000) 2.0908854261426484
Мэтт Либерти
1
Я, конечно, должен уточнить, что оптимизация first=myList[0]выдает IndexErrorпустой список, поэтому комментаторам, которые говорили об этой оптимизации, о которой я упоминал, придется иметь дело с крайним случаем пустого списка. Тем не менее, оригинал в порядке ( x==myList[0]в порядке, allпотому что он никогда не оценивается, если список пуст).
ninjagecko
1
Это явно верный путь к этому. Если вы хотите скорость в каждом случае, используйте что-то вроде numpy.
Генри Гомерсалл,
45

Набор сравнительных работ:

len(set(the_list)) == 1

Использование setудаляет все дубликаты элементов.

cbalawat
источник
26

Вы можете преобразовать список в набор. Набор не может иметь дубликатов. Таким образом, если все элементы в исходном списке идентичны, в наборе будет только один элемент.

if len(sets.Set(input_list)) == 1
// input_list has all identical elements.
codaddict
источник
это хорошо, но это не короткое замыкание, и вы должны рассчитать длину результирующего списка.
Ааронастерлинг
15
почему не просто len(set(input_list)) == 1?
Ник Дандулакис
2
@codaddict. Это означает, что даже если первые два элемента различны, он все равно завершит весь поиск. он также использует O (k) дополнительный пробел, где k - количество различных элементов в списке.
aaronasterling
1
@Максимум. потому что создание набора происходит в C, и у вас плохая реализация. Вы должны по крайней мере сделать это в выражении генератора. См. Ответ KennyTM о том, как сделать это правильно, не используя набор.
Ааронастерлинг
1
sets.Set является «Устаревшим с версии 2.6: этот модуль заменяют встроенные типы set / frozenset». (из docs.python.org/2/library/sets.html )
Моберг
21

Для чего это стоит, это недавно появилось в списке рассылки python-ideas . Оказывается, что для этого уже есть рецепт itertools : 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)

Предположительно, он работает очень хорошо и имеет несколько хороших свойств.

  1. Короткие замыкания: он прекратит потребление элементов из итерируемого, как только обнаружит первый неравный элемент.
  2. Не требует, чтобы элементы были хэшируемыми.
  3. Это ленивый и требует только O (1) дополнительной памяти, чтобы сделать проверку.

1 Другими словами, я не могу взять кредит за то, что нашел решение - и я не могу взять кредит за то, что даже нашел его.

mgilson
источник
3
Гораздо быстрее, чем самый быстрый ответ, указанный здесь в худшем случае.
ChaimG
return next(g, f := next(g, g)) == f(из py3.8 конечно)
Chris_Rands
17

Вот два простых способа сделать это

используя set ()

При преобразовании списка в набор дублирующиеся элементы удаляются. Таким образом, если длина преобразованного набора равна 1, то это означает, что все элементы одинаковы.

len(set(input_list))==1

Вот пример

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

используя все ()

Это позволит сравнить (эквивалентность) первого элемента входного списка с каждым другим элементом в списке. Если все эквивалентны True, будет возвращено, в противном случае False будет возвращено.

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

Вот пример

>>> 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 Если вы проверяете, является ли весь список эквивалентным определенному значению, вы можете выбрать значение для input_list [0].

Кристофер Нуччо
источник
1
Для людей, интересующихся временем выполнения, выполнение len(set(a))списка из 10 000 000 элементов заняло 0,09 с, тогда как выполнение allзаняло 0,9 с (в 10 раз дольше).
Эллиптика
2
Мне также нравится этот ответ за его питоническую простоту, помимо оценки производительности, упомянутой @Elliptica
NickBraunagel
11

Это еще один вариант, более быстрый, чем len(set(x))==1для длинных списков (использует короткое замыкание)

def constantList(x):
    return x and [x[0]]*len(x) == x
6502
источник
Это в 3 раза медленнее, чем установленное решение на моем компьютере, игнорируя короткое замыкание. Таким образом, если неравный элемент находится в среднем в первой трети списка, он быстрее в среднем.
максимум
9

Это простой способ сделать это:

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

Это немного сложнее, это вызывает накладные расходы при вызове функций, но семантика более четко прописана:

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
источник
Вы можете избежать избыточного сравнения здесь, используя for elem in mylist[1:]. Сомневаюсь, что это значительно повышает скорость, хотя, как я полагаю, elem[0] is elem[0]переводчик, вероятно, сможет сделать это сравнение очень быстро.
Брендан
5

Проверьте, все ли элементы равны первому.

np.allclose(array, array[0])

Гусев Слава
источник
Нужен сторонний модуль.
Бахсау
4

Сомневаюсь, что это «самый Pythonic», но что-то вроде:

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

сделал бы трюк.

machineghost
источник
1
Ваш forцикл может быть сделан более Pythonic if any(item != list[0] for item in list[1:]): return False, с точно такой же семантикой.
Musiphil
4

Если вы заинтересованы в чем-то более читабельном (но, конечно, не столь эффективном), вы можете попробовать:

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
Джошуа Бернс
источник
Я на самом деле пытаюсь увидеть, все ли элементы в одном списке идентичны; нет, если два отдельных списка идентичны.
максимум
4

Преобразуйте список в набор, а затем найдите количество элементов в наборе. Если результат равен 1, он имеет идентичные элементы, а если нет, то элементы в списке не идентичны.

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

list1 = [1,2,3]
len(set(list1)
>3
Депп
источник
4

Что касается использования reduce()с lambda. Вот рабочий код, который я лично считаю намного лучше, чем некоторые другие ответы.

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

Возвращает кортеж, в котором первое значение является логическим, если все элементы одинаковы или нет.

Маркус Линд
источник
В написанном коде есть небольшая ошибка (try [1, 2, 2]): он не учитывает предыдущее логическое значение. Это можно исправить, заменив x[1] == yна x[0] and x[1] == y.
Скот
3

Я сделаю:

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

as anyпрекращает поиск итерируемого, как только находит Trueусловие.

Роберт Россни
источник
Вам не нужны лишние скобки вокруг выражения генератора, если это единственный аргумент.
ninjagecko
так all()почему бы не использовать all(x == seq[0] for x in seq)? выглядит более питоническим и должен выполнять то же самое
Чен А.
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
источник
2
def allTheSame(i):
    j = itertools.groupby(i)
    for k in j: break
    for k in j: return False
    return True

Работает в Python 2.4, который не имеет «все».

itertool
источник
1
for k in j: breakэквивалентно next(j). Вы могли бы также сделать, def allTheSame(x): return len(list(itertools.groupby(x))<2)если бы вы не заботились об эффективности.
ninjagecko
2

Можно использовать карту и лямбду

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

print all(map(lambda x: x == lst[0], lst[1:]))
SuperNova
источник
2

Или используйте diffметод NumPy:

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

И позвонить:

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

Вывод:

True
U10-Forward
источник
Я думаю, что not np.any(np.diff(l))может быть немного быстрее.
GZ0
2

Или используйте метод сравнения numpy:

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

И позвонить:

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

Вывод:

Правда

Луис Б
источник
Этот ответ идентичен ответу от U9-Forward прошлого года.
Мхвомбат
Хороший глаз! Я использовал ту же структуру / API, но мой метод использует np.unique и shape. Функция U9 использует np.all () и np.diff () - я не использую ни одну из этих функций.
Луис Б
1

Ты можешь сделать:

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

Довольно досадно, что python заставляет вас импортировать операторы как operator.and_. Начиная с python3, вам нужно будет также импортироватьfunctools.reduce .

(Вы не должны использовать этот метод, потому что он не сломается, если он найдет неравные значения, но продолжит изучение всего списка. Он просто включен здесь как ответ на полноту.)

ninjagecko
источник
Это не будет коротким замыканием. Почему вы предпочли бы это по сравнению с другим решением?
максимум
@max: вы бы не стали, именно по этой причине; Я включил это для полноты картины. Я должен вероятно отредактировать это, чтобы упомянуть это, спасибо.
ninjagecko
1
lambda lst: reduce(lambda a,b:(b,b==a[0] and a[1]), lst, (lst[0], True))[1]

Следующее закоротит короткое замыкание:

all(itertools.imap(lambda i:yourlist[i]==yourlist[i+1], xrange(len(yourlist)-1)))
user3015260
источник
Ваш первый код был явно неправильным: reduce(lambda a,b:a==b, [2,2,2])доходность False... Я его отредактировал, но теперь это уже не красиво
berdario
@berdario Тогда вы должны были написать свой собственный ответ, а не менять то, что написал кто-то другой. Если вы считаете, что этот ответ был неправильным, вы можете прокомментировать его и / или понизить его.
Горпик
3
Лучше исправить что-то не так, чем оставить это там, чтобы все люди могли его прочитать, возможно, пропустив комментарии, объясняющие, почему это не так
berdario
3
"Когда я должен редактировать сообщения?" «Каждый раз, когда вы чувствуете, что можете сделать пост лучше и склонны к этому. Редактирование приветствуется!»
Бердарио
1

Измените список на набор. Тогда, если размер набора только 1, они должны быть одинаковыми.

if len(set(my_list)) == 1:
Lumo5
источник
1

Существует также рекурсивная опция чистого Python:

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

Однако по некоторым причинам это в некоторых случаях на два порядка медленнее, чем другие варианты. Исходя из менталитета языка Си, я ожидал, что это будет быстрее, но это не так!

Другим недостатком является то, что в Python есть предел рекурсии, который в этом случае необходимо скорректировать. Например, используя это .

Foad
источник
0

Вы можете использовать, .nunique()чтобы найти количество уникальных предметов в списке.

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
источник
0

Вы можете использовать set. Это сделает набор и удалит повторяющиеся элементы. Затем убедитесь, что в нем не более 1 элемента.

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

Пример:

>>> len(set([5, 5])) <= 1
True
МНВ
источник