5

ジェネレーターのシーケンスがあります:(gen_0、gen_1、... gen_n)

これらのジェネレーターは、値を遅延して作成しますが、有限であり、長さが異なる可能性があります。

使い果たされたジェネレーターからの値をスキップして、各ジェネレーターの最初の要素を順番に生成し、次に2番目の要素を生成する別のジェネレーターを構築できる必要があります。

この問題はタプルを取るのと似ていると思います

((1, 4, 7, 10, 13, 16), (2, 5, 8, 11, 14), (3, 6, 9, 12, 15, 17, 18))

そしてそれをトラバースして、1から18までの数字を順番に生成します。

私は(genA、genB、genC)を使用してこの単純な例を解決することに取り組んでいます。genAは(1、4、7、10、13、16)からの値を生成し、genBは(2、5、8、11、14)を生成しますgenCの収量(3、6、9、12、15、17、18)。

タプルのタプルに関するより単純な問題を解決するには、タプルの要素が同じ長さである場合、答えはかなり簡単です。変数「a」がタプルを参照している場合は、次を使用できます。

[i for t in zip(*a) for i in t]

残念ながら、アイテムは必ずしも同じ長さではなく、zipトリックはジェネレーターでは機能しないようです。

これまでのところ、私のコードはひどく醜く、クリーンなソリューションに近づいているものを見つけることができていません。ヘルプ?

4

4 に答える 4

8

itertools.izip_longestが必要だと思います

>>> list([e for e in t if  e is not None] for t in itertools.izip_longest(*some_gen,
                                                               fillvalue=None))
[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12], [13, 14, 15], [16, 17], [18]]
>>> 
于 2013-01-17T11:21:34.917 に答える
4

のドキュメントをitertools.izip_longest見ると、純粋なPython実装が提供されていることがわかります。この実装を変更して、代わりに必要な結果が生成されるようにするのは簡単です(つまり、と同じですが、izip_longest何もありませんfillvalue)。

class ZipExhausted(Exception):
    pass

def izip_longest_nofill(*args):
    """
    Return a generator whose .next() method returns a tuple where the
    i-th element comes from the i-th iterable argument that has not
    yet been exhausted. The .next() method continues until all
    iterables in the argument sequence have been exhausted and then it
    raises StopIteration.

    >>> list(izip_longest_nofill(*[xrange(i,2*i) for i in 2,3,5]))
    [(2, 3, 5), (3, 4, 6), (5, 7), (8,), (9,)]
    """
    iterators = map(iter, args)
    def zip_next():
        i = 0
        while i < len(iterators):
            try:
                yield next(iterators[i])
                i += 1
            except StopIteration:
                del iterators[i]
        if i == 0:
            raise ZipExhausted
    try:
        while iterators:
            yield tuple(zip_next())
    except ZipExhausted:
        pass

izip_longestこれにより、fillvaluesを破棄するためにの出力を再フィルタリングする必要がなくなります。または、「フラット化された」出力が必要な場合:

def iter_round_robin(*args):
    """
    Return a generator whose .next() method cycles round the iterable
    arguments in turn (ignoring ones that have been exhausted). The
    .next() method continues until all iterables in the argument
    sequence have been exhausted and then it raises StopIteration.

    >>> list(iter_round_robin(*[xrange(i) for i in 2,3,5]))
    [0, 0, 0, 1, 1, 1, 2, 2, 3, 4]
    """
    iterators = map(iter, args)
    while iterators:
        i = 0
        while i < len(iterators):
            try:
                yield next(iterators[i])
                i += 1
            except StopIteration:
                del iterators[i]
于 2013-01-17T12:02:02.303 に答える
2

それらをすべて1つのリストにまとめたい場合は、別のitertoolsオプション。これは(@ gg.kasperskyが別のスレッドですでに指摘しているように)生成されたNone値を処理しません。

g = (generator1, generator2, generator3)

res = [e for e in itertools.chain(*itertools.izip_longest(*g)) if e is not None]
print res

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]
于 2013-01-17T11:54:36.300 に答える
1

を検討するかもしれませんitertools.izip_longestが、Noneが有効な値である場合、そのソリューションは失敗します。これがサンプルの「別のジェネレーター」です。これはあなたが要求したことを正確に実行し、かなりクリーンです。

def my_gen(generators):
    while True:
        rez = () 
        for gen in generators:
            try:
                rez = rez + (gen.next(),)
            except StopIteration:
                pass
        if rez:
            yield rez
        else:
            break

print [x for x in my_gen((iter(xrange(2)), iter(xrange(3)), iter(xrange(1))))]

[(0, 0, 0), (1, 1), (2,)] #output
于 2013-01-17T11:46:05.207 に答える