Pythonのitertools – compress()、dropwhile()、takewhile()、groupby()

このガイドでは、Python の itertools モジュールを使ってイテレータのパワーを利用する方法を見ていきます。

itertools` モジュールは、高速でメモリ効率の良いイテレータを作成するためのインターフェイスを提供します。

これらのイテレータは、無限、組合せ、または終端とすることができます。

イテレータとイテラブルの比較

Iterator は、Iterable (コンテナ) の項目を一定の順序で案内 (反復) することができる知的なポインターです。

色のリストと整数のリストを考えてみましょう。

colors = ['red', 'blue', 'pink']
ints = [1, 3, 5, 4, 2]


これらのリストを特定の順序で定義しても、メモリに配置するときに同じ順序で格納する必要はありません。

iterators:  it1                 it2 
             V                   V
memory:     red   4   2   blue   1    3    pink   5


もし、メモリを順番通りに通過したら、 colors 配列の 2 番目の要素は 4 になってしまうでしょう。

イテレータの仕事は、メモリ上のどこにあっても、リストの次の要素を見つけることです。

これは、イテレータが指し示す次の要素を返す next() メソッドによって行われます。

例えば、 it1 はアクセス可能なメモリを探し回って blue を返し、 it23 を返します。

イテレータの大きな特徴は、それぞれのイテレータブルでどのように要素を検索するかを定義できることです。

例えば、すべての奇数をスキップして、そのサブセットを返すように要求することができます。

これは、カスタムの next() メソッドを実装するか、ビルトインの itertools を使用することで、様々な方法でオブジェクトを反復処理するための特定のイテレータを生成することで実現できます。

これから紹介するイテレーションツールは以下の通りです。

  • compress()
  • dropwhile()
  • takewhile()
  • groupby()

これらのイテレータ構築関数(イテレータを生成する関数)は、それぞれ単独で使うこともできますし、組み合わせて使うこともできます。

compress()関数

compress(data, selector)関数は、ブール値のリスト -selectorに従ってdataから選択的に値を取り出すイテレータを作成する。

もし、dataの値がselectorリスト中のTrue` に一致する場合は、その値が選択され、一致しない場合はスキップされる。

dataselectorが同じサイズでない場合、compress()dataまたはselector` のリストのいずれかを使い切った時点で停止する。

# Importing the compress tool
from itertools import compress


cars = ['Audi', 'Volvo', 'Benz', 
        'BMW', 'Nissan', 'Mazda',
        'Ford']

selector = [True, True, False, False, 
            False, True, False]


# This makes an iterator that filters elements, 
# from data, for which selector values amount to True
my_cars = compress(cars, selector)


for each in my_cars:
    print(each)


この結果

Audi
Volvo
Mazda


selectorには、10` のリストや、任意の真偽値を指定することができます。

通常、これらの真偽値のリストは、次のような何らかの条件によって取得されます。

int_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
boolean_list = [True if x % 2 == 0 else False for x in int_list]


# OR


boolean_list = [1 if x % 2 == 0 else 0 for x in int_list]


print(boolean_list)


ここでは、偶数ごとに True を持つ boolean_list を生成しています。

[False, True, False, True, False, True, False, True, False, True]


# OR


[0, 1, 0, 1, 0, 1, 0, 1, 0, 1]


一般的に、物事を短くするために、新しい変数に結果を代入せずに compress() ツールや他のツールを使用することがあります。

import itertools


word =  'STACKABUSE'
selector = [1, 0, 1, 0, 0, 0, 0, 1, 1, 1]


for each in itertools.compress(word, selector ):
    print(each)


その結果、次のようになります。

S
A 
U 
S 
E


さらに技術的には、selectorの値を任意の真偽値で混在させることができます。

from itertools import compress


cars = ['Audi', 'Volvo', 'Benz',
        'BMW', 'Nissan', 'Mazda', 'Ford']


# Empty string is falsy, non empty is truthy
selector = [True, 1, 0, 0, '', 1, 'string']


for each in compress(cars, selector):
    print(each)


出力は次のようになります。

Audi
Volvo
Mazda
Ford


しかし、このようにリンゴとナシを混ぜることは推奨されないことに注意してください。

dropwhile()関数

