Python Toolzの使い方 Itertoolz編

今回はToolzのAPIの中からItertoolzの関数をいくつか紹介します。

サンプルコードは”from toolz.itertoolz import *”の様に扱う関数をインポートしてある前提で書いてあります。

イテレータの出力から一部を取り出す関数

first(seq)

シーケンスの最初の要素を取得します。

result = first('ABC')
print(result)

【結果】

A

get(ind, seq, default=‘__no__default__’)

インデックスで指定したシーケンスや辞書の要素を取得します。

result = get(1, 'ABC')
print(result)

【結果】

B

文字列’ABC’ の要素番号が1の’B’を取得できます。

第1引数にリストを渡すと、複数の値をタプル型で取得できます。次の例では、(’ん’, ‘ご’)という値を取得できます。

get([1, 2], 'りんご') # 戻り値 ('ん', 'ご')

どんな値でも値を取得する方法に則ります。例えば、辞書の場合以下のようにキーを指定して値を取得できます。

phonebook = {
    'アリス':'555-1234',
    'けんじ':'555-5678',
    'さき':'555-9999',
}
get('アリス', phonebook) # 戻り値 555-1234

文字列の時と同様に、リストで複数取得することもできます。

get(['アリス', 'けんじ'], phonebook) # 戻り値 ('555-1234', '555-5678')

第3引数に値が見つからなかった場合のデフォルト値を指定できます。

get(['アリス', 'イチロー'], phonebook, None) # 戻り値 ('555-1234', None)

その他の関数は以下のようなものがあります。

関数説明
second(seq)シーケンスの2番目の要素を取得
last(seq)シーケンスの最後の要素を取得
nth(n, seq)シーケンスのn番目の要素を取得
tail(n, seq)シーケンスの最後のn個の要素を取得

以下の関数は上記の関数と、イテレーターを返す点が異なります。リスト型が欲しい場合は型変換をする必要があります。

関数説明
take(n, seq)シーケンスの最初のn個のイテレーターを取得
drop(n, seq)最初のn要素に続くイテレーターを取得
take_nth(n, seq)seqのn番目ごとの要素から成るイテレーターを取得

既存のイテレータに基づいて新しいイテレータを作る関数

accumulate(binop, seq, initial=‘__no__default__’)

バイナリ関数をシーケンスに繰り返し適用し、結果を蓄積します

“binop” は “binary operation”の略で、二項演算をする関数(以下、二項関数と言います)のことです。

シーケンスに二項関数を繰り返し適用し、結果を累積した結果を得られます。

from operator import add, mul
result = list(accumulate(add, [2, 4, 6, 8]))
result2 = list(accumulate(mul, [2, 4, 6, 8]))
print(result)
print(result2)

【結果】

[2, 6, 12, 20]
[2, 8, 48, 384]

初期値を設定することもできます。

list(accumulate(add, [2, 4, 6], -1))

注)itertoolsにもaccumulate関数がありますが引数が異なります。関数型プログラミングにおいては、itertoolzのaccumulate関数の方が扱いやすいです。

イテレータの要素を引数として扱う関数

concat(seqs)

0個以上のイテラブルを連結します。

無限長のシーケンスを引数に指定する場合は最後に指定する必要があります。

この関数はitertools.chain.from_iterableと同等の処理をします。

result = list(concat([[], [1], [2, 3]]))
print(result)

【結果】

[1, 2, 3]

 concatv(*seqs)というconcatの引数が可変のバージョンもあります。

join(leftkey, leftseq, rightkey, rightseq, left_default=’__no__default__’, right_default=’__no__default__’)

2つのシーケンスを結合します。

SQL文のjoin文と同様の機能です。

leftseqのシーケンスは全て評価され、メモリに配置されます。rightseqのシーケンスは遅延評価されるため任意のサイズにすることができます。

friends = [
    ('アリス', 'イチロウ'),
    ('アリス', 'リン'),
    ('イチロー', 'アリス'),
    ('リン', 'アリス'),
    ('リン', 'アリス'),
    ('リン', 'イチロウ')
]

cities = [
    ('アリス', 'ニューヨーク'),
    ('アリス', 'シカゴ'),
    ('さとし', 'シドニー'),
    ('イチロウ', 'パリ'),
    ('イチロウ', 'ベルリン'),
    ('リン', '上海')
]

join関数を使って「アリス、イチロー、リンの友だちはどこに住んでいるか?」を求めます。

result = join(second, friends, first, cities)
for ((a, b), (c, d)) in sorted(unique(result)):
    print((a, d))

【結果】

('アリス', 'パリ')
('アリス', 'ベルリン')
('アリス', '上海')
('イチロー', 'シカゴ')
('イチロー', 'ニューヨーク')
('リン', 'シカゴ')
('リン', 'ニューヨーク')
('リン', 'パリ')
('リン', 'ベルリン')

left_defaultやright_defaultで外部結合を指定します。以下は、一致しない要素がNoneとペアになっている完全外部結合です。

identity = lambda x: x
list(join(identity, [1, 2, 3],
    identity, [2, 3, 4],
    left_default=None, right_default=None))

【結果】

[(2, 2), (3, 3), (None, 4), (1, None)]

通常、leftkeyやrightkeyはシーケンスに適用される呼び出し可能なオブジェクトです。

もし、明らかにキーが呼び出し可能でない場合、インデックスの作成を意図していると見なされます。たとえば、以下の書き方です。

# result = join(second, friends, first, cities)
result = join(1, friends, 0, cities)

イテレータをグループ分けする関数

frequencies

シーケンスの各値の出現回数をカウントします。

 統計を取りたい時などに使えます。

result = frequencies(['ネコ', 'ネコ', '犬', '馬', '馬', 'ネコ']) 
print(result)

【結果】

{'ネコ': 3, '犬': 1, '馬': 2}

groupby(key, seq)

keyに指定された関数で、シーケンスをグループ化します。

以下の例では名前を文字数でグループ化します。len関数を使って文字数をカウントしています。

names = ['アリス', 'イチロー', 'シャーロット', 'ライラ', 'エド', 'たけし']
result = groupby(len, names)
print(result)

【結果】

{3: ['アリス', 'ライラ', 'たけし'], 4: ['イチロー'], 6: ['シャーロット'], 2: ['エド']}

次の例は数値を整数と偶数でグループ化します。出力結果のキーはTrue、または、Falseになります。

iseven = lambda x: x % 2 == 0
result = groupby(iseven, [1, 2, 3, 4, 5, 6, 7, 8])
print(result)

【結果】

{False: [1, 3, 5, 7], True: [2, 4, 6, 8]}

countby(key, seq)

keyの関数に基づいてシーケンスの要素数を数えます。countbyは上記で説明した関数と異なり、toolz.recipesモジュールの関数です。

from toolz.recipes import countby
result = countby(len, ['cat', 'mouse', 'dog'])
print(result)

【結果】

{3: 2, 5: 1}

次の例はiseven関数の結果がTrueの要素数と、Falseの要素数を数えています。

def iseven(x): return x % 2 == 0
result = countby(iseven, [1, 2, 3])
print(result)

【結果】

{True: 1, False: 2}

まとめ

今回はtoolzのitertoolzの中から便利な関数をいくつか紹介しました。

イテレータを処理するシーンは非常に多いです。itertoolsやitertoolzの関数を有効活用して、バグが入りにくい堅牢なコーディングができると良いと思います。

また、functoolzやdicttoolzと組み合わせることで、更に複雑な処理も簡潔に書くことができる様になります。

スポンサーリンク
スポンサーリンク

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です