- 概要
- itertoolsとは
- itertoolsモジュールの各関数
- itertools.chain
- itertools.zip_longest
- itertools.cycle
- itertools.repeat
- itertools.count
- itertools.accumulate
- itertools.compress
- itertools.dropwhile
- itertools.filterfalse
- dropwhileとfilterfalseの違い
- itertools.groupby
- itertools.islice
- itertools.starmap
- itertools.takewhile
- itertools.tee
- itertools.product
- itertools.permutations
- itertools.combinations, combinations_with_replacement
概要
標準ライブラリのitertools
についてのメモです。
先日、以下の記事を発見。
とても分かりやすく纏められていたので、ついでに自分のメモも書こうってなりました。
以下、それぞれの関数についての個人メモです。
上記サイトの内容の方がもっとわかりやすいので、ここから下は見る必要ないかもですw
itertoolsとは
itertoolsモジュールは、イテレータを構築するための関数が集まっているモジュールです。
有名なやつだと、zip_longest
とかgroupby
とかですかね。
元のシーケンスに対して、ある条件を満たしているもののみを返すイテレータとか、無限イテレータとかを操作できます。
他の言語やってる人、たとえばC#
とかJava
だと、似たようなものにLINQ
やstream
がありますが
感覚的にitertools + 内包表記
がLINQ
やstream
に近いって感じで捉えるとわかりやすいカモ。
このモジュールが使えるようになると、シーケンスの処理がぐっと楽になります。
itertoolsモジュールの各関数
以下自分のメモコードをそのまま貼り付けていますので、順不同です。
なお、予め以下を実行している状態です。
import itertools as it import operator as op import pandas as pd
itertools.chain
# ----------------------------------------------- # itertools.chain() # ---------------------- # 複数のシーケンスを繋いで一つのようにする。 # ----------------------------------------------- hr('it.chain()') list01 = ['hello', 'world'] list02 = list(range(10)) for x in it.chain(list01, list02, 'abc'): pr('value', x)
結果は
----------------it.chain()---------------- value='hello' value='world' value=0 value=1 value=2 value=3 value=4 value=5 value=6 value=7 value=8 value=9 value='a' value='b' value='c'
try-python/itertools01.py at master · devlights/try-python · GitHub
itertools.zip_longest
# ----------------------------------------------- # itertools.zip_longest() # ---------------------- # 組み込み関数 zip() の別バージョン # 要素数が多い方に合わせる。 # # zip() は、要素が最も少ない方になる。 # ----------------------------------------------- hr('it.zip_longest()') for x, y in zip(list01, list02): pr('zip()', (x, y)) # zip_longest()は、要素が最も多い方になる for x, y in it.zip_longest(list01, list02): pr('zip_longest()', (x, y))
結果は
----------------it.zip_longest()---------------- zip()=('hello', 0) zip()=('world', 1) zip_longest()=('hello', 0) zip_longest()=('world', 1) zip_longest()=(None, 2) zip_longest()=(None, 3) zip_longest()=(None, 4) zip_longest()=(None, 5) zip_longest()=(None, 6) zip_longest()=(None, 7) zip_longest()=(None, 8) zip_longest()=(None, 9)
try-python/itertools01.py at master · devlights/try-python · GitHub
itertools.cycle
# ----------------------------------------------- # itertools.cycle() # ---------------------- # itertools.cycleは、名前の通り # 最初に指定したシーケンスを繰り返しサイクルする # イテレータを生成してくれる。 # # これで生成したイテレータは無限にサイクルするので # ストップさせるのは、利用者側の責任になる。 # ------------------------------------------- numbers = list(range(0, 9)) cycle_iter = it.cycle((1, 2, 3)) hr('itertools.cycle()') for i, j in zip(numbers, cycle_iter): pr("i,j", f'{i}-{j}')
結果は
----------------itertools.cycle()---------------- i,j='0-1' i,j='1-2' i,j='2-3' i,j='3-1' i,j='4-2' i,j='5-3' i,j='6-1' i,j='7-2' i,j='8-3'
try-python/itertools02.py at master · devlights/try-python · GitHub
itertools.repeat
# ----------------------------------------------- # itertools.repeat() # ---------------------- # itertools.repeat() は、指定したオブジェクトを # 指定した回数分繰り返すイテレータを生成してくれる。 # 繰り返し回数は、第二引数の times で指定できる。 # timesのデフォルトは None となっており、これは # 無限の繰り返しを表す。 # ----------------------------------------------- hr('it.repeat()') str01 = 'hello python' for x in it.repeat(str01, 2): pr('it-repeat', x) list01 = list(range(5)) for i, x in enumerate(it.repeat(list01)): # そのままだと、無限ループするので10回出力で止める if i >= 10: break pr('it-repeat times=None', f'{i} -- {x}')
結果は
----------------it.repeat()---------------- it-repeat='hello python' it-repeat='hello python' it-repeat times=None='0 -- [0, 1, 2, 3, 4]' it-repeat times=None='1 -- [0, 1, 2, 3, 4]' it-repeat times=None='2 -- [0, 1, 2, 3, 4]' it-repeat times=None='3 -- [0, 1, 2, 3, 4]' it-repeat times=None='4 -- [0, 1, 2, 3, 4]' it-repeat times=None='5 -- [0, 1, 2, 3, 4]' it-repeat times=None='6 -- [0, 1, 2, 3, 4]' it-repeat times=None='7 -- [0, 1, 2, 3, 4]' it-repeat times=None='8 -- [0, 1, 2, 3, 4]' it-repeat times=None='9 -- [0, 1, 2, 3, 4]'
try-python/itertools03.py at master · devlights/try-python · GitHub
itertools.count
# ----------------------------------------------- # itertools.count() # ---------------------- # 無限イテレータ。 # 指定した start 値から step 分加算した値を # 生成し続ける。 # ----------------------------------------------- hr('itertools.count()') iter01 = it.count(start=10, step=2) for i in iter01: if i > 20: break pr('it.count', i)
結果は
----------------itertools.count()---------------- it.count=10 it.count=12 it.count=14 it.count=16 it.count=18 it.count=20
try-python/itertools04.py at master · devlights/try-python · GitHub
itertools.accumulate
# ----------------------------------------------- # itertools.accumulate() # ---------------------- # 指定した関数の結果を累積した結果を返す。 # 汎用性が高い関数。seed は指定できない。 # 最終的な累積結果のみが欲しい場合はfunctools.reduce() を使う。 # # 操作するための関数を指定しない場合、デフォルトは加算(operator.add)となる # 操作関数は引数を二つ受け取るものを指定する必要がある # 一つ目の引数に現在の累積値、二つ目に次の項目が入る。 # 以下のようなイメージとなる # # list(itertools.accumlate([1, 2, 3, 4, 5])) # # operator.add(1, 2) ==> 3 # operator.add(3, 3) ==> 6 # operator.add(6, 4) ==> 10 # operator.add(10, 5) ==> 15 # # なので、結果は[1, 3, 6, 10, 15]となる # ----------------------------------------------- hr('itertools.accumulate()') data01 = [1, 2, 3, 4, 5] # 加算 pr('it.accumulate', list(it.accumulate(data01, op.add))) # 乗算 pr('it.accumulate', list(it.accumulate(data01, op.mul))) # 自前関数 def _accum(accum: int, curr: int) -> int: return accum * curr if accum < 20 else 20 pr('it.accumulate', list(it.accumulate(data01, func=_accum)))
結果は
----------------itertools.accumulate()---------------- it.accumulate=[1, 3, 6, 10, 15] it.accumulate=[1, 2, 6, 24, 120] it.accumulate=[1, 2, 6, 24, 20]
try-python/itertools04.py at master · devlights/try-python · GitHub
itertools.compress
# ----------------------------------------------- # itertools.compress() # ---------------------- # 引数に指定されたシーケンスを第二引数で指定したリスト # 内でTrueとなっているもののみにフィルタリングする。 # 注意点として、zip関数と同様に短い方が終了した # 段階で処理が打ち切られる。 # ----------------------------------------------- hr('itertools.compress()') selector01 = [True if x >= 3 else False for x in data01] pr('it.compress', list(it.compress(data01, selectors=selector01)))
結果は
----------------itertools.compress()---------------- it.compress=[3, 4, 5]
try-python/itertools04.py at master · devlights/try-python · GitHub
itertools.dropwhile
# ----------------------------------------------- # itertools.dropwhile() # ---------------------- # 条件が成り立つ間、要素を捨てる。 # 条件が成り立たなくなってからの要素が返る。 # 他の言語では、SkipWhile() という名前になったりする。 # 第一引数が predicate つまり、条件。 # 第二引数が データ であることに注意。 # ----------------------------------------------- hr('itertools.dropwhile()') pr('it.dropwhile', list(it.dropwhile(lambda x: x < 3, data01)))
結果は
----------------itertools.dropwhile()---------------- it.dropwhile=[3, 4, 5]
try-python/itertools04.py at master · devlights/try-python · GitHub
itertools.filterfalse
# ----------------------------------------------- # itertools.filterfalse() # ---------------------- # ちょっと特殊な子で、条件がfalseとなるものが返る。 # predicate に None が指定できる。Noneを指定した # 場合は、データの要素が false 判定されたものが返る。 # 第一引数が predicate つまり、条件。 # 第二引数が データ であることに注意。 # ----------------------------------------------- hr('itertools.filterfalse()') pr('it.filterfalse', list(it.filterfalse(lambda x: x < 3, data01)))
結果は
----------------itertools.filterfalse()---------------- it.filterfalse=[3, 4, 5]
try-python/itertools04.py at master · devlights/try-python · GitHub
dropwhileとfilterfalseの違い
# ---------------------------------------------------------------------------- # 上の dropwhile と filterfalse では同じ条件を指定している。 # dropwhile は predicate が True の要素は飛ばして、falseになってから要素を返す。 # filterfalse は predicate が false の要素を返す。 # つまり、上の2つは対象データが[1, 2, 3, 4, 5]の場合は、同じ結果となる。 # # 違いが出るのは、一旦 True なデータが出現した後に 再度 false データが出現した場合 # # dropwhile() は、一度 True になった後は、以降のデータは無条件で全部返る。 # filterfalse() は、false の判定になったものしか返さない。 # ---------------------------------------------------------------------------- hr('difference between dropwhile() and filterfalse()') data02 = [*data01, 2, 1, 3, 4, 5] pr('it.dropwhile', list(it.dropwhile(lambda x: x < 3, data02))) pr('it.filterfalse', list(it.filterfalse(lambda x: x < 3, data02)))
結果は
----------------difference between dropwhile() and filterfalse()---------------- it.dropwhile=[3, 4, 5, 2, 1, 3, 4, 5] it.filterfalse=[3, 4, 5, 3, 4, 5]
try-python/itertools04.py at master · devlights/try-python · GitHub
itertools.groupby
# ----------------------------------------------- # itertools.groupby() # ---------------------- # 指定された条件でグルーピングを行う。 # groupby() は、key で指定した関数が返す値が # 変わる度に新たなグループを生成するため # 通常指定するシーケンスはソート済みである必要がある。 # # また、groupby() が返すデータはそれ自身もイテレータ # となっているため、データが後の処理で必要な場合は # 別途リストなどを用意して保持しておく必要がある。 # (それか、再度 groupby() 呼ぶ) # ----------------------------------------------- GroupingData = collections.namedtuple('GroupingData', ['id', 'name']) # 未ソートのシーケンス groups_not_sorted = [ GroupingData(1, 'data1'), GroupingData(1, 'data2'), GroupingData(2, 'data3'), GroupingData(1, 'data4') ] def key_func(grp: GroupingData) -> int: return grp.id hr('未ソートの状態で groupby() ') grp_iter = it.groupby(groups_not_sorted, key=key_func) for k, g in grp_iter: pr('grp-key', k) pr('\tgrp-items', ','.join(_.name for _ in g)) hr('ソート済みの状態で groupby() ') # ソート済み groups_sorted = sorted(groups_not_sorted, key=key_func) grp_iter = it.groupby(groups_sorted, key=key_func) for k, g in grp_iter: pr('grp-key', k) pr('\tgrp-items', ','.join(_.name for _ in g))
結果は
----------------未ソートの状態で groupby() ---------------- grp-key=1 grp-items='data1,data2' grp-key=2 grp-items='data3' grp-key=1 grp-items='data4' ----------------ソート済みの状態で groupby() ---------------- grp-key=1 grp-items='data1,data2,data4' grp-key=2 grp-items='data3'
try-python/itertools05.py at master · devlights/try-python · GitHub
itertools.islice
# ----------------------------------------------- # itertools.islice() # ---------------------- # 指定されたイテレータブルに対してスライスを行う。 # islice() には、キーワード引数が存在せず位置引数のみ。 # なので、値の指定数で挙動が変わる。 # 戻り値も当然イテレータとなっている。 # # 引数が一つの場合、stopが指定された状態となる。 # 引数が二つの場合、start, stopが指定された状態となる。 # 引数が三つの場合、start, stop, stepが指定された(略 # ----------------------------------------------- hr('it.islice()') data01 = list(range(10)) pr('it.islice(8)', list(it.islice(data01, 8))) pr('it.islice(5,8)', list(it.islice(data01, 5, 8))) pr('it.islice(2,8,2)', list(it.islice(data01, 2, 8, 2))) # stop には、None が指定可能。None を指定した場合は最後までという意味となる。 pr('it.islice(5,None)', list(it.islice(data01, 5, None)))
結果は
----------------it.islice()---------------- it.islice(8)=[0, 1, 2, 3, 4, 5, 6, 7] it.islice(5,8)=[5, 6, 7] it.islice(2,8,2)=[2, 4, 6] it.islice(5,None)=[5, 6, 7, 8, 9]
try-python/itertools06.py at master · devlights/try-python · GitHub
itertools.starmap
# ----------------------------------------------- # itertools.starmap() # ---------------------- # グループ化済みの iterable に対して function を適用する。 # 例えば、zipした後の結果に対して、更に function 適用するなど。 # # つまり、以下のような感じ。 # # l1 = [1, 2, 3] # l2 = [9, 8, 7] # l3 = list(zip(l1, l2)) ==> [(1,9), (2,8), (3,7)] # # l3に対して operator.add で starmapする # list(itertools.starmap(operator.add, l3)) # # 結果は [10, 10, 10] となる。 # つまり、以下を実施したのと同じこと # # for item in l3: # operator.add(*item) # # なので、名前が starmap となっている # ----------------------------------------------- hr('it.starmap()') list01 = [9, 8, 7] list02 = [1, 2, 3] list03 = list(zip(list01, list02)) starmap = it.starmap(ope.sub, list03) pr('it.starmap', list(starmap)) list04 = list(zip(list01, list02, *list03)) pr('it.starmap', list(it.starmap(lambda *args: sum(args), list04)))
結果は
----------------it.starmap()---------------- it.starmap=[8, 6, 4] it.starmap=[34, 16]
try-python/itertools07.py at master · devlights/try-python · GitHub
itertools.takewhile
# ----------------------------------------------- # itertools.takewhile() # ---------------------- # 指定した条件を満たす間、要素を返す。 # dropwhile() の 逆。 # # なので、一度でも条件から外れた場合、それ以降に # 条件を満たす値があっても要素は返らない。 # ----------------------------------------------- hr('it.takewhile()') list05 = sorted(it.chain(list01, list02)) pr('list05', list05) takewhile = it.takewhile(lambda x: x < 5, list05) pr('it.takewhile', list(takewhile))
結果は
----------------it.takewhile()---------------- list05=[1, 2, 3, 7, 8, 9] it.takewhile=[1, 2, 3]
try-python/itertools07.py at master · devlights/try-python · GitHub
itertools.tee
# ----------------------------------------------- # itertools.tee() # ---------------------- # 指定された iterable を複数の独立した iterable にして返す。 # つまり、n=2 とすると、元の iterable を複製した # 二つの iterable が取得できる。(tuple(iterable, iterable)) # # 公式ドキュメントに記載されているように、一度 tee() を # 使用して分割した original iterable は、内部状態を共有しているので # もう別の場所では利用しないほうがいい。 # # 引用: # Once tee() has made a split, # the original iterable should not be used anywhere else; # otherwise, the iterable could get advanced # without the tee objects being informed. # ----------------------------------------------- hr('it.tee()') list06 = list('helloworld') it_tee = it.tee(list06, 2) it_asc, it_desc = it_tee[0], reversed(list(it_tee[-1])) for it01, it02 in zip(it_asc, it_desc): pr('it.tee', f'{it01}, {it02}')
結果は
----------------it.tee()---------------- it.tee='h, d' it.tee='e, l' it.tee='l, r' it.tee='l, o' it.tee='o, w' it.tee='w, o' it.tee='o, l' it.tee='r, l' it.tee='l, e' it.tee='d, h'
try-python/itertools07.py at master · devlights/try-python · GitHub
itertools.product
# ----------------------------------------------- # itertools.product() # ---------------------- # デカルト積を求める。とドキュメントに記載されているが # 要は list01 と list02 が存在する場合 # (x, y) for x in list01 for y in list02 # と同じことになる。 # # キーワード引数の repeat を利用すると直積も求められる。 # ----------------------------------------------- hr('it.product()') list01 = list('ABC') list02 = list('DEF') pr('it.product(*iterable)', list(it.product(list01, list02))) pr('listcomp', [(x, y) for x in list01 for y in list02]) pr('it.product(*iterable, repeat=2)', list(it.product('AB', repeat=4))) # テストデータを一気に生成するときに便利 date_range = pd.date_range(dt.date.today(), periods=3, freq='D') values = (10, 11, 12) pr('it.product', list(it.product(date_range, values))) pr('listcomp', [(d, v) for d in date_range for v in values]) # 同じ
結果は
----------------it.product()---------------- it.product(*iterable)=[('A', 'D'), ('A', 'E'), ('A', 'F'), ('B', 'D'), ('B', 'E'), ('B', 'F'), ('C', 'D'), ('C', 'E'), ('C', 'F')] listcomp=[('A', 'D'), ('A', 'E'), ('A', 'F'), ('B', 'D'), ('B', 'E'), ('B', 'F'), ('C', 'D'), ('C', 'E'), ('C', 'F')] it.product(*iterable, repeat=2)=[('A', 'A', 'A', 'A'), ('A', 'A', 'A', 'B'), ('A', 'A', 'B', 'A'), ('A', 'A', 'B', 'B'), ('A', 'B', 'A', 'A'), ('A', 'B', 'A', 'B'), ('A', 'B', 'B', 'A'), ('A', 'B', 'B', 'B'), ('B', 'A', 'A', 'A'), ('B', 'A', 'A', 'B'), ('B', 'A', 'B', 'A'), ('B', 'A', 'B', 'B'), ('B', 'B', 'A', 'A'), ('B', 'B', 'A', 'B'), ('B', 'B', 'B', 'A'), ('B', 'B', 'B', 'B')] it.product=[(Timestamp('2018-03-29 00:00:00', freq='D'), 10), (Timestamp('2018-03-29 00:00:00', freq='D'), 11), (Timestamp('2018-03-29 00:00:00', freq='D'), 12), (Timestamp('2018-03-30 00:00:00', freq='D'), 10), (Timestamp('2018-03-30 00:00:00', freq='D'), 11), (Timestamp('2018-03-30 00:00:00', freq='D'), 12), (Timestamp('2018-03-31 00:00:00', freq='D'), 10), (Timestamp('2018-03-31 00:00:00', freq='D'), 11), (Timestamp('2018-03-31 00:00:00', freq='D'), 12)] listcomp=[(Timestamp('2018-03-29 00:00:00', freq='D'), 10), (Timestamp('2018-03-29 00:00:00', freq='D'), 11), (Timestamp('2018-03-29 00:00:00', freq='D'), 12), (Timestamp('2018-03-30 00:00:00', freq='D'), 10), (Timestamp('2018-03-30 00:00:00', freq='D'), 11), (Timestamp('2018-03-30 00:00:00', freq='D'), 12), (Timestamp('2018-03-31 00:00:00', freq='D'), 10), (Timestamp('2018-03-31 00:00:00', freq='D'), 11), (Timestamp('2018-03-31 00:00:00', freq='D'), 12)]
try-python/itertools08.py at master · devlights/try-python · GitHub
itertools.permutations
# ----------------------------------------------- # itertools.permutations() # ---------------------- # 順列を生成する。(e.g. 5! = 5*4*3*2*1 = 120) # 第二引数に順列の長さを指定できる。省略可能 # 長さにNone(これがデフォルト)を指定すると # 可能な最長の順列が生成される。 # ----------------------------------------------- hr('it.permutations()') pr('it.permutations(r=None)...(3!=3*2*1)', list(it.permutations('ABC'))) # 長さを指定することで、シーケンスからいくつ選ぶのかを指定できる pr('it.permutations(r=3)...(4p3=4*3*2)', len(list(it.permutations('ABCD', r=3)))) pr('it.permutations(r=2)...(4p2=4*3)', len(list(it.permutations('ABCD', r=2))))
結果は
----------------it.permutations()---------------- it.permutations(r=None)...(3!=3*2*1)=[('A', 'B', 'C'), ('A', 'C', 'B'), ('B', 'A', 'C'), ('B', 'C', 'A'), ('C', 'A', 'B'), ('C', 'B', 'A')] it.permutations(r=3)...(4p3=4*3*2)=24 it.permutations(r=2)...(4p2=4*3)=12
try-python/itertools08.py at master · devlights/try-python · GitHub
itertools.combinations, combinations_with_replacement
# ----------------------------------------------- # itertools.combinations() # ---------------------- # 組合せを生成する。 # 第二引数は必須。 # # 並べる際の順序を無視した結果を返す。 # つまり、(a, b, c)と(b, a, c)と(c, b, a)は同じとみなす。 # ----------------------------------------------- hr('it.combinations()') pr('it.combinations(r=2)...(4c2=4p2/2!)', list(it.combinations('ABCD', r=2))) pr('it.combinations(r=2)...(4c2=4p2/2!)', len(list(it.combinations('ABCD', r=2)))) # combinations_with_replacement() は、それぞれの要素が重複することを許す pr('it.combinations_with_replacement(r=2)', list(it.combinations_with_replacement('ABCD', r=2))) pr('it.combinations_with_replacement(r=2)', len(list(it.combinations_with_replacement('ABCD', r=2))))
結果は
----------------it.combinations()---------------- it.combinations(r=2)...(4c2=4p2/2!)=[('A', 'B'), ('A', 'C'), ('A', 'D'), ('B', 'C'), ('B', 'D'), ('C', 'D')] it.combinations(r=2)...(4c2=4p2/2!)=6 it.combinations_with_replacement(r=2)=[('A', 'A'), ('A', 'B'), ('A', 'C'), ('A', 'D'), ('B', 'B'), ('B', 'C'), ('B', 'D'), ('C', 'C'), ('C', 'D'), ('D', 'D')] it.combinations_with_replacement(r=2)=10
try-python/itertools08.py at master · devlights/try-python · GitHub
過去の記事については、以下のページからご参照下さい。
- いろいろ備忘録日記まとめ
サンプルコードは、以下の場所で公開しています。
- いろいろ備忘録日記サンプルソース置き場