2つのPythonイテレータをマージするにはどうすればよいですか?

StackOverflow https://stackoverflow.com/questions/243865

  •  04-07-2019
  •  | 
  •  

質問

2つのイテレーター、 list itertools.count オブジェクト(つまり、無限値ジェネレーター)があります。これら2つを、2つの値を交互に生成するイテレータにマージしたいと思います。

>>> import itertools
>>> c = itertools.count(1)
>>> items = ['foo', 'bar']
>>> merged = imerge(items, c)  # the mythical "imerge"
>>> merged.next()
'foo'
>>> merged.next()
1
>>> merged.next()
'bar'
>>> merged.next()
2
>>> merged.next()
Traceback (most recent call last):
    ...
StopIteration

これを行う最も簡単で最も簡潔な方法は何ですか?

役に立ちましたか?

解決

ジェネレータは問題をうまく解決します。

def imerge(a, b):
    for i, j in itertools.izip(a,b):
        yield i
        yield j

他のヒント

@Pramodが最初に提案したこととほぼまったく同じことができます。

def izipmerge(a, b):
  for i, j in itertools.izip(a,b):
    yield i
    yield j

このアプローチの利点は、aとbの両方が無限の場合にメモリが不足しないことです。

また、itertoolsは必要ないことに同意します。

しかし、なぜ2で停止するのですか?

  def tmerge(*iterators):
    for values in zip(*iterators):
      for value in values:
        yield value

0以上の任意の数の反復子を処理します。

更新:DOH!コメント作成者は、すべてのイテレータが同じ長さでなければ機能しないと指摘しました。

正しいコードは次のとおりです。

def tmerge(*iterators):
  empty = {}
  for values in itertools.izip_longest(*iterators, fillvalue=empty):
    for value in values:
      if value is not empty:
        yield value

そしてはい、長さが等しくないリストと{}を含むリストで試しました。

このようなことをします。オブジェクトをまとめて圧縮するオーバーヘッドがないため、これは時間とスペースの効率が最も高くなります。これは、 a b の両方が無限の場合にも機能します。

def imerge(a, b):
    i1 = iter(a)
    i2 = iter(b)
    while True:
        try:
            yield i1.next()
            yield i2.next()
        except StopIteration:
            return

itertools.chain と同様に zip を使用できます。これは、最初のリストが finite である場合にのみ機能します

merge=itertools.chain(*[iter(i) for i in zip(['foo', 'bar'], itertools.count(1))])

アプリケーションが何なのかわかりませんが、enumerate()関数の方が便利かもしれません。

>>> items = ['foo', 'bar', 'baz']
>>> for i, item in enumerate(items):
...  print item
...  print i
... 
foo
0
bar
1
baz
2

もっと簡潔なこの別の方法が好きです:

iter = reduce(lambda x,y: itertools.chain(x,y), iters)

Pythonのあまり知られていない機能の1つは、ジェネレーター式にfor句を追加できることです。 zip()/ izip()から取得するリストなど、ネストされたリストを平坦化するのに非常に便利です。

def imerge(*iterators):
    return (value for row in itertools.izip(*iterators) for value in row)

エレガントなソリューションは次のとおりです。

def alternate(*iterators):
    while len(iterators) > 0:
        try:
            yield next(iterators[0])
            # Move this iterator to the back of the queue
            iterators = iterators[1:] + iterators[:1]
        except StopIteration:
            # Remove this iterator from the queue completely
            iterators = iterators[1:]

パフォーマンスを向上させるために実際のキューを使用する(Davidの提案による):

from collections import deque

def alternate(*iterators):
    queue = deque(iterators)
    while len(queue) > 0:
        iterator = queue.popleft()
        try:
            yield next(iterator)
            queue.append(iterator)
        except StopIteration:
            pass

一部のイテレータが有限で、他のイテレータが無限であっても機能します:

from itertools import count

for n in alternate(count(), iter(range(3)), count(100)):
    input(n)

印刷:

0
0
100
1
1
101
2
2
102
3
103
4
104
5
105
6
106

また、すべてのイテレータが使い果たされた場合には、正しく停止します。

リストなどの非反復子のイテラブルを処理する場合は、次を使用できます

def alternate(*iterables):
    queue = deque(map(iter, iterables))
    ...

izipとチェーンを一緒に使用:

>>> list(itertools.chain.from_iterable(itertools.izip(items, c))) # 2.6 only
['foo', 1, 'bar', 2]

>>> list(itertools.chain(*itertools.izip(items, c)))
['foo', 1, 'bar', 2]

なぜitertoolsが必要なのですか

def imerge(a,b):
    for i,j in zip(a,b):
        yield i
        yield j

この場合、aまたはbの少なくとも1つは有限の長さでなければなりません。zipは反復子ではなくリストを返します。出力としてイテレータが必要な場合は、Claudiuソリューションを使用できます。

他のいくつかの回答のようにzip()の代わりにitertools.izip()を使用すると、パフォーマンスが向上します。

" pydoc itertools.izip"として" zip()関数と同様に機能しますが、リストの代わりにイテレーターを返すことにより、メモリーの消費が少なくなります。"

Itertools.izipは、反復子の1つが無限であっても適切に機能します。

簡潔な方法は、itertools.cycle()でジェネレーター式を使用することです。タプルの長いchain()の作成を回避します。

generator = (it.next() for it in itertools.cycle([i1, i2]))
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top