note.nkmk.me

Pythonのitertools.count, cycle, repeatによる無限イテレータ

Date: 2019-11-05 / tags: Python

Pythonの標準ライブラリitertoolsモジュールの関数itertools.count(), itertools.cycle(), itertools.repeat()を使うと、無限に続くイテレータを生成することができる。

ここでは以下の内容について説明する。

  • itertools.count(): 無限カウント
  • itertools.cycle(): リストなどの要素を無限繰り返し
  • itertools.repeat(): 同じ値を無限繰り返し

それぞれの関数について、for文による繰り返し処理の例とzip()関数との組み合わせの例を紹介する。

for文で使う場合、終了条件を指定しbreakしないと無限ループに陥るので要注意。

while文でもカウンターなどを伴う無限ループを実装できるが、itertoolsの各関数を使うとスッキリ書けることが多い。

スポンサーリンク

itertools.count(): 無限カウント

itertools.count()は無限にカウントアップまたはカウントダウンする値を返すイテレータを生成する。

デフォルトは開始値が01ずつ増加。

import itertools

for i in itertools.count():
    print(i)
    if i > 3:
        break
# 0
# 1
# 2
# 3
# 4

第一引数startで開始値、第二引数stepで増分を指定可能。

for i in itertools.count(2):
    print(i)
    if i > 3:
        break
# 2
# 3
# 4

for i in itertools.count(step=3):
    print(i)
    if i > 8:
        break
# 0
# 3
# 6
# 9

for i in itertools.count(2, 3):
    print(i)
    if i > 8:
        break
# 2
# 5
# 8
# 11

カウントダウンしたいときはstepに負の値を指定すればよい。

for i in itertools.count(10, -1):
    print(i)
    if i < 8:
        break
# 10
# 9
# 8
# 7

浮動小数点数floatでの指定も可能だが、掛け算を使ったほうが正確になる場合がある(らしい)。

浮動小数点数でカウントするときは (start + step * i for i in count()) のように掛け算を使ったコードに置き換えたほうが正確にできることがあります。 itertools.count() --- イテレータ生成関数 — Python 3.8.0 ドキュメント

for i in itertools.count(0.1, 1.5):
    print(i)
    if i > 3:
        break
# 0.1
# 1.6
# 3.1

for i in itertools.count():
    ii = 0.1 + 1.5 * i
    print(ii)
    if ii > 3:
        break
# 0.1
# 1.6
# 3.1

zip()と組み合わせるとカウンターを追加したタプルを生成できる。

l1 = ['a', 'b', 'c']
l2 = ['x', 'y', 'z']

print(list(zip(itertools.count(), l1, l2)))
# [(0, 'a', 'x'), (1, 'b', 'y'), (2, 'c', 'z')]

なお、enumerate()zip()の場合は入れ子のタプルになる。

print(list(enumerate(zip(l1, l2))))
# [(0, ('a', 'x')), (1, ('b', 'y')), (2, ('c', 'z'))]

forループで使う場合はenumerate()zip()でも以下のようにそれぞれの値を展開して使用できる。

names = ['Alice', 'Bob', 'Charlie']
ages = [24, 50, 18]

for i, (name, age) in enumerate(zip(names, ages)):
    print(i, name, age)
# 0 Alice 24
# 1 Bob 50
# 2 Charlie 18

itertools.cycle(): リストなどの要素を無限繰り返し

itertools.cycle()は引数に指定したリストなどのイテラブルオブジェクトの要素を無限に繰り返すイテレータを生成する。

l = [1, 10, 100]

sum_value = 0

for i in itertools.cycle(l):
    print(i)
    sum_value += i
    if sum_value > 300:
        break
# 1
# 10
# 100
# 1
# 10
# 100
# 1
# 10
# 100

range()を引数に指定した例。

sum_value = 0

for i in itertools.cycle(range(3)):
    print(i)
    sum_value += i
    if sum_value > 5:
        break
# 0
# 1
# 2
# 0
# 1
# 2

zip()と組み合わせた例。

l1 = [1, 10, 100]
l2 = [0, 1, 2, 3, 4, 5, 6]

print(list(zip(itertools.cycle(l1), l2)))
# [(1, 0), (10, 1), (100, 2), (1, 3), (10, 4), (100, 5), (1, 6)]

なお、itertools.cycle()は元のイテラブルオブジェクトのコピーを生成する。元のイテラブルオブジェクトのサイズが大きいとメモリを大量に使用してしまうので注意。

cycle() は大きなメモリ領域を使用します。使用するメモリ量は iterable の大きさに依存します。 itertools.cycle() --- イテレータ生成関数 — Python 3.8.0 ドキュメント

itertools.repeat(): 同じ値を無限繰り返し

itertools.repeat()は同じ値を無限に返すイテレータを生成する。

sum_value = 0

for i in itertools.repeat(10):
    print(i)
    sum_value += i
    if sum_value > 40:
        break
# 10
# 10
# 10
# 10
# 10

第二引数timesで繰り返し回数を指定可能。

for i in itertools.repeat(10, 3):
    print(i)
# 10
# 10
# 10

なお、便宜上「値」と書いたが、第一引数にはどんなオブジェクトでも指定できる。使いどころがあるかどうかは分からないが、関数オブジェクト(例では組み込み関数len())を繰り返すことも可能。

for l in itertools.repeat([0, 1, 2], 3):
    print(l)
# [0, 1, 2]
# [0, 1, 2]
# [0, 1, 2]

for func in itertools.repeat(len, 3):
    print(func('abc'))
# 3
# 3
# 3

zip()と組み合わせた例は以下の通り。定数の要素を追加できる。

l = [0, 1, 2, 3]

print(list(zip(itertools.repeat(10), l)))
# [(10, 0), (10, 1), (10, 2), (10, 3)]
スポンサーリンク
シェア
このエントリーをはてなブックマークに追加

関連カテゴリー

関連記事