Проверка на равенство всех элементов в списке
Я столкнулся с проблемой: мне нужна функция, которая принимает на вход список и возвращает True
, если все элементы в этом списке равны друг другу с использованием стандартного оператора равенства, и False
в противном случае.
Я думаю, что лучше всего будет пройтись по списку, сравнивая соседние элементы, а затем применить оператор AND
ко всем полученным логическим значениям. Но я не уверен, какой способ был бы наиболее питоничным для реализации этого.
5 ответ(ов)
Если вы ищете более быстрое решение, чем использование 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
Как видно из замеров, использование метода count()
на первом элементе списка работает значительно быстрее, чем подход с set()
.
Все, кажется, зацикливаются на скорости/производительности по какой-то безумной причине, в то время как автор вопроса явно спрашивает о самом чистом решении.
Этот ответ предоставляет и то:
- Чистое решение, а также
- Самое быстрое решение (более быстрое, чем текущее самое голосуемое решение с использованием
itertools.groupby
; см. дополнение в конце).
Без переписывания программы, наиболее асимптотически эффективный и наиболее читаемый способ выглядит следующим образом:
all(x==myList[0] for x in myList)
(Да, это даже работает с пустым списком! Это возможно благодаря тому, что в этом случае Python использует ленивую семантику.)
Этот код завершится в самый ранний возможный момент, поэтому он асимптотически оптимален (ожидаемое время составляет примерно O(#uniques), а не O(N), но худшее время все равно O(N)). Это предполагает, что вы раньше не видели данные...
(Если вам важна производительность, но не настолько, чтобы сильно беспокоиться об этом, вы можете просто сделать стандартные оптимизации, такие как извлечение myList[0]
из цикла и добавление громоздкой логики для крайних случаев. Однако это то, чему компилятор Python может в конечном итоге научиться, и, следовательно, не следует это делать, если это абсолютно не необходимо, так как это снижает читаемость на минимальную выгоду.)
Если вас немного больше беспокоит производительность, этот вариант в два раза быстрее предыдущего, но немного более многословный:
def allEqual(iterable):
iterator = iter(iterable)
try:
firstItem = next(iterator)
except StopIteration:
return True
for x in iterator:
if x != firstItem:
return False
return True
Если вас беспокоит производительность еще больше (но не настолько, чтобы переписывать программу), используйте текущее самое голосуемое решение с itertools.groupby
, которое в два раза быстрее allEqual
, поскольку, вероятно, это оптимизированный код на C. (Согласно документации, он не должен иметь никаких накладных расходов по памяти, так как ленивый генератор никогда не преобразуется в список... о чем можно беспокоиться, но псевдокод показывает, что сгруппированные «списки» на самом деле являются ленивыми генераторами.)
Если вас беспокоит производительность еще больше, читайте дальше...
Замечания по производительности, потому что другие ответы обсуждают это по какой-то неизвестной причине:
Эвристические приемы:
- (если это последовательность со getitem) Если вы подозреваете, что первый или последний элемент может отличаться, вы можете проверить их эвристическим образом, и если они одинаковы, тогда продолжайте с алгоритмом, описанным выше.
Самая высокая производительность:
... если вы уже видели данные и, возможно, используете какую-либо коллекцию, вы можете получить .isAllEqual()
без затрат O(1), добавив к вашей структуре Counter
, который будет обновляться при каждом добавлении/удалении и т.д. Тогда .isAllEqual()
просто проверяет, равен ли Counter {что-то:количество}
т.е. len(counter)==1
(хотя также необходимо проверить, является ли это пустым списком []
, для которого утверждение «все элементы равны» является тривиально истинным, поэтому следует return len(counter)<=1
). Это предполагает, что элементы вашего списка являются хэшируемыми.
Смотрите https://docs.python.org/3/reference/datamodel.html#emulating-container-types
from collections import Counter
def asList(x):
return x if isinstance(x,list) else [x]
class CounterList(list): # НЕ ПРОВЕРЕНО, могут отсутствовать методы
def __init__(self, iterable):
super().__init__(iterable)
self.counts = Counter(iterable)
def __setitem__(self, target, itemOrItems): # работает срезами!
toRemove = asList(self[target])
toAdd = asList(itemOrItems)
super().__setitem__(target, itemOrItems)
for x in toRemove:
self.counts[x] -= 1
if self.counts[x] == 0:
del self.counts[x]
for y in toAdd:
self.counts[y] += 1
def __delitem__(self, target):
toRemove = asList(self[target])
super().__delitem__(target)
for x in toRemove:
self.counts[x] -= 1
if self.counts[x] == 0:
del self.counts[x]
def pop(self):
R = super().pop()
self.counts[R] -= 1
return R
def remove(self, item):
super().remove(item) # вызывает ValueError, если отсутствует
self.counts[item] -= 1
def insert(self, index, item):
super().insert(index, item)
self.counts[item] += 1
def append(self, item):
super().append(item)
self.counts[item] += 1
def extend(self, otherList):
super().extend(otherList)
for item in otherList:
self.counts[item] += 1
def isAllEqual(self):
return len(self.counts) <= 1
Демо:
demo = CounterList([5,6])
demo[0] = 6
demo.isAllEqual() # выводит True
Кроме того, вы можете вести Counter
в отдельной переменной и обновлять его всякий раз, когда вы будете модифицировать myList
. (Конкретно, вручную скопируйте код выше в CounterList
, когда вы выполняете операцию.)
myList = [.....]
myListCounter = Counter()
myList.append(5)
myListCounter[5] += 1 # дополнительная запись
myListCounter[myList[2]] -= 1 # дополнительная запись
myList[2] = True
myListCounter[True] += 1 # дополнительная запись
Оба этих метода эквивалентны и доказуемо асимптотически лучше всего остального. В двух словах: вы всегда можете избежать итерации по всему списку, чтобы проверить all(x==myList[0] for x in myList)
; для этого, каждый раз, когда вы модифицируете myList
, вы также отслеживаете с помощью Counter
, сколько из каждого элемента имеется.
Конечно, стоит обратить внимание на читаемость.
Вы можете преобразовать список в множество. Множество не может содержать дубликаты. Таким образом, если все элементы в исходном списке одинаковые, то в множестве останется только один элемент.
if len(set(input_list)) == 1:
# input_list содержит все идентичные элементы.
Вот простой способ сделать это:
result = mylist and all(mylist[0] == elem for elem in mylist)
Этот подход немного сложнее, он накладывает накладные расходы на вызовы функций, но семантика здесь более ясна:
def all_identical(seq):
if not seq:
# пустой список считается False.
return False
first = seq[0]
return all(first == elem for elem in seq)
Оба подхода позволяют проверить, все ли элементы в списке mylist
одинаковы. Первый вариант более компактный, но может быть менее понятным, тогда как второй вариант явно описывает логику и обработку пустого списка. Выбирайте тот, который лучше соответствует вашим требованиям по читаемости и производительности.
Вот еще один вариант, который работает быстрее, чем len(set(x)) == 1
для длинных списков (использует короткое замыкание):
def constantList(x):
return x[:1] * len(x) == x
Этот подход создает новый список, состоящий из первого элемента x
, повторенного столько раз, сколько элементов в списке x
. Затем он сравнивает этот новый список с оригинальным. Если все элементы в x
одинаковы, то выражение вернет True
, в противном случае - False
. Это решение может оказаться более эффективным для длинных списков благодаря отсутствию дополнительных затрат на создание множества.
Почему сравнение строк с помощью '==' и 'is' иногда дает разные результаты?
Как сгенерировать все перестановки списка?
Как протестировать несколько переменных на равенство одному значению?
Как выполнить регистронезависимое сравнение строк?
Сравнение строк в Python: is vs. ==