【Python】個数も含めて2つの一覧を比較する:Counterで過不足を調べる

PythonのTopに戻る


二つの一覧を比較するとき、同じ要素の個数まで重要なら集合ではなくcollections.Counterを使う。必要な部品と手元の部品をそれぞれ数えれば、不足と余剰を簡単に求められる。ただし、Counter同士の通常の引き算は、0以下の結果を出力に残さない。符号付きの差を見たい場合はsubtractを使い分ける。

集合では重複した個数が消える

ボルトが2本必要なのに1本しかない状況では、必要品にも保有品にもボルトが存在するだけでは十分ではない。集合へ変換すると、同じ名前の重複は一つにまとまるため、何本不足しているかを計算できなくなる。

Counterは要素をキー、個数を値として持つ辞書の一種である。リストから作れば出現回数を数えられる。この例では部品名を文字列にそろえ、同じ名前は同じ部品を意味するものとして扱う。規格やサイズが違う部品を同名にまとめないことも重要である。

不足・余剰・符号付き差を計算する

example.py

from collections import Counter

needed = Counter(["bolt", "bolt", "nut"])
available = Counter(["bolt", "nut", "nut", "washer"])
missing = needed - available
extra = available - needed
signed = needed.copy()
signed.subtract(available)

print("missing:", dict(sorted(missing.items())))
print("extra:", dict(sorted(extra.items())))
print("signed:", dict(sorted(signed.items())))
print("items to add:", sorted(missing.elements()))
assert missing == Counter({"bolt": 1})
assert extra == Counter({"nut": 1, "washer": 1})
assert dict(signed) == {"bolt": 1, "nut": -1, "washer": -1}
assert needed == Counter({"bolt": 2, "nut": 1})
assert needed["unknown"] == 0 and "unknown" not in needed
assert needed - needed == Counter()
assert Counter() - available == Counter()

実行結果

missing: {'bolt': 1}
extra: {'nut': 1, 'washer': 1}
signed: {'bolt': 1, 'nut': -1, 'washer': -1}
items to add: ['bolt']

needed – availableは不足数だけを、available – neededは余剰数だけを残す。どちらも正の個数だけになるので、用意する品物の一覧を出す用途に向いている。対してsubtractは負の値を残し、必要数から見た差を一つの表として確認できる。

引き算とsubtractは意味が違う

Counterの-演算は、通常の辞書の値をすべて差し引いてそのまま残す操作とは異なる。差が0や負になるキーは結果から省かれる。そのため、一度不足だけにした結果から、元の余剰情報を復元することはできない。

signedはneeded.copy()から作っているので、subtractしても元のneededは変わらない。一方、needed.subtract(available)と直接書けば、必要数のCounter自体が更新される。元の一覧を後でも使うなら、どのオブジェクトを変更しているかを意識しよう。

存在しないキーと0のキーを区別する

Counterで存在しないキーを[]で取得すると0になる。通常の辞書のように、その読み取りだけでKeyErrorにはならない。ただし、0という値のキーを明示的に保存した場合、キーそのものが消えるわけではない。キーの存在と個数が正であることは別に考える。

結果を表示するときは、この例のようにキーを並べ替えると、実行ごとの見比べが容易になる。most_commonで多い順にする方法もあるが、同数のときの順序をどう見せたいかは別途確認する。レポート用の順序を集計値と混同しないようにしたい。

数量の条件を確認してから使う

この例は非負の整数個数を前提にしている。Counterそのものは負の値なども保持できるが、実在庫として負数が現れたなら、入力や更新履歴に問題がないかを確認する。差分を表す負数と、在庫そのものの負数は意味が異なる。

elements()を使うと、正の整数個数を要素の繰り返しへ戻せる。例では不足分の購入候補を一覧に戻している。ただし、数量が非常に大きい場合は同じ文字列を大量に並べることになるため、名前と個数のまま扱った方がよい。

部品名をそろえる作業を先に行う

前後の空白や表記の揺れがあると、同じ部品のつもりでも別のキーとして数えられる。正規化をする場合は、識別に必要なサイズや記号を消さないようにする。単純に大文字・小文字を統一するだけでよいかも、扱うデータの規則による。

入力がすでに数量の辞書なら、Counterへそのまま数量を渡すこともできる。各数量が正しい型・単位であるかを確認してから演算する。数の比較では、両側が同じ部品の単位を使っていることが前提になる。

不足なし、余剰なし、片方が空、同じ部品が複数というケースを確かめると、集合との差がよく分かる。存在だけを知りたいなら集合、個数も知りたいならCounterという使い分けが基本である。

動作確認と参考資料

掲載例はLinux・CPython 3.12.14で動作確認した。OS固有のコマンドや環境ごとに変わるパスは、本文中の条件を確認して使ってほしい。

関連するTips


PythonのTopに戻る