dropwhile(criteria, sequence)関数は、sequenceに含まれる全ての要素を削除する (読み飛ばす) イテレータを作成し、criteria関数に渡すとTrue` を返します。

この criteria 関数は通常ラムダ関数ですが、必ずしもラムダ関数である必要はありません。

通常、単純な関数であればラムダ関数に短縮されますが、複雑な関数の場合はそうではありません。

from itertools import dropwhile


int_list = [0, 1, 2, 3, 4, 5, 6]
result = list(dropwhile(lambda x : x < 3, int_list))


print(result)


このラムダ関数が与えられたとき、3より小さい値を持つすべての要素は True を返すので、3より小さい要素はすべてスキップされます。

criteriaがtrueの間、それらはドロップされます。

[3, 4, 5, 6]


ラムダ関数の代わりに、もっと複雑な関数を定義して、その関数の参照を渡すこともできます。

from itertools import dropwhile


def doesnt_contain_character(str):
    substring = 'a'
    if substring in str:
        return False
    else:
        return True

string_list = ['lorem', 'ipsum', 'dolor', 'sit', 'amet']
print(list(dropwhile(doesnt_contain_character, string_list)))


例えば、このメソッドは文字列に substring (この場合は a) が含まれないかどうかをチェックする。

与えられた文字列が a を含んでいれば False が返され、含んでいなければ True が返されます。

したがって、 amet までのすべての単語は True を返し、結果から取り除かれる。

['amet']


しかし、条件が満たされない場合は、それ以降のすべての要素が取り込まれます。

この例では、 'amet' 要素以降は criteria に関係なく、すべて含まれることになります。

from itertools import dropwhile


def doesnt_contain_character(str):
    substring = 'a'
    if substring in str:
        return False
    else:
        return True

string_list = ['lorem', 'ipsum', 'dolor', 'sit', 'amet', 'a', 'b']
print(list(dropwhile(doesnt_contain_character, string_list)))


これは 'amet' までの要素を削除し、それ以降は削除しないようにします。

['amet', 'a', 'b']


takewhile()関数

takewhile(criteria, sequence)関数はdropwhile()` の対極にある関数です。

この関数では、関数が失敗しないすべての要素を保存します。

先ほどの例を、ある単語がある文字を含むかどうかをチェックするように書き換えてみましょう。

調べてみましょう。

from itertools import takewhile


def contains_character(str):
    substring = 'o'
    if substring in str:
        return True
    else:
        return False

string_list = ['lorem', 'ipsum', 'dolor', 'sit', 'amet']
print(list(takewhile(contains_character, string_list)))


['lorem']


2番目の要素で criteria が失敗するので、 'dolor' にも o という文字が含まれているにもかかわらず – それは考慮されません。

groupby()関数

groupby(iterable, key_function)は、同じグループに属する連続した要素を束ねたイテレータを生成する関数である。

ある要素がグループに属するかどうかは、key_function` に依存する。

この関数は、各要素のキー値を計算する。

この場合のキー値とは、特定のグループの ID のことである。

クラスタは終了し、 key_function が新しい id を返すと、以前からあったものであっても新しく作成される。

もし key_function が指定されていない場合、デフォルトは ID 関数である。

ただし、重複した値であっても、 – 別のクラスタに分離されている場合は、クラスタ化されないことに注意する必要があります。

from itertools import groupby


word = "aaabbbccaabbbbb"


for key, group in groupby(word):
    print(key, list(group))


直感的には、abのインスタンスはすべて一緒にクラスタリングされると思うかもしれませんが、間にクラスタがあるため、それぞれ別のクラスタに分離されます。

a ['a', 'a', 'a'] 
b ['b', 'b', 'b'] 
c ['c', 'c'] 
a ['a', 'a'] 
b ['b', 'b', 'b', 'b', 'b']


注意: これを避ける唯一の方法は、キーに基づいてイテラブルを事前にソートすることです。

では、カスタムの key_function を定義してみましょう。

これはラムダ関数でも専用の関数でもかまいません。

from itertools import groupby


some_list = [("Animal", "cat"), 
          ("Animal", "dog"),
          ("Animal", "lion"),
          ("Plant", "dandellion"),
          ("Plant", "blumen")]

for key, group in groupby(some_list, lambda x : x[0]):
    key_and_group = { key : list(group) }
    print(key_and_group)


最初の要素は、エントリーが動物か植物かという一般的な分類を表し、2番目の要素は動物名か植物名を表します。

そして、最初の要素に基づいてこれらをグループ化し、一連の各要素を出力しました。

{'Animal': [('Animal', 'cat'), ('Animal', 'dog'), ('Animal', 'lion')]}
{'Plant': [('Plant', 'dandellion'), ('Plant', 'blumen')]}


結論

このガイドでは、Python の組み込みモジュールである itertoolscompress(), dropwhile(), takewhile(), groupby() の反復処理ツールについて見てきました。

もし、 itertools モジュールや一般的なイテレータについてもっと学びたいのであれば、他のガイドを参照してください。

  • Pythonのイテレーションツール: filter(), islice(), map() and zip()
  • Python のイテレータツール – count()、cycle()、chain()
タイトルとURLをコピーしました