何かを書き留める何か

数学や読んだ本について書く何かです。最近は社会人として生き残りの術を学ぶ日々です。

辞書のリストから重複した辞書を取り除く

困った時のitertools

Pythonにおいて、リストから重複を取り除く手段としてよく取り上げられるのが集合set()に渡して取り除く方法である。

>>> a = [1, 3, 2, 3, 6, 2, 5]
>>> a = list(set(a))
>>> a
[1, 2, 3, 5, 6]

この手法の欠点は、集合の要素はハッシュ可能である必要がある。 つまり、上記の例のようなintstrならば集合で十分目的を達成できるが、 リストや辞書のようなハッシュ可能ではないオブジェクトからなるリストの重複を取り除くには工夫が必要である。 直近の業務で遭遇したのがこのような状況である。

レシピを調べる

この手の「リスト全体をなめて云々」するケースでまず調べるべきモジュールはitertoolsモジュールである。 大抵のケースでモジュールのツールそのものか、ドキュメントのレシピ集にその答えがある。 今回のケースでは、レシピ集のunique_everseenで解決することがわかった。

以下は、unique_everseenを大いに参考にして実際に投入したヘルパー関数である。

from typing import List, Dict, Generator


def unique_dictionaries(list_of_dict: List[Dict]) -> Generator[Dict, None, None]:
    """辞書のリストから重複を取り除くジェネレータ"""
    seen = set()
    for element in list_of_dict:
        k = tuple(element.items())
        if k not in seen:
            seen.add(k)
            yield element

unique_everseenは任意のイテレータに対して動作するように書かれているが、今回は「辞書のリスト」とわかっているのでそれがわかるように単純化した。 また、「集合の要素はハッシュ可能」を踏まえ、辞書の中身をハッシュ可能なオブジェクトであるタプルに変換する処理を追加した。

>>> data = [{"a": 1, "b": 2}, {"a": 1, "b": 1}, {"a": 3, "b": 2}, {"a": 1, "b": 2}, {"a": 1, "b": 1}]
>>> assert list(unique_dictionaries(data)) == [{"a": 1, "b": 2}, {"a": 1, "b": 1}, {"a": 3, "b": 2}]  # should be True

重複という概念が成立するのであれば、個々のオブジェクトに応じて何かしらハッシュ可能なオブジェクトに変換する方法があるので応用範囲はそれなりに広いはずである。