Pythonのitertools – count()、cycle()、chain()

Python には、データを反復処理したり変換したりするためのビルトインツールがたくさんあります。

その好例が itertools モジュールで、いくつかの便利なイテレータ関数を提供しています。

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

このモジュールは、APL、Haskell、SPL などの関数型言語からインスピレーションを受けており、 itertools 内の要素は Python のイテレータ代数を形成しています。

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

イテレーションに入る前に、まず、イテラブルとイテレータという2つの重要な用語の区別を定義しておこう。

イテラブルとは、反復処理可能なオブジェクトのことです。

iter()` 関数を使用すると、イテレータが生成されます。

一般に、リスト、タプル、文字列など、ほとんどのシーケンスはイテレート可能です。

イテレータはオブジェクトでもあり、イテレート可能なオブジェクトに対してイテレーションを行うために使われます。

これは、next() メソッドを用いて、走査しようとするイテレータを渡すことで実現されます。

next()メソッドは、イテレート可能なオブジェクトの次の要素を返します。

イテレータは、イテレート可能なオブジェクトから生成することができます (iter()` を使用します)。

list = [1,2,3,4,5]
iterator = iter(list)


print(iterator)


これは次のようになります。

<list_iterator 0x0000018e393a0f28="" at="" object=""


では、iterator を使って next() 要素 (最初の要素から) にアクセスしてみましょう。

print(next(iterator))


この結果は次のようになります。

1


これは実質的に for ループの内部で起こっていることです。

繰り返し処理中のコレクションに対して iter() を呼び出し、その後 next() 要素に n 回アクセスしています。

このチュートリアルでは、いくつかのPythonの反復処理ツールについて見ていきます。

  • count()
  • cycle()
  • chain()

count()関数

count(start, step)関数はイテレータを作成し、等間隔の値を生成するために使用します。

イテレータ間のスペースはstep引数で定義します。

start 引数はイテレータの開始値を定義します。

デフォルトでは、 start=0step=1 に設定されています。

ブレーク条件がなければ、 count() 関数は無限にカウントし続けます (不定なメモリを持つシステム上)。

from itertools import count


iterator_count = count(start=0, step=5)


for i in iterator_count:
    if(i == 25):
        break
    print(i)


Note: count() をこのように使用するのは珍しいことです。

通常、 zip()map()imap() などの他のメソッドと一緒に使うことになるでしょう。

ここでは、イテレータは自分自身を反復し、5段階ごとに値を表示します。

0
5
10
15
20


この関数は、その生成的な性質から、新しいシーケンスを期待したり生成したりする他の関数と一緒に使われることが最も一般的です。

例えば、zip() を使ってリストの複数の項目をまとめてzipするとき、位置のインデックスで注釈を付けたいと思うかもしれません。

zip 中に count() を使用して、これらのインデックスに対応する値を生成することになります。

from itertools import count


list = ['John', 'Marie', 'Jack', 'Anna']
for i in zip(count(), list):
    print(i)


という結果になります。

(0, 'John')
(1, 'Marie')
(2, 'Jack')
(3, 'Anna')


もし zip() 関数や、それと一緒によく使われる他の関数についてもっと知りたいなら、 Python Iteration Tools – filter(), islice(), map() and zip() のガイドを読んでみてください。

cycle()関数

cycle()` 関数はイテラブルを受け取ってイテレータを生成し、その中にイテラブルの全要素が含まれる。

これらの要素に加えて、各要素のコピーも含まれます。

イテレータが要素の最後まで到達すると,そのコピーに対してイテレーションを開始します.コピーを繰り返しながら、新しいコピーが作られる。

コピーの最初のセットがなくなったら、新しいセットを繰り返し処理する。

このプロセスは無限に繰り返される。

注意: この事実を考えると、特に長いシーケンスに対して cycle() を使用することは、非常に大きなメモリ負担となります。

無限かつ再帰的な作成ロジックには注意が必要です。

なぜなら、すべてを格納するためのメモリを簡単に使い果たしてしまうからです。

from itertools import cycle


list = [1,2,3,4]
iterator = cycle(list)


for i in iterator:
    print(i)


この結果

1
2
3
4
1
2
3
4
...


プログラムを終了させるか、メモリを使い果たすまで。

とはいえ、cycle()関数には常に終了条件となるものを用意しておくべきです。

cycle()` は任意のイテラブルを循環させることができるため、文字列やタプルにも簡単に適用できます。

from itertools import cycle


string = "This is a random string"
iterator = cycle(string)


for i in iterator:
    print(i)


この結果、無限に続く

T
h
i
s
i
s
a
r
a
n
d
o
...


chain()関数

chain()`関数は、複数のイテレータブルを順番に走査するイテレータを生成して、複数のイテレータブルを連結するために使われます。

result = list(chain([1, 2, 3], 
        ["one", "two", "three"], 
        "String", 
        ("this", "is", "a", "tuple")))

print(result)


出力は以下のようになる。

[1, 2, 3, 'one', 'two', 'three', 'S', 't', 'r', 'i', 'n', 'g', 'this', 'is', 'a', 'tuple']


ここでは、4種類のイテレータブルがあり、それぞれが連結されています。

one”, “two”, “three”]は文字列のリストですが、chain()はこれをリストとして扱い、文字列ごとにchain()を呼び出すことなく単にその要素を連鎖させています。

一方、”String”` は構成する文字に分解されます。

前者は chain() 関数から派生した別のメソッド – chain.from_iterable() を用いて実現することができます。

result2 = list(chain(["one", "two", "three"]))
result3 = list(chain.from_iterable(["one", "two", "three"]))


print(result2)
print(result3)


chain()関数の動作は、これまで見てきたのと同じで、要素をそのまま連結します。

一方、chain.from_iterable()`メソッドは、各要素をイテラブルとして扱い、その構成要素を他の要素とともに同じ方法で分解して返します。

['one', 'two', 'three']
['o', 'n', 'e', 't', 'w', 'o', 't', 'h', 'r', 'e', 'e']


一般的には、chain.from_iterable() を使って、最初にチェーン接続したいくつかのコレクションに含まれる数字の合計を計算し、次に sum() を計算します。

from itertools import chain


number_list = [[1, 2, 3],[4, 5, 6],[7, 8, 9]]
result = list(chain.from_iterable(number_list))
print(sum(result))


number_listコレクションの各要素は、別のリストです。

リストは反復可能なので、chain.from_iterable()の呼び出しによって、[1..9]の要素を含む 1 つのリストに分解されます。

その後、sum()` を計算してその結果を表示します。

45


結論

itertools` モジュールは、イテレートやイテレーションを処理するための便利な関数をいくつか紹介しています。

これらの多くは独立した便利な関数として使用できますが、データを変換するために他の関数と連結して使用するのが最も一般的です。

タイトルとURLをコピーしました