Ответ 1
Генератор будет хорошо решать вашу проблему.
def imerge(a, b):
for i, j in itertools.izip(a,b):
yield i
yield j
У меня есть два итератора, объект list
и itertools.count
(т.е. бесконечный генератор значений). Я хотел бы объединить эти два в результирующий итератор, который будет чередовать значения доходности между ними:
>>> 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 бесконечны.
Я бы сделал что-то вроде этого. Это будет наиболее эффективным временем и пространством, так как у вас не будет накладных расходов на скрепление объектов. Это также будет работать, если оба 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 не требуется.
Но зачем останавливаться на 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
и да, я просто попробовал его со списками неравной длины и список, содержащий {}.
Вы можете использовать zip
, а также itertools.chain
. Это будет работать только, если первый список конечен:
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 является то, что в выражении генератора вы можете иметь больше предложений. Очень полезно для сглаживания вложенных списков, например, из 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:]
Использование фактической очереди для повышения производительности (как предложил Дэвид):
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 должен иметь конечную длину, то zip вернет список, а не итератор. Если вам нужен итератор в качестве вывода, вы можете пойти на решение Claudiu.
Использование itertools.izip() вместо zip(), как и в некоторых других ответах, улучшит производительность:
Как "pydoc itertools.izip" показывает: "Работает как функция zip(), но потребляет меньше памяти, возвращая итератор вместо списка".
Itertools.izip также будет работать нормально, даже если один из итераторов бесконечен.
Краткий метод заключается в использовании выражения генератора с itertools.cycle(). Он избегает создания длинной цепочки() кортежей.
generator = (it.next() for it in itertools.cycle([i1, i2]))