Определите, имеют ли 2 списка одни и те же элементы независимо от порядка?
Извините за простой вопрос, но мне трудно найти ответ.
Когда я сравниваю 2 списка, я хочу знать, являются ли они "равными", поскольку они имеют одинаковое содержимое, но в другом порядке.
Пример:
x = ['a', 'b']
y = ['b', 'a']
Я хочу, чтобы x == y
оценивался до True
.
Ответы
Ответ 1
Вы можете просто проверить, равны ли мультимножества с элементами x и y:
import collections
collections.Counter(x) == collections.Counter(y)
Для этого требуется, чтобы элементы были хешируемыми; runtime будет находиться в O(n)
, где n
- это размер списков.
Если элементы также уникальны, вы также можете преобразовать в множества (такая же асимптотическая продолжительность выполнения, на практике может быть немного быстрее):
set(x) == set(y)
Если элементы не являются хешируемыми, но сортируемыми, другой альтернативой (время выполнения в O(n log n)
) является
sorted(x) == sorted(y)
Если элементы не являются ни хешируемыми, ни сортируемыми, вы можете использовать следующую вспомогательную функцию. Обратите внимание, что он будет довольно медленным (O(n²)
) и обычно должен не использоваться вне эзотерического случая нераспаковываемых и несортируемых элементов.
def equal_ignore_order(a, b):
""" Use only when elements are neither hashable nor sortable! """
unmatched = list(b)
for element in a:
try:
unmatched.remove(element)
except ValueError:
return False
return not unmatched
Ответ 2
Определить, имеют ли 2 списка одни и те же элементы независимо от порядка?
Вывод из вашего примера:
x = ['a', 'b']
y = ['b', 'a']
что элементы списков не будут повторяться (они уникальны), а также hashable (какие строки и другие определенные неизменяемые объекты python), самый прямой и эффективный с точки зрения вычислений ответ Python встроенных множеств (которые являются семантически подобными математическими наборами, о которых вы, возможно, узнали в школе).
set(x) == set(y) # prefer this if elements are hashable
В случае, если элементы хешируются, но не являются уникальными, collections.Counter
также работает семантически как мультимножество, но он намного медленнее:
from collections import Counter
Counter(x) == Counter(y)
Предпочитаете использовать sorted
:
sorted(x) == sorted(y)
если элементы упорядочиваются. Это будет учитывать не уникальные или небезупречные обстоятельства, но это может быть намного медленнее, чем использование наборов.
Эмпирический эксперимент
В эмпирическом эксперименте делается вывод, что следует предпочесть set
, затем sorted
. Используйте только Counter
, если вам нужны другие вещи, такие как подсчеты или дальнейшее использование в качестве мультимножества.
Первая настройка:
import timeit
import random
from collections import Counter
data = [str(random.randint(0, 100000)) for i in xrange(100)]
data2 = data[:] # copy the list into a new one
def sets_equal():
return set(data) == set(data2)
def counters_equal():
return Counter(data) == Counter(data2)
def sorted_lists_equal():
return sorted(data) == sorted(data2)
И тестирование:
>>> min(timeit.repeat(sets_equal))
13.976069927215576
>>> min(timeit.repeat(counters_equal))
73.17287588119507
>>> min(timeit.repeat(sorted_lists_equal))
36.177085876464844
Итак, мы видим, что сравнение наборов является самым быстрым решением, а сравнение отсортированных списков является вторым самым быстрым.
Ответ 3
Это, похоже, работает, хотя, возможно, громоздко для больших списков.
>>> A = [0, 1]
>>> B = [1, 0]
>>> C = [0, 2]
>>> not sum([not i in A for i in B])
True
>>> not sum([not i in A for i in C])
False
>>>
Однако, если каждый список должен содержать все элементы другого, то приведенный выше код является проблематичным.
>>> A = [0, 1, 2]
>>> not sum([not i in A for i in B])
True
Проблема возникает, когда len(A) != len(B)
и в этом примере len(A) > len(B)
. Чтобы этого избежать, вы можете добавить еще одно выражение.
>>> not sum([not i in A for i in B]) if len(A) == len(B) else False
False
Еще одна вещь, я сравнил свое решение с timeit.repeat, при тех же условиях, которые использовал Аарон Холл в своем посте. Как и предполагалось, результаты разочаровывают. Мой метод - последний. set(x) == set(y)
это.
>>> def foocomprehend(): return not sum([not i in data for i in data2])
>>> min(timeit.repeat('fooset()', 'from __main__ import fooset, foocount, foocomprehend'))
25.2893661496
>>> min(timeit.repeat('foosort()', 'from __main__ import fooset, foocount, foocomprehend'))
94.3974742993
>>> min(timeit.repeat('foocomprehend()', 'from __main__ import fooset, foocount, foocomprehend'))
187.224562545
Ответ 4
Как упоминалось в комментариях выше, общий случай - боль. Это довольно легко, если все элементы хешируются или все элементы сортируются. Однако мне недавно пришлось попробовать решить общий случай. Вот мое решение. Я понял после публикации, что это дубликат решения выше, которое я пропустил на первом проходе. В любом случае, если вы используете срезы, а не list.remove(), вы можете сравнивать неизменяемые последовательности.
def sequences_contain_same_items(a, b):
for item in a:
try:
i = b.index(item)
except ValueError:
return False
b = b[:i] + b[i+1:]
return not b