Python
 Computer >> コンピューター >  >> プログラミング >> Python

Pythonのitertoolsモジュールで効率的なイテレータを作成する方法

多くのプログラミング言語と同様に、Pythonにもwhile文とfor文というループ構造が用意されています。特にfor文は、リスト・タプル・文字列などのイテラブル(反復可能オブジェクト)を走査する際に非常に便利です。さらにPythonの標準ライブラリにはitertoolsモジュールがあり、より高速で効率的な反復処理ツールが定義されています。これらのイテレータ構成要素は、HaskellやSMLといった関数型プログラミング言語における類似ツールの「Pythonらしい(Pythonic)」実装です。

itertoolsモジュールの関数は3種類

itertoolsモジュールに含まれる関数は、以下の3つのカテゴリに分類されます。

  • 無限イテレータ:終了しない無限シーケンスを生成
  • 有限イテレータ:入力シーケンスに基づいて有限の結果を返す
  • 組み合わせイテレータ:順列や組み合わせなどを生成

それぞれのカテゴリについて、代表的な関数を見ていきましょう。

1. 無限イテレータを生成する関数

以下の関数は無限のシーケンスを生成します。ループが自動的には終了しないため、実行時はCtrl-Cで中断してください。

count()

count()関数は、指定した開始値から等間隔に増加する値を持つイテレータを返します。第2引数としてステップ値(増分)を指定することもできます。

>>> from itertools import count
>>> for x in count(20):
    print(x)

>>> for x in count(100, 10):
    print(x)

最初の例では20から始まる無限シーケンスが、次の例では100から10刻みで数値が生成されます。どちらも無限ループなので、Ctrl-Cで強制終了する必要があります。

cycle()

cycle()関数は、与えられたイテラブルの各要素を順に返し、そのコピーを保存します。要素を使い果たすと、保存しておいたコピーから再び要素を返し始め、無限ループを形成します。

>>> from itertools import cycle
>>> for x in cycle("hello"):
    print(x)

この例では、文字列内の各文字がCtrl-Cで中断されるまで繰り返し出力され続けます。

repeat()

repeat()関数は、オブジェクト引数を繰り返し返します。第2引数に回数(times)を指定すると、その回数だけ繰り返します。

>>> from itertools import repeat
>>> for x in repeat(1):
    print(x)

>>> for x in repeat('hello', 10):
    print(x)

最初のループは1を無限に出力し続けます。2番目のループは'hello'を10回出力して終了します。

2. 有限イテレータを生成する関数

このカテゴリの関数は、最短の入力シーケンスで終了する有限のイテレータを返します。

accumulate()

accumulate()関数は2つのパラメータを取ります。第1引数はイテラブル(リスト、タプル、文字列など)、第2引数はデフォルトでoperator.add()(標準的な加算演算子を実装したoperatorモジュールの関数)ですが、2つの数値引数を受け取る任意の関数を指定できます。

accumulate(sequence, func)

まず入力シーケンスの先頭2要素がfuncで処理されます。その結果が次の反復の第1パラメータとなり、funcの第2パラメータには入力シーケンスの3番目の要素が渡されます。この処理をシーケンスが尽きるまで繰り返し、各段階の累積結果を含むイテレータを返します。

次の例では、リスト内の数値が累積的に加算されています。デフォルトの関数引数は加算操作である点に注目してください。

>>> from itertools import accumulate
>>> list(accumulate([1,2,3,4,5]))
[1, 3, 6, 10, 15]

ユーザー定義関数を第2引数として渡すこともできます。

>>> def multiply(x, y):
    return x * y

>>> list(accumulate([1,2,3,4,5], multiply))
[1, 2, 6, 24, 120]

この動作は組み込みのreduce()関数に似ていますが、違いもあります。reduce()は累積の最終結果のみを返すのに対し、accumulate()はすべての中間結果を含むイテレータを構築します。

chain()

chain()関数は、複数のイテラブルを引数に取ることができます。最初のイテラブルの各要素を結果のイテレータに順に渡し、それが尽きたら次のイテラブルへと進みます。

>>> from itertools import chain
>>> list(chain([10,20], 'hello', range(4)))
[10, 20, 'h', 'e', 'l', 'l', 'o', 0, 1, 2, 3]

dropwhile()

dropwhile()関数は、述語関数(predicate)がTrueを返す間、イテラブルの要素を破棄し続けます。関数がFalseを返した瞬間から、残りのすべての要素がイテレータに渡されます。

>>> from itertools import dropwhile
>>> def iseven(x):
    if x % 2 == 0:
        return True
    else:
        return False

>>> list(dropwhile(iseven, [12,90,61,4,15]))
[61, 4, 15]

