Определите, имеют ли 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