web-dev-qa-db-ja.com

順序に関係なく、2つのリストに同じ要素があるかどうかを判断しますか?

簡単な質問で申し訳ありませんが、答えを見つけるのに苦労しています。

2つのリストを比較するとき、リストの内容が同じで順序が異なるという点で、それらが「等しい」かどうかを知りたいです。

例:

x = ['a', 'b']
y = ['b', 'a']

x == yTrueに評価したい。

101
toofly

Xとyの要素を持つマルチセットが等しいかどうかを簡単に確認できます。

import collections
collections.Counter(x) == collections.Counter(y)

これには、要素がハッシュ可能である必要があります。ランタイムはO(n)になります。nはリストのサイズです。

要素も一意である場合は、セットに変換することもできます(同じ漸近的なランタイム、実際には少し速くなる場合があります)。

set(x) == set(y)

要素がハッシュ可能ではなく、ソート可能である場合、別の代替(O(n log n)のランタイム)は

sorted(x) == sorted(y)

要素がハッシュ可能でもソート可能でもない場合、次のヘルパー関数を使用できます。非常に遅い(O(n²))ため、一般的にnotは、ハッシュ不可能でソート不可能な要素の難解なケースの外で使用されることに注意してください。

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

順序に関係なく、2つのリストに同じ要素があるかどうかを判断しますか?

あなたの例から推測する:

x = ['a', 'b']
y = ['b', 'a']

リストの要素は繰り返されない(一意である)だけでなく、ハッシュ可能(文字列およびその他の特定の不変の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

したがって、セットの比較が最速のソリューションであり、ソートされたリストの比較が2番目に速いことがわかります。

18
Aaron Hall

これは機能しているようですが、大きなリストの場合は面倒かもしれません。

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

ただし、各リストmustにotherのすべての要素が含まれている場合、上記のコードには問題があります。

>>> A = [0, 1, 2]
>>> not sum([not i in A for i in B])
True

この問題は、len(A) != len(B)、この例ではlen(A) > len(B)のときに発生します。これを避けるために、もう1つのステートメントを追加できます。

>>> not sum([not i in A for i in B]) if len(A) == len(B) else False
False

もう1つ、アーロンホールの投稿で使用したのと同じ条件下で、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
1
blahreport

上記のコメントで述べたように、一般的な場合は痛みです。すべてのアイテムがハッシュ可能またはすべてのアイテムがソート可能であれば、かなり簡単です。しかし、私は最近、一般的なケースを解決しようとする必要がありました。これが私の解決策です。投稿した後、これは最初のパスで見逃した上記のソリューションの複製であることに気付きました。とにかく、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
0
Grahame