この例では、偶数である12と90がスキップされ、最初の奇数61以降の要素がすべて返されています。

filterfalse()

filterfalse()関数は、述語関数がFalseとなる要素だけを選び出したイテレータを返します。組み込みのfilter()関数とは逆の動作です。

>>> from itertools import filterfalse
>>> def iseven(x):
    if x % 2 == 0:
        return True
    else:
        return False

>>> list(filterfalse(iseven, [12,90,61,4,15]))
[61, 15]

islice()

islice()関数は、イテラブルから特定の要素を選択してイテレータを構築します。選択基準はstart(開始位置)、stop(終了位置)、step(間隔)の各パラメータによって決まります。選択はstart値から始まりstop値まで続きます。stopがNoneの場合はイテラブルの末尾まで進みます。stepのデフォルト値は1です。いずれのパラメータにも負の値は指定できません。

>>> from itertools import islice
>>> list(islice(range(10), 1, 5, 2))
[1, 3]
>>> list(islice(range(10), 0, None, 3))
[0, 3, 6, 9]
>>> list(islice(range(10), 5, None))
[5, 6, 7, 8, 9]
>>> list(islice(range(10), 5))
[0, 1, 2, 3, 4]

3. 組み合わせイテレータを生成する関数

以下の関数は、イテラブルオブジェクトから組み合わせに関するイテレータを生成します。

product()

product()関数は、入力シーケンスの要素の直積(カルテシアン積)となるイテレータを生成します。これは、各イテラブルを走査するネストされたforループを構築するのと同等です。

次の例では、リスト内包表記を使って2つのシーケンスに対する二重ループを実行し、直積を構築しています。

>>> [[x,y] for x in [1,2,3] for y in ['a','b','c']]
[[1, 'a'], [1, 'b'], [1, 'c'], [2, 'a'], [2, 'b'], [2, 'c'], [3, 'a'], [3, 'b'], [3, 'c']]

product()関数を使うと、同様の結果をより簡潔に得られます。

>>> from itertools import product
>>> list(product([1,2,3], ['a','b','c']))
[(1, 'a'), (1, 'b'), (1, 'c'), (2, 'a'), (2, 'b'), (2, 'c'), (3, 'a'), (3, 'b'), (3, 'c')]

permutations()

permutations()関数は、入力イテラブルの要素から考えられるすべての順列を生成します。各順列の長さは第2引数で指定できます。省略した場合、長さはイテラブル全体の長さになります。

>>> from itertools import permutations
>>> list(permutations(range(1,4), 2))
[(1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2)]

combinations()

combinations()関数は、入力イテラブルの要素から考えられるすべての組み合わせを生成します。各組み合わせの長さは第2引数で指定できます。順列と異なり、要素の並び順は考慮されません。

>>> from itertools import combinations
>>> list(combinations(range(1,4), 2))
[(1, 2), (1, 3), (2, 3)]

まとめ

本記事では、Python標準ライブラリのitertoolsモジュールに定義されているさまざまなイテレータツールを紹介しました。無限イテレータ(count()cycle()repeat())、有限イテレータ(accumulate()chain()dropwhile()filterfalse()islice())、そして組み合わせイテレータ(product()permutations()combinations())を活用すれば、従来のループ処理よりも簡潔かつ効率的なコードを書くことができます。日常的なデータ処理からアルゴリズム実装まで、ぜひ活用してみてください。

  1. Pythonのissubset()関数とは?部分集合の判定方法を実例付きで解説

    本記事では、Python標準ライブラリに用意されているissubset()関数の仕組みと使い方について詳しく解説します。 issubset()メソッドは、あるセット(集合)のすべての要素が、引数として渡した別のセットにも含まれている場合にブール値のTrueを返し、1つでも含まれていない要素があればFalseを返します。 下の図では、BはAの部分集合です。もしAとBが同一のセットであれば、両者は互いに部分集合の関係にあるといえます。つまり、両方のセットがまったく同じ要素を持っているということを意味します。 構文 <set 1>.issubset(<set 2>) 戻り値

  2. Pythonのintersection()関数とは?集合の共通要素(積集合)を求める方法

    この記事では、Pythonのセット(集合)に対して実行できるintersection()関数について詳しく解説します。数学における「積集合(インターセクション)」とは、2つの集合から共通する要素を見つけ出すことを指します。構文<セット名>.intersection(<セットa1>, <セットa2>, …)戻り値引数として渡されたすべての集合に共通する要素(積集合)が、新しいセットとして返されます。使用例set_1 = {t,u,t,o,r,i,a,l} set_2 = {p,o,i,n,t} set_3 = {t,u,t} # 2つの集合の積集合 print