Python

忘れがちだが、便利なPythonのデータ型 5つ

More than 1 year has passed since last update.

Pythonは標準ライブラリが非常に強力ですが、ライブラリが多すぎて把握しきれていない、把握しているけれども存在を忘れて車輪の再発明をしてしまう人も多いのではないでしょうか?少なくとも筆者はそんな人間の一人なので、自分用のメモも兼ねて、便利だが意識していないと使わない、Pythonの標準ライブラリに含まれているデータ型を幾つか紹介します。

DefaultDict

公式ドキュメント:
https://docs.python.jp/3/library/collections.html#collections.defaultdict

文字通り、デフォルトの値を設定できる辞書型。
これが便利なのは、いちいちキーが辞書に登録されているかを確認しなくていい点です。例えば、単語の出現回数を数え上げる時に、以下のような形で使えます:

>>> from collections import defaultdict
>>> d = defaultdict(int)
>>> string = "python is way way way way better than java"
>>> for w in string.split(" "):
...     d[w] += 1
...
>>> d.items()
dict_items([('better', 1), ('than', 1), ('python', 1), ('java', 1), ('way', 4), ('is', 1)])

ちなみに、一見するとわかりにくいのですが、defaultdictのコンストラクタは、デフォルト値ではなく、値を生成する関数(正確にはcallableなオブジェクト)を引数に取ります。なので、以下のようにすると、エラーを吐かれます。

>>> from collections import defaultdict
>>> d = defaultdict(0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: first argument must be callable or None

正しくは、

>>> d = defaultdict(lambda: 0)

または

>>> d = defaultdict(int)

となります(呼ばれた時に0を返すものならなんでもいい)。

Counter

公式ドキュメント:
https://docs.python.jp/3/library/collections.html#collections.Counter

単語を数え上げるだけなら、Counterクラスの方が圧倒的に便利です。
リストを渡すとその要素の数を、文字列を渡すと文字の数を、要素・文字ごとに数え上げてくれます。

>>> from collections import Counter
>>> c = Counter("python is way way way way better than C".split(" ")
>>> c
Counter({'way': 3, 'is': 1, 'better': 1, 'python': 1, 'C': 1, 'than': 1})
>>> c.most_common(1)
[('way', 3)]

ついつい自分で実装してしまいそうな処理ですが、せっかくこんな便利なデータ型があるので使いましょう。
見ての通り、最頻値や、最も頻繁に出現するn要素を簡単に取得できます。また、Counter同士の加減算もできるので、文章同士を比較したい時にもCounterは活躍します。

deque

公式ドキュメント:
https://docs.python.jp/3/library/collections.html#collections.deque

pythonは組み込みのlist型がすでにpopメソッドを持っているので、つい見逃してしまいがちですが、先頭からも最後尾からも、O(1)で要素を取り出し、削除することができる、dequeというデータ型があります。ちなみにlist型は、先頭からデータを削除することは、要素の移動が発生するので、O(n)のオペレーションです。また、dequeは初期化の際にmaxlenというパラメーターを取り、その場合はmaxlen以上の要素を追加しようとすると、自動的に先頭の要素から削除していってくれます。
dequeが活躍する場面は、長さが動的に、双方向から変化するデータ構造が必要になるときです。例えば、決まった長さの履歴を管理したい時なんかがその一例です。

>>> from collections import deque
>>> history = deque(maxlen=100)
>>> lines = open("python.txt")
>>> for line in lines:
...     if 'python' in line:
...         print(lines)
...     history.append(line)

さらに嬉しいことに、dequeはスレッドセーフです。生産者と消費者が別々のスレッドで複数存在するシステムにおけるデータ共有の手段として利用することもできます。

PriorityQueue

公式ドキュメント:
https://docs.python.jp/3/library/queue.html#queue.PriorityQueue

筆者も最近まで知りませんでしたが、Pythonには標準ライブラリにPriorityQueueが実装されています。以前heapqを使って実装した経験があるのですが、そこまでする必要がなかったということですね。
PriorityQueueは、探索のアルゴリズムを実装する際に役立ちます。幅優先探索、深さ優先探索、A*探索も、PriorityQueueの優先度が異なるだけの同一のアルゴリズムとみなすことができます。
ちなみにPriorityQueueもスレッドセーフです。

OrderedDict

公式ドキュメント:
https://docs.python.jp/3/library/collections.html#collections.OrderedDict

Pythonの辞書型の要素には、基本的に順番がありません。したがって、要素を動的につかする場合、辞書の要素に逐次的にアクセスした際に返って来る順序が不定です。さらに、辞書の要素をソートしようとしても、一手間かかってしまいます(というか辞書のままではソートできない)。
OrderedDictを使うと、簡単にこれらの処理が実現します。例えば、テストのスコアを辞書で管理し、様々な順番でスコアを表示する状況を考えます:

>>> from collections import OrderedDict
>>> d = OrderedDict({"Suzuki": 100, "Tanaka": 30, "Sato": 50})
>>> sorted(d.items(), key=lambda x: x[1])
[('Tanaka', 30), ('Sato', 50), ('Suzuki', 100)]
>>> sorted(d.items(), key=lambda x: x[0])
[('Sato', 50), ('Suzuki', 100), ('Tanaka', 30)]

上の例のように、点数順に並び替えることも、名前順に並び替えることも容易です。

まとめ

今回は自分が特に有用だと感じている5つのデータ型に絞って書きましたが、Pythonには様々な便利な標準ライブラリがあるので、目を通しておくことをお勧めします(筆者も未だに新しいことを発見することがありますし)。