困った時のitertools
Pythonにおいて、リストから重複を取り除く手段としてよく取り上げられるのが集合set()
に渡して取り除く方法である。
>>> a = [1, 3, 2, 3, 6, 2, 5] >>> a = list(set(a)) >>> a [1, 2, 3, 5, 6]
この手法の欠点は、集合の要素はハッシュ可能である必要がある。
つまり、上記の例のようなint
やstr
ならば集合で十分目的を達成できるが、
リストや辞書のようなハッシュ可能ではないオブジェクトからなるリストの重複を取り除くには工夫が必要である。
直近の業務で遭遇したのがこのような状況である。
レシピを調べる
この手の「リスト全体をなめて云々」するケースでまず調べるべきモジュールは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
重複という概念が成立するのであれば、個々のオブジェクトに応じて何かしらハッシュ可能なオブジェクトに変換する方法があるので応用範囲はそれなりに広いはずである。