Як ефективно порівняти два не упорядковані списки (а не набори) в Python?


141
a = [1, 2, 3, 1, 2, 3]
b = [3, 2, 1, 3, 2, 1]

a & b слід вважати рівними, оскільки вони мають абсолютно однакові елементи, лише в іншому порядку.

Справа в тому, що мої фактичні списки будуть складатися з об’єктів (мої екземпляри класу), а не цілих чисел.


7
Як порівнюються об'єкти?
Marcelo Cantos

2
який очікуваний розмір реальних списків? Чи будуть списки, що порівнюються, бути порівнянних розмірів чи дуже різними? Чи очікуєте, що більшість списків збігаються чи ні?
Дмитро Б.

Можна перевірити len()спершу.
сіра борода

Відповіді:


245

O (n) : Найкращим є метод Counter () (якщо ваші об'єкти є доступними):

def compare(s, t):
    return Counter(s) == Counter(t)

O (n log n) : Найкращим є наступний метод сортування () (якщо ваші об'єкти впорядковані):

def compare(s, t):
    return sorted(s) == sorted(t)

O (n * n) : Якщо об'єкти не є ні доступними, ні упорядкованими, ви можете використовувати рівність:

def compare(s, t):
    t = list(t)   # make a mutable copy
    try:
        for elem in s:
            t.remove(elem)
    except ValueError:
        return False
    return not t

1
Дякую. Я перетворив кожен об'єкт у рядок, потім застосував метод Counter ().
johndir

Ей, @ Реймонд, я нещодавно на інтерв'ю стикався з цим питанням, і використовував sorted(), правда, не знаючи про це Counter. Інтерв'юер наполягав, що існує більш ефективний метод, і я чітко намалював його. Після розширеного тестування в python 3 з timeitмодулем сортування послідовно виходить швидше за списками цілих чисел. У списках - 1 к. Предметів, приблизно на 1,5% повільніше, а в коротких списках - 10 предметів, на 7,5% повільніше. Думки?
arctelix

4
Для коротких списків аналіз big-O, як правило, не має значення, оскільки в таймінгах переважають постійні фактори. Що стосується більш довгих списків, я підозрюю, що з вашим бенчмаркінгом щось не так. За 100 вкладок з 5 повтореннями я отримую: 127 Usec для відсортованого та 42 для Counter (приблизно в 3 рази швидше). На 1000 входів з 5 повтореннями, лічильник в 4 рази швидший. python3.6 -m timeit -s 'from collections import Counter' -s 'from random import shuffle' -s 't=list(range(100)) * 5' -s 'shuffle(t)' -s 'u=t[:]' -s 'shuffle(u)' 'Counter(t)==Counter(u)'
Реймонд Хеттінгер

@Raymond Дійсно ми отримуємо різні результати. Я розмістив налаштування в чаті sorted vs counter. Мені дуже цікаво, що тут відбувається.
arctelix

4
Ні, дякую. Я не маю особливого інтересу до налагодження помилкових сценаріїв синхронізації. Тут багато чого відбувається (чистий python vs C код, тимчасовий додаток до рандомізованих даних проти напівзамовних даних, різні деталі реалізації в різних версіях, скільки дублікатів у даних тощо)
Реймонд Хеттінгер

16

Ви можете сортувати обидва:

sorted(a) == sorted(b)

Підрахунку роду також може бути більш ефективним (але це вимагає , щоб об'єкт бути hashable).

>>> from collections import Counter
>>> a = [1, 2, 3, 1, 2, 3]
>>> b = [3, 2, 1, 3, 2, 1]
>>> print (Counter(a) == Counter(b))
True

Лічильник використовує хешування, але об'єкти самі по собі не є незмінними. Вам просто потрібно реалізувати розумне __hash__, але це може бути неможливим для колекцій.
Йохен Рітцель

2
впорядковані також не будуть працювати для всього, наприклад, складні номериsorted([0, 1j])
Джон Ла Рой,

1
sorted () також не працює з наборами, де оператори порівняння були замінені для тестів підмножини / суперсети.
Реймонд Хеттінгер

12

Якщо ви знаєте, що елементи завжди доступні для використання, ви можете використовувати a, Counter()який є O (n)
Якщо ви знаєте, що елементи завжди можна сортувати, ви можете використовувати, sorted()який є O (n log n)

У загальному випадку ви не можете розраховувати на можливість сортування або на елементах, тому вам потрібен такий запасний варіант, який, на жаль, O (n ^ 2)

len(a)==len(b) and all(a.count(i)==b.count(i) for i in a)

5

Найкращий спосіб зробити це - сортування списків та порівняння їх. (Використання Counterне буде працювати з об'єктами, які не підлягають перегляду.) Це зрозуміло для цілих чисел:

sorted(a) == sorted(b)

Це стає трохи складніше з довільними предметами. Якщо ви дбаєте про ідентифікацію об'єкта, тобто про те, чи є однакові об’єкти в обох списках, ви можете використовувати id()функцію як ключ сортування.

sorted(a, key=id) == sorted(b, key==id)

(У Python 2.x насправді це не потрібно key= параметр, тому що ви можете порівнювати будь-який об’єкт з будь-яким об'єктом. Порядок впорядкування є довільним, але стабільним, тому він цілком працює для цієї мети; не має значення, в якому порядку розташовані об'єкти лише в тому, що впорядкування є однаковим для обох списків. Однак у Python 3 порівняння об'єктів різних типів заборонено за багатьох обставин - наприклад, ви не можете порівнювати рядки з цілими числами - тому якщо у вас будуть об'єкти різних типів, найкраще явно використовувати ідентифікатор об’єкта.)

Якщо ви хочете порівняти об’єкти у списку за значенням, з іншого боку, спочатку потрібно визначити, що означає "значення" для об'єктів. Тоді вам знадобиться якийсь спосіб забезпечити це як ключем (і для Python 3, як послідовний тип). Один з потенційних способів, який би працював для багатьох довільних об'єктів, - це сортування за їхніми ознаками repr(). Звичайно, це може витратити багато зайвого часу та repr()рядків для створення пам'яті для великих списків тощо.

sorted(a, key=repr) == sorted(b, key==repr)

Якщо об'єкти всі ваші власні типи, ви можете визначити __lt__()їх, щоб об’єкт знав, як порівнювати себе з іншими. Тоді ви можете просто сортувати їх і не турбуватися про key=параметр. Звичайно, ви також можете визначити __hash__()та використовувати Counter, що буде швидше.


4

https://docs.python.org/3.5/library/unittest.html#unittest.TestCase.assertCountEqual

assertCountEqual (перший, другий, msg = Немає)

Перевірте, що спочатку послідовність містить ті ж елементи, що і друга, незалежно від їх порядку. Якщо цього не відбувається, буде створено повідомлення про помилку, в якому перелічені відмінності між послідовностями.

Дублюючі елементи не ігноруються при порівнянні першого і другого. Він перевіряє, чи має кожен елемент однаковий підрахунок в обох послідовностях. Еквівалентно: assertEqual (Counter (список (перший)), Counter (список (другий))), але працює і з послідовностями нечутливих об'єктів.

Нове у версії 3.2.

або в 2.7: https://docs.python.org/2.7/library/unittest.html#unittest.TestCase.assertItemsEqual


2
(Що це додає до відповіді jarekwg ?)
сіра борода

3

Якщо список містить елементи, які не доступні для перегляду (наприклад, список об'єктів), можливо, ви зможете використовувати Counter Class та функцію id (), такі як:

from collections import Counter
...
if Counter(map(id,a)) == Counter(map(id,b)):
    print("Lists a and b contain the same objects")

2

Я сподіваюся, що наведений нижче код може працювати у вашому випадку: -

if ((len(a) == len(b)) and
   (all(i in a for i in b))):
    print 'True'
else:
    print 'False'

Це забезпечить, щоб усі елементи в обох списках a& bбули однаковими, незалежно від того, в одному порядку вони чи ні.

Для кращого розуміння зверніться до моєї відповіді в цьому питанні



1

Нехай списки a, b

def ass_equal(a,b):
try:
    map(lambda x: a.pop(a.index(x)), b) # try to remove all the elements of b from a, on fail, throw exception
    if len(a) == 0: # if a is empty, means that b has removed them all
        return True 
except:
    return False # b failed to remove some items from a

Не потрібно робити їх перемішуваними або сортувати.


1
Так, але це O (n ** 2), як зазначено в кількох інших плакатах, тому їх слід використовувати лише в тому випадку, якщо інші методи не працюють. Він також передбачає aпідтримку pop(є змінною) і index(є послідовністю). Реймонд не передбачає жодного, тоді як гніблер передбачає лише послідовність.
agf

0

Використання unittestмодуля дає чіткий та стандартний підхід.

import unittest

test_object = unittest.TestCase()
test_object.assertCountEqual(a, b)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.