HOWTO · Python

Python でリストを比較する方法

順序と重複の扱いに応じて、`==`、`Counter`、集合演算、または順序を保つリスト内包表記で Python のリストを正しく比較します。

このページの内容

Python の 2 つのリストを比較するときは、まず順序と重複数に意味があるかを決めます。順序も含めた完全一致には ==、順序を無視しつつ重複数を保つ比較には Counter、一意な値の共通部分や差分には set()、入力リストの順序で結果を得るにはリスト内包表記を使います。

以下の例はすべて Python 標準ライブラリだけを使い、Python 3.14.7 で検証しています。プロジェクト固有の互換性要件がなければ、これらの方法はサポート対象の以前の Python 3 バージョンでも利用できます。

目的 推奨する方法 順序が重要か 重複数が重要か 要素の要件
リストが完全に等しいか調べる left == right はい はい 要素が等値比較をサポートすること
順序を無視して等しいか調べる Counter(left) == Counter(right) いいえ はい 要素がハッシュ可能であること
一意な値を比較する set() の演算 いいえ いいえ 要素がハッシュ可能であること
リスト順で共通項目を残す メンバーシップ集合とリスト内包表記 出力順のみ 走査したリストの重複は残る メンバーシップ側の値がハッシュ可能であること
共通部分や差分の個数を残す Counter の演算 いいえ はい 要素がハッシュ可能であること

これらは、どれか 1 つが常に最速という競争ではなく、別々の問いに答える選択肢です。等値比較は 1 つの真偽値を返し、集合と Counter の演算はコレクションのような結果を作り、内包表記は新しいリストの形と順序を制御します。性能を考える前に必要な意味論を決めてください。高速化のためだけに表現を変えると、答えそのものも変わることがあります。

== で完全に順序付けられたリストを比較する

Python のリストの等値比較は、対応する要素を左から右へ比較します。2 つのリストが等しいのは、長さが同じで、各位置の値が等しく、したがって順序も同じ場合だけです。処理手順、順位付きの結果、順序付きイベントのようにリストが列を表すときに最も分かりやすい選択です。

"""Verify that list equality considers both values and order."""

first = [1, 2, 3]
same = [1, 2, 3]
reordered = [3, 2, 1]

print(first == same)
print(first == reordered)
True
False

両方のリストに同じ一意な値が含まれていても、2 回目の比較は False です。直接の等値比較はハッシュテーブルを作らないため、入れ子のリストのようなハッシュ不可能な要素にも使えます。

ただし、要素の比較は各値の等値比較規則に従います。たとえばカスタムオブジェクトのリストを比較すると、そのオブジェクトの __eq__ 実装が呼び出されることがあります。この方法は入れ子の構造のどこが違うかを再帰的に報告せず、最終的な真偽値だけを返します。

Counter で重複を保持して順序なしのリストを比較する

順序を無視しても各値の出現回数が重要なら、collections.Counter を使います。Counter はハッシュ可能な各項目をその個数に対応付けるため、2 つのカウンターが等しいのは、両方のリストに同じ値が同じ重複数だけ含まれる場合だけです。

"""Compare unordered lists with and without duplicate multiplicity."""

from collections import Counter

left = [1, 2, 2, 3]
reordered = [3, 2, 1, 2]
fewer_duplicates = [3, 2, 1]

print(Counter(left) == Counter(reordered))
print(Counter(left) == Counter(fewer_duplicates))
print(set(left) == set(fewer_duplicates))
True
False
True

最後の 2 行は重要な境界を示しています。Counter は 2 つ目の 2 がないことを検出しますが、集合の比較は重複を捨てるため等しいと報告します。繰り返し値に意味がある場合、set(left) == set(right) を代わりに使ってはいけません。

組み込みの sorted() でも、sorted(left) == sorted(right) により重複を考慮した比較ができますが、リスト内のすべての値を相互に順序付けできる場合に限られます。ソート済みリストを確保してソート処理も行うため、順序なしの頻度比較を表すには通常 Counter の方が直接的です。後でソート済みの列も必要なら、ソートが役立つ場合があります。

set() で一意の値、共通部分、差分を比較する

異なる値だけが重要なときは、リストを集合に変換します。集合の共通部分(&)は両方の入力にある値を返します。差分(-)は方向を持ち、対称差(^)は片方にしかない値を返します。

次の検証済みの例では、集合の結果を表示する部分だけをソートして、出力を決定的にしています。比較そのものは集合に順序を与えません。

