Domanda

Ho due iteratori, un list e un oggetto itertools.count (ovvero un generatore di valore infinito). Vorrei unire questi due in un iteratore risultante che alternerà i valori di rendimento tra i due:

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

Qual è il modo più semplice e conciso per farlo?

È stato utile?

Soluzione

Un generatore risolverà bene il tuo problema.

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

Altri suggerimenti

Puoi fare qualcosa che è quasi esattamente quello che @Pramod ha suggerito per la prima volta.

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

Il vantaggio di questo approccio è che non esaurirai la memoria se sia a che b sono infiniti.

Sono anche d'accordo che non è necessario itertools.

Ma perché fermarsi alle 2?

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

gestisce un numero qualsiasi di iteratori da 0 in poi.

AGGIORNAMENTO: DOH! Un commentatore ha sottolineato che questo non funzionerà a meno che tutti gli iteratori non abbiano la stessa lunghezza.

Il codice corretto è:

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

e sì, l'ho appena provato con elenchi di lunghezza diversa, e un elenco contenente {}.

Farei qualcosa del genere. Questo sarà più efficiente in termini di tempo e spazio, dal momento che non avrai il sovraccarico di zippare oggetti insieme. Questo funzionerà anche se sia a che b sono infiniti.

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

Puoi usare zip e itertools.chain . Questo funzionerà solo se il primo elenco è finito :

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

Non sono sicuro di quale sia la tua applicazione, ma potresti trovare più utile la funzione enumerate ().

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

Preferisco questo altro modo che è molto più conciso:

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

Una delle caratteristiche meno conosciute di Python è che puoi avere di più per le clausole in un'espressione del generatore. Molto utile per appiattire gli elenchi nidificati, come quelli che ottieni da zip () / izip ().

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

Ecco una soluzione elegante:

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

Utilizzo di una coda effettiva per prestazioni migliori (come suggerito da 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

Funziona anche quando alcuni iteratori sono limitati e altri infiniti:

from itertools import count

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

Stampe:

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

Si interrompe anche correttamente se / quando tutti gli iteratori sono stati esauriti.

Se vuoi gestire iterabili non iteratori, come gli elenchi, puoi usare

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

Usa izip e chain insieme:

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

Perché è necessario itertools?

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

In questo caso almeno uno di a o b deve essere di lunghezza finita, perché zip restituirà un elenco, non un iteratore. Se hai bisogno di un iteratore come output, puoi scegliere la soluzione Claudiu.

L'uso di itertools.izip (), anziché zip () come in alcune delle altre risposte, migliorerà le prestazioni:

Come " pydoc itertools.izip " mostra: " Funziona come la funzione zip () ma consuma meno memoria restituendo un iteratore invece di un elenco. "

Itertools.izip funzionerà correttamente anche se uno degli iteratori è infinito.

Un metodo conciso è usare un'espressione di generatore con itertools.cycle (). Evita di creare una lunga catena () di tuple.

generator = (it.next() for it in itertools.cycle([i1, i2]))
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top