Pythonで2つの順序なしリスト(セットではない)を効率的に比較する方法は?


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

aとbは要素がまったく同じで、順序が異なるため、等しいと見なす必要があります。

実は、私の実際のリストは、整数ではなくオブジェクト(私のクラスインスタンス)で構成されています。


7
オブジェクトはどのように比較されますか?
Marcelo Cantos、2011年

2
実際のリストの予想サイズは何ですか?比較されているリストは、同等のサイズですか、それとも大きく異なりますか?ほとんどのリストが一致するかどうかを予想しますか?
ドミトリーB.

len()最初にをチェックするかもしれません。
greybeard 2017

回答:


245

O(n)Counter()メソッドが最適です(オブジェクトがハッシュ可能な場合):

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

O(n log n)Sorted()メソッドが次に最適です(オブジェクトが順序付け可能な場合):

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

@Raymondさん、最近インタビューでこの質問に遭遇しましたがsorted()、確かに知りませんでしたCounter。インタビュアーはもっと効率的な方法があると主張し、明らかに私は空白を描きました。timeitモジュールを使用したpython 3での広範なテストの後、ソートされた整数のリストで一貫して速く出てきます。1k個のアイテムのリストでは、約1.5%遅く、短いリストでは、10個のアイテム、7.5%遅くなります。考え?
arctelix 2016年

4
短いリストの場合、タイミングが一定の要因によって支配されるため、big-O分析は通常無関係です。長いリストについては、ベンチマークに問題があると思います。それぞれが5回の繰り返しで構成される100 intの場合、次のようになります。ソート用に127 usec、カウンター用に42(約3倍高速)。5回の繰り返しで1,000整数で、カウンターは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)'
レイモンドヘッティンガー2016年

@Raymond確かに私たちは異なる結果を得ています。私は自分のセットアップをチャットルームに投稿しsorted vs counterました..ここで何が起こっているのか非常に興味があります。
arctelix 2016年

4
結構です。スプリアスタイミングスクリプトのデバッグにはあまり興味がありません。ここでは多くのことが行われています(純粋なpythonとCのコード、ランダム化されたデータと半順序のデータに適用されたティムソート、バージョン間での異なる実装の詳細、データ内の重複の数など)
Raymond Hettinger

16

両方を並べ替えることができます。

sorted(a) == sorted(b)

カウンティングソートはまた、より効率的かもしれない(しかし、それはハッシュ可能であることをオブジェクトが必要です)。

>>> 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__、コレクションではそれが不可能な場合があります。
Jochen Ritzel、2011年

2
ソートはすべてに対して機能しません。たとえば、複素数sorted([0, 1j])
John La Rooy

1
また、sorted()は、サブセット/スーパーセットテストで比較演算子がオーバーライドされているセットでは機能しません。
レイモンドヘッティンガー、2011年

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では、さまざまなタイプのオブジェクトを比較することは、多くの状況で許可されていません。たとえば、文字列を整数と比較できないため、オブジェクトがある場合オブジェクトのIDを明示的に使用するのが最適です。)

一方、リスト内のオブジェクトをで比較する場合は、最初にオブジェクトの「値」の意味を定義する必要があります。次に、それをキーとして(そしてPython 3の場合は一貫した型として)提供する方法が必要になります。多くの任意のオブジェクトで機能する1つの潜在的な方法は、それらのオブジェクトで並べ替えること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(最初、2番目、msg =なし)

順序に関係なく、最初にシーケンスに2番目と同じ要素が含まれていることをテストします。そうでない場合、シーケンス間の違いをリストするエラーメッセージが生成されます。

1番目と2番目を比較するときに、重複する要素は無視されません。各要素が両方のシーケンスで同じ数を持っているかどうかを確認します。assertEqual(Counter(list(first))、Counter(list(second)))と同等ですが、ハッシュ化できないオブジェクトのシーケンスでも機能します。

バージョン3.2の新機能。

または2.7:https : //docs.python.org/2.7/library/unittest.html#unittest.TestCase.assertItemsEqual


2
(これはjarekwgの答えに何を追加しますか?)
greybeard

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 '20年

0

unittestモジュールを使用すると、クリーンで標準的なアプローチが得られます。

import unittest

test_object = unittest.TestCase()
test_object.assertCountEqual(a, b)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.