無限オブ無限 (2)

okagawaさんのテストのプログラムを使って自分のflattenでテストしてみたら結果が違っていて自分の実装の間違いに気がついた(汗)。正しいものに書き換えて、ついでに、if i+1 == cnt: breakの部分がいまいちだったので、その部分も書き直してみた。

from itertools import izip, tee

def flatten(L):
    D, cnt = {}, 1
    while True:
        L, L2 = tee(L)
        for i, gen in izip(xrange(cnt), L2):
            g = D.setdefault(i, iter(gen))
            try:
                yield g.next()
            except StopIteration:
                pass
        cnt += 1

def inf_list(f):
    cnt = 0
    while 1:
        yield f(cnt)
	cnt += 1

x = flatten( inf_list(lambda x: inf_list(lambda y: chr(97+x)+str(y))) )

for i in range(20):
    print x.next(),

実行結果。

a0 a1 b0 a2 b1 c0 a3 b2 c1 d0 a4 b3 c2 d1 e0 a5 b4 c3 d2 e1

okagawaさんの実装を有限にも対応するにはEAFPによりエラー処理を追加するだけだと思う。しかし、自分の実装は実行時に結構無駄な処理が多いしシンプルではない。okagawaさんとの違いはシーケンスの保存にリストを使うか辞書を使うかだが、辞書を使うメリットを生かせていないのでリストを使ったokagawaさんの実装の方がシンプルになっていると思われる。辞書を使うメリットの1つはシーケンスの実行順序をオリジナルの順序と変えたい時だと思われる。


辞書をシーケンス的な使い方をするときには、機能的に「リスト + α == 辞書」という式が成り立つと思われる。辞書の方が構文がシンプルでない分、高機能というイメージ。

有限でも無限でもOK版を無限のみ対応版と同じくらいシンプルに書けるような Generatorのインターフェイスというのは考えられないものだろうか…

確かに今のPythonは統一性がない。以下に簡単にまとめた。


iterableなオブジェクト(いわゆるシーケンス)の種類


iterableなオブジェクトの扱いの抽象性

  • 関数の引数で何が受け取れるか
  • 関数の返値で何を返すか ⇒ 関数によって分ける場合がある。zipとizipなど。
  • 操作の構文 ⇒ 次のアイテムを返す構文が違う。g.next()やg[idx]など。inを使えば抽象化できる。


問題となりそうなのは、次のアイテムを取り出すときにリスト系とイテレータ系で取り出し方が違うということだと思う。あと、関数の引数でリスト系かイテレータ系かを区別する場合は、実装側で抽象的な書き方ができないことから起こるものである。しかし、リスト系にしか存在しない操作が必要になったり、next()でアイテムを1つずつ取り出したい場合は抽象化できないと思われる。


最低限、イテレータ系のnext()にあたるものをリスト系にも用意してくれてもだいぶ改善されそう。そういう関数って今でも存在するのかな…?


追記:

考えてみたらリスト系でnext()にあたるものを用意するのは無理だった。カレントのインデックスを保持する場所がないので。リスト系のインスタンスに持たせるのも不自然だし、リスト系とイテレータ系で挙動が違うのに統一的に扱うのも問題だし、だからiter(lst)でイテレータに昇格する関数がある訳だし。