2013-01-17 56 views
5

的順序我有發電機的序列:(gen_0,gen_1,... gen_n)遍歷發電機

這些發電機將懶洋洋地創造自己的價值,但是是有限的,將有可能不同的長度。

我需要能夠構建另一個發生器產生,以便每個發生器的第一元件,接着是第二等等,從發電機跳過值已用盡。

我覺得這個問題是類似於拍攝的元組

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

和穿越它,這樣它會從1到18的順序產生的數字。 (1,4,7,10,13,16),genB yielding(2,5,8,11)中我正在使用(genA,genB,genC)解決這個簡單的例子,14)和產生GENC(3,6,9,12,15,17,18)。

要解決元組元組的簡單問題,如果元組的元素長度相同,則答案相當簡單。如果變量「A」所指的元組,你可以使用

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

不幸的是,項目不一定是相同的長度和拉鍊招似乎並不爲發電機工作反正。

到目前爲止,我的代碼是可怕的醜陋,我沒有找到任何接近一個乾淨的解決方案。幫幫我?

+0

'itertools.izip_longest';你可以通過一個標記來填充耗盡的發電機。如果你想,你可以過濾結果中的哨兵。 – katrielalex

回答

1

你可能會考慮itertools.izip_longest,但如果沒有一個有效的價值,該解決方案將失敗。下面是一個示例「另一個生成」,這不正是你問什麼,倒也乾淨:

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

你可以使用'iter(range(x))'而不是'simple_gen(x)'。 –

+0

或更好:Python 2.x上的'iter(xrange(x))'。 – phant0m

+0

謝謝,我會解決的。 – kaspersky

8

我想你需要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

我建議使用「如果e不是無」。 – kaspersky

+2

此外,如果都不是一個有效的價值? – kaspersky

+0

處理'None'的情況並保持這種方法很容易。只需預先添加一行'sentinel = object()',然後使用'e不是哨兵'和'fillvalue = sentinel'。 – DSM

2

,如果你想他們都在一個單獨的列表倒塌另一個itertools選項;這(如@ gg.kaspersky已經在另一個線程中指出)不處理產生None值。

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

如果你看一下itertools.izip_longest的文檔,你會看到,它給出了一個純Python實現。可以很容易地修改這個實現,這樣它產生的結果,你需要的,而不是(也就是,就像izip_longest,但沒有任何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 

這就避免了需要重新過濾的izip_longest輸出丟棄填充值。或者,如果你想要一個「平坦」輸出:

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]