How do I merge two python iterators?

前端 未结 13 1173
花落未央
花落未央 2020-12-06 09:29

I have two iterators, a list and an itertools.count object (i.e. an infinite value generator). I would like to merge these two into a resulting ite

相关标签:
13条回答
  • 2020-12-06 09:47

    A concise method is to use a generator expression with itertools.cycle(). It avoids creating a long chain() of tuples.

    generator = (it.next() for it in itertools.cycle([i1, i2]))
    
    0 讨论(0)
  • 2020-12-06 09:49

    I'm not sure what your application is, but you might find the enumerate() function more useful.

    >>> items = ['foo', 'bar', 'baz']
    >>> for i, item in enumerate(items):
    ...  print item
    ...  print i
    ... 
    foo
    0
    bar
    1
    baz
    2
    
    0 讨论(0)
  • 2020-12-06 09:50

    I'd do something like this. This will be most time and space efficient, since you won't have the overhead of zipping objects together. This will also work if both a and b are infinite.

    def imerge(a, b):
        i1 = iter(a)
        i2 = iter(b)
        while True:
            try:
                yield i1.next()
                yield i2.next()
            except StopIteration:
                return
    
    0 讨论(0)
  • 2020-12-06 09:50

    Use izip and chain together:

    >>> 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]
    
    0 讨论(0)
  • 2020-12-06 09:54

    Here is an elegant solution:

    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:]
    

    Using an actual queue for better performance (as suggested by 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
    

    It works even when some iterators are finite and others are infinite:

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

    Prints:

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

    It also correctly stops if/when all iterators have been exhausted.

    If you want to handle non-iterator iterables, like lists, you can use

    def alternate(*iterables):
        queue = deque(map(iter, iterables))
        ...
    
    0 讨论(0)
  • 2020-12-06 09:58

    One of the less well known features of Python is that you can have more for clauses in a generator expression. Very useful for flattening nested lists, like those you get from zip()/izip().

    def imerge(*iterators):
        return (value for row in itertools.izip(*iterators) for value in row)
    
    0 讨论(0)
提交回复
热议问题