Перевірте, чи всі елементи в списку однакові


389

Мені потрібна наступна функція:

Вхід : alist

Вихід :

  • True якщо всі елементи у вхідному списку оцінюють як рівні між собою за допомогою стандартного оператора рівності;
  • False інакше.

Продуктивність : звичайно, я вважаю за краще не зазнавати зайвих накладних витрат.

Я вважаю, що найкраще:

  • повторити список
  • порівняйте сусідні елементи
  • і ANDвсі отримані булеві значення

Але я не впевнений, який найбільш піфонічний спосіб це зробити.


Відсутність функції короткого замикання завдає шкоди лише довгому входу (понад ~ 50 елементів), у якого на початку є нерівні елементи. Якщо це трапляється досить часто (як часто залежить від того, як довго можуть бути списки), потрібне коротке замикання. Найкращим алгоритмом короткого замикання здається @KennyTM checkEqual1. Однак вона платить за це значні витрати:

  • до 20-кратного виконання майже однакових списків
  • до 2,5х показників у коротких списках

Якщо довгі входи з ранніми нерівними елементами не трапляються (або трапляються досить рідко), коротке замикання не потрібно. Тоді, безумовно, найшвидшим є рішення @Ivo van der Wijk.


3
Рівні як у a == bабо однакові, як у a is b?
kennytm

1
Чи має рішення обробляти порожні списки? Якщо так, що потрібно повернути?
Дуг

1
Дорівнює, як у a == b. Потрібно обробити порожній список і повернути True.
макс

2
Хоча я знаю, що це повільніше, ніж деякі інші рекомендації, я здивований, functools.reduce(operator.eq, a)що не було запропоновано.
користувач2846495

Відповіді:


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]

Різниця між трьома версіями полягає в тому, що:

  1. В checkEqual2 змістом має бути хешируемим.
  2. checkEqual1і checkEqual2може використовувати будь-які ітератори, алеcheckEqual3 повинні приймати послідовність введення, як правило, конкретні контейнери, такі як список або кортеж.
  3. checkEqual1 припиняється, як тільки виявляється різниця.
  4. Оскільки checkEqual1містить більше Python-коду, він менш ефективний, коли багато елементів на початку рівні.
  5. Оскільки checkEqual2і checkEqual3завжди виконуються операції з копіювання O (N), вони зайнятимуть більше часу, якщо більша частина вашого вводу поверне помилкове значення.
  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

1
Дякую, це справді корисне пояснення альтернатив. Чи можете ви двічі перевірити таблицю ефективності - чи все це в мсек і чи є цифри у правильних клітинках?
макс

7
@max: Так. Зауважте, що 1 мсек = 1000 у.о.
kennytm

1
Не забувайте аналіз використання пам’яті для дуже великих масивів, власне рішення, яке оптимізує виклики до, obj.__eq__коли lhs is rhsта поза оптимізацією, щоб швидше дозволяти коротке замикання відсортованих списків.
Гленн Мейнард

3
Іво ван дер Війк має краще рішення для послідовностей, які приблизно в 5 разів швидше, ніж набір і O (1) в пам'яті.
aaronasterling

2
Є також itertoolsрецепт, який я додав у відповідь. Можливо, варто закинути це у вашу часову матрицю :-).
mgilson

298

Рішення швидше, ніж використання 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
OMG, це в 6 разів швидше заданого рішення! (280 мільйонів елементів / сек проти 45 мільйонів елементів / сек на моєму ноутбуці). Чому ??? І чи є спосіб змінити його так, щоб воно мало коротке замикання (мабуть, ні ...)
макс

1
Я думаю, що list.count має оптимізовану реалізацію на C, а довжина списку зберігається всередині, тому len () також дешевий. Не існує способу підрахунку короткого замикання (), оскільки вам потрібно буде справді перевірити всі елементи, щоб отримати правильний підрахунок.
Іво ван дер Війк

Чи можу я змінити його на: x.count(next(x)) == len(x)щоб він працював для будь-якого контейнера x? Ааа .. нм, щойно побачив, що .count доступний лише для послідовностей .. Чому він не реалізований для інших вбудованих контейнерів? Невже підрахунок всередині словника є менш значущим, ніж усередині списку?
макс

4
Ітератор може не мати довжини. Наприклад, це може бути нескінченно або просто динамічно породжене. Дізнатися його довжину можна лише перетворивши на список, який забирає більшість переваг ітераторів
Іво ван дер Війк

Вибачте, що я мав на увазі, чому countвін не реалізований для ітерабелів, а lenне для ітераторів. Відповідь, ймовірно, що це просто недогляд. Але це для нас нерелевантно, тому що за замовчуванням .count()для послідовностей дуже повільно (чистий пітон). Причина того, що ваше рішення настільки швидке, полягає в тому, що воно покладається на C, реалізовані countкомпанією list. Отже, я вважаю, що залежно від того, чи вдасться застосувати countметод в С, виграє ваш підхід.
макс

163

Найпростіший і елегантний спосіб полягає в наступному:

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

(Так, це навіть працює з порожнім списком! Це тому, що це один з небагатьох випадків, коли пітон має ліниву семантику.)

Щодо продуктивності, то це не вдасться якомога раніше, тому це асимптотично оптимально.


Це працює, але трохи (1,5х) повільніше, ніж @KennyTM checkEqual1. Я не впевнений, чому.
макс

4
max: Мабуть тому, що я не first=myList[0] all(x==first for x in myList)
покладався

