2013-01-17 9 views
5

Verfahrgeschwindigkeit habe ich eine Reihe von Generatoren: (gen_0, gen_1, ... gen_n)eine Folge von Generatoren

Diese Generatoren ihre Werte träge schaffen, aber endlich sind und möglicherweise unterschiedliche Längen haben.

Ich muss in der Lage sein, einen anderen Generator zu konstruieren, der das erste Element jedes Generators der Reihe nach liefert, gefolgt von der zweiten usw., wobei die Werte von Generatoren, die erschöpft sind, übersprungen werden.

glaube ich dieses Problem zu nehmen das Tupel analog

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

und durchquert, so dass es die Zahlen von 1 bis 18 um ergeben würde.

Ich arbeite an der Lösung dieses einfachen Beispiels mit (genA, genB, genC) mit genA liefert Werte von (1, 4, 7, 10, 13, 16), genB ergibt (2, 5, 8, 11 , 14) und genC ergibt (3, 6, 9, 12, 15, 17, 18).

Um das einfachere Problem mit dem Tupel von Tupeln zu lösen, ist die Antwort ziemlich einfach, wenn die Elemente des Tupels die gleiche Länge haben. Wenn die Variable ‚a‘ auf das Tupel bezeichnet, könnte man

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

verwenden Leider sind die Elemente sind nicht notwendigerweise die gleiche Länge und die Zip-Trick scheint nicht ohnehin für Generatoren zu arbeiten.

Bis jetzt ist mein Code schrecklich hässlich und ich finde nichts, das einer sauberen Lösung nähert. Hilfe?

+0

'itertools.izip_longest'; Sie können einen Sentinel übergeben, um die auslaufenden Generatoren zu puffern. Wenn Sie möchten, können Sie diesen Sentinel dann aus den Ergebnissen filtern. – katrielalex

Antwort

1

Sie itertools.izip_longest betrachten könnte, aber im Falle None ist ein gültiger Wert, diese Lösung fehlschlagen. Hier ist ein Beispiel „ein weiterer Generator“, die genau das tut, was Sie gefragt, und ist ziemlich sauber:

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

Sie können 'iter (range (x))' anstelle von 'simple_gen (x)' verwenden. –

+0

Oder noch besser: 'iter (xrange (x))' auf Python 2.x. – phant0m

+0

Danke, ich werde reparieren. – kaspersky

8

Ich glaube, Sie brauchen 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]] 
>>> 
+2

Ich würde vorschlagen zu verwenden "wenn e ist nicht keine". – kaspersky

+2

Was wäre, wenn None ein gültiger Wert wäre? – kaspersky

+0

Es ist einfach, den Fall von 'None' zu ​​behandeln und diesen Ansatz beizubehalten.Fügen Sie einfach vorher eine Zeile 'sentinel = object()' hinzu und verwenden Sie dann 'e ist nicht sentinel' und' fillvalue = sentinel'. – DSM

2

Eine weitere itertools Option, wenn Sie sie in einer einzigen Liste alle kollabiert wollen; dies (wie @ gg.kaspersky bereits in einem anderen Thread darauf hingewiesen hat) verarbeitet keine generierten None Werte.

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

Wenn Sie itertools.izip_longest in der Dokumentation anschauen, werden Sie sehen, dass es eine reine Python-Implementierung gibt. Es ist einfach, diese Implementierung zu ändern, damit es die Ergebnisse produziert müssen Sie stattdessen (das heißt, genau wie izip_longest, aber ohne 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 

Dies vermeidet die Notwendigkeit, erneut Filter die Ausgabe von izip_longest zu verwerfen die Füllwerte Wenn Sie alternativ einen "abgeflachten" Ausgang haben möchten:

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