集合への変換は、メンバーシップ判定が多い処理では一度だけ検索用の構造を作れるため有用です。ただし、出力で位置や重複数を保つ必要がある場合には適しません。また、集合の表示順には依存しないでください。値を相互に順序付けでき、安定した表示が必要なときだけ明示的にソートします。

"""Verify unique-value, order-preserving, and multiplicity-aware differences."""

from collections import Counter

left = [1, 2, 2, 3, 4]
right = [2, 3, 3, 5]
left_set = set(left)
right_set = set(right)

print(sorted(left_set & right_set))
print(sorted(left_set - right_set))
print(sorted(right_set - left_set))

right_members = set(right)
print([item for item in left if item in right_members])

left_counts = Counter(left)
right_counts = Counter(right)
print(sorted((left_counts & right_counts).elements()))
print(sorted((left_counts - right_counts).elements()))
print(sorted((right_counts - left_counts).elements()))
[2, 3]
[1, 4]
[5]
[2, 2, 3]
[2, 3]
[1, 2, 4]
[3, 5]

最初の 3 行の出力は、一意な値の共通部分と方向付きの差分を示します。片方の集合にだけ現れる値も必要なら、left_set ^ right_set を使います。この入力では 1、4、5 という一意な値になりますが、順序は未指定です。

リスト内包表記で結果の順序を保持する

集合の結果は最初のリストの順序を保持しません。出力順が重要なら、メンバーシップ集合を一度作り、順序を残したいリストを走査します。前の例では、内包表記のたびに 2 つ目のリストを作り直したり線形走査したりせず、right_members を一度だけ作っています。

4 行目の出力 [2, 2, 3] は、left の順序と重複した出現を保持します。この結果は意図的に非対称です。代わりに right を走査すれば、right の順序と重複が保持されます。メンバーシップ側の値がハッシュ不可能なら、もう一方のリストに対する直接の等値比較によるメンバーシップ判定を使えますが、大きな入力ではリストを繰り返し走査するコストが増えることを理解してください。

Counter で共通部分と差分の個数を保持する

集合演算は一意な値についての問いに答えます。Counter の演算は、個数を保ちながら同じ種類の問いに答えます。

  • left_counts & right_counts は、共有する各値について正の最小個数を残します。
  • left_counts - right_counts は個数を減算し、正の残りだけを残します。
  • right_counts - left_counts は、逆方向の差分を返します。
  • .elements() は、結果の個数を個々の値へ展開します。

検証済みの例で、多重集合の共通部分は [2, 3] であり、[2, 2, 3] ではありません。2 は左側に 2 回ありますが右側には 1 回しかないので、最小個数は 1 です。左側に残る方向付きの差分は [1, 2, 4]、右側に残る差分は [3, 5] です。

在庫、投票、繰り返しを含むタグなど、数量に意味があるデータにはこの方法を選びます。通常の集合はその情報を黙って消してしまいます。

Counter の算術演算では、これらの多重集合の結果から個数が 0 または負のものが除かれます。この動作は残りの項目を求める問いには便利ですが、考えられるすべてのキーに対する通常の数値の減算とは異なります。アプリケーションで 0 や負の残高も保持する必要がある場合は、カウンターを直接確認してください。

ハッシュ不可能な値を扱い、正しい方法を選ぶ

set() と Counter はどちらもハッシュ可能な要素を必要とします。リストは変更可能でハッシュ不可能なので、入れ子のリストでは TypeError が発生します。一方、直接のリスト等値比較は引き続き使えます。

"""Show the unhashable-item boundary of set and Counter comparisons."""

from collections import Counter

left = [[1], [2]]
right = [[1], [2]]

print(left == right)
for name, operation in (("set", set), ("Counter", Counter)):
    try:
        operation(left)
    except TypeError as error:
        print(f"{name}: {error}")
True
set: cannot use 'list' as a set element (unhashable type: 'list')
Counter: unhashable type: 'list'

正確な set の診断メッセージは Python のリリースによって異なることがあるため、例外型と要素がハッシュ不可能である原因を安定した挙動として扱ってください。入れ子の列を不変の形で表せるなら、集合や Counter を使う前に各内側のリストをタプルへ変換します。そうでなければ、必要な結果に適した等値比較ベースの方法を使います。

要するに、データの意味に合う表現を選んでください。順序付きの列には ==、順序を無視する多重集合には Counter、一意な値には set()、フィルターした出力には順序を保つ内包表記を使います。順序と重複の規則を明示すれば、正しそうに見えて別の問いに答えてしまう比較を防げます。