Я думаю, що мій список [0] оцінюється з кожною ітерацією. >>> timeit.timeit ('all ([y == x [0] for y in x])', 'x = [1] * 4000', число = 10000) 2.707076672740641 >>> timeit.timeit ('x0 = x [0]; all ([y == x0 for y in x]) ',' x = [1] * 4000 ', число = 10000) 2.0908854261426484
Matt Liberty

1
Я, звичайно, повинен уточнити, що оптимізація first=myList[0]викине IndexErrorпорожній список, тому коментатори, які говорили про ту оптимізацію, про яку я згадав, повинні мати справу з кращим випадком порожнього списку. Однак оригінал прекрасний ( x==myList[0]добре в межах, allтому що він ніколи не оцінюється, якщо список порожній).
ninjagecko

1
Це явно правильний шлях до цього. Якщо ви хочете швидкості в будь-якому випадку, використовуйте щось на кшталт оніміння.
Генрі Гомерсалл

45

Набір порівняльних робіт:

len(set(the_list)) == 1

Використання setвидаляє всі повторювані елементи.


26

Ви можете перетворити список у набір. Набір не може мати дублікатів. Отже, якщо всі елементи в оригінальному списку однакові, набір матиме лише один елемент.

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

це добре, але це не має короткого замикання, і вам доведеться обчислити довжину результуючого списку.
aaronasterling

15
чому не просто len(set(input_list)) == 1?
Нік Дандулакіс

2
@codaddict. Це означає, що навіть якщо перші два елементи є різними, він все одно завершить весь пошук. він також використовує O (k) додатковий простір, де k - кількість різних елементів у списку.
aaronasterling

1
@max. тому що побудова набору відбувається в C, і у вас погана реалізація. Ви повинні принаймні зробити це в виразі генератора. Дивіться відповідь KennyTM, як правильно це зробити без використання набору.
aaronasterling

1
sets.Set є "Застаріло з версії 2.6: Вбудовані типи наборів / заморожених заміняють цей модуль." (від docs.python.org/2/library/sets.html )
Моберг

21

Що того варте, це нещодавно з’явилось у списку розсилки python-ідеї . Виявляється, для цього вже є рецепт 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 Іншими словами, я не можу взяти на себе кредит за те, щоб знайти рішення - і не можу взяти кредит навіть за те, щоб його знайти .


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))у списку 1000000000 елементів займало 0,09 с, тоді як виконання allзаймало 0,9 с (у 10 разів довше).
Elliptica

2
Мені також подобається ця відповідь за її пітонічну простоту, окрім результативності, згаданої @Elliptica
NickBraunagel

11

Це інший варіант, швидший, ніж len(set(x))==1для довгих списків (використовує коротке замикання)

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

Це в 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)

Тут можна уникнути зайвого порівняння, використовуючи for elem in mylist[1:]. Сумніваєтесь, що це значно покращує швидкість, хоча, мабуть, elem[0] is elem[0]тому перекладач, можливо, може зробити це порівняння дуже швидко.
Брендан


4

Сумніваєтесь, що це "найбільш піфонічний", але щось на кшталт:

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

зробив би трюк.


1
Ваш forцикл можна зробити більш пітонічним 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))

Повертає кортеж, де перше значення булеве, якщо всі елементи однакові чи ні.


У коді є невелика помилка, як написано (спробуйте [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)))

як anyперестає шукати ітерабельний, як тільки знайде Trueумову.


Вам не потрібні додаткові дужки навколо виразу генератора, якщо це єдиний аргумент.
ninjagecko

так all(), чому б не використовувати all(x == seq[0] for x in seq)? виглядає більш пітонічно і повинен виконувати те саме
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

Працює в Python 2.4, який не має "всіх".


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:]))

2

Або використовувати diffметод numpy:

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

І зателефонувати:

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

Вихід:

True

Думаю, 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 минулого року.
mhwombat

Добре око! Я використовував ту саму структуру / 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.

(Не слід використовувати цей метод, оскільки він не порушиться, якщо він знайде нерівні значення, але продовжить вивчення всього списку. Він просто включений сюди як відповідь на повноту.)


Це не буде коротким замиканням. Чому ви віддаєте перевагу цьому, ніж іншому рішенню?
макс

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

Ваш перший код був явно невірним: reduce(lambda a,b:a==b, [2,2,2])урожай False... Я його відредагував, але цей спосіб вже не гарний
berdario

@berdario Тоді ти повинен був написати свою відповідь, а не змінювати те, що написав хтось інший. Якщо ви вважаєте, що ця відповідь була неправильною, ви можете прокоментувати її та / або спростувати її.
Горпик

3
Краще виправити щось не так, ніж залишити його там, щоб усі люди його прочитали, можливо, пропустивши коментарі, які пояснюють, чому це було не так
berdario

3
"Коли я повинен редагувати публікації?" "У будь-який час ви відчуваєте, що можете зробити публікацію кращою і схильні до цього. Редагування рекомендується!"
berdario

1

Змініть список на набір. Тоді, якщо розмір набору становить лише 1, вони повинні бути однаковими.

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

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 є обмеження рекурсії, яке в цьому випадку потрібно скорегувати. Наприклад, використовуючи це .


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

0

ви можете використовувати set. Це зробить набір і видалить повторювані елементи. Потім перевірте, чи не містить він більше 1 елемента.

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

Приклад:

>>> len(set([5, 5])) <= 1
True
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.