2016-04-03 57 views
11

是否存在從提供的迭代器列表中選擇一個值而不推進那些未被選中的標準Python方法?Python中的迭代器選擇器

東西在這個靜脈兩個迭代器(不要判斷這太難了:它很快就被扔在一起只是爲了說明的想法):

def iselect(i1, i2, f): 
    e1_read = False 
    e2_read = False 

    while True: 
     try: 
      if not e1_read: 
       e1 = next(i1) 
       e1_read = True 

      if not e2_read: 
       e2 = next(i2) 
       e2_read = True 

      if f(e1, e2): 
       yield e1 
       e1_read = False 
      else: 
       yield e2 
       e2_read = False 
     except StopIteration: 
      return 

注意,如果使用了這樣的事情,而不是:

[e1 if f(e1, e2) else e2 for (e1, e2) in zip(i1, i2)] 

然後非選定的迭代器每次前進,這是不是我想要的。

+1

順便說一下,一旦兩個迭代器中的一個被耗盡,即使另一個迭代器仍然有更多的元素要走,它將退出。這是預期的行爲? – Reti43

+0

@ Reti43是的,這正是我所需要的(符合'zip'或'izip') –

回答

-1

你可以把它放回發生器:相反

def iselect(i1, i2, f): 
    while True: 
     try: 
      e1, e2 = next(i1), next(i2) 
      if f(e1, e2): 
       yield e1 
       i2.send(e2) 
      else: 
       yield e2 
       i1.send(e1) 
     except StopIteration: 
      return 
+5

我的理解是'send()'不會[只是將生成的值返回到生成器以便再次生成](http://stackoverflow.com/questions/19302530/python-generator-send-function-purpose)。此外,'send()'觸發下一個值,無論如何。那麼如果迭代器的構建方式不能通過發送來影響它們呢? 'gen =(我爲xrange(5)中的i); gen.next(); gen.send(10)'。 – Reti43

1

一個「選擇功能」的,我會用一個「排序功能」,它告訴哪個元素應該先走。

程序從創建2元組列表開始:(iterator,當前值)。由於一個迭代器可能爲空,所以必須使用try..catch(即它不能以緊湊形式)完成。

其次,只要有至少一個迭代器,我們就迭代。排序功能將首先必須外出的元素放在一起。這個元素是「屈服」的。之後,迭代器被調用來獲取下一個元素。如果沒有更多元素,迭代器將從列表中刪除。

這使下面的代碼

def iselect(list_of_iterators, sort_function): 
    work_list = [] 
    for i in list_of_iterators: 
    try: 
     new_item = (i, next(i)) # iterator and its first element 
     work_list.append(new_item) 
    except StopIteration: 
     pass      # this iterator is empty, skip it 
    # 
    while len(work_list) > 0: 
    # this selects which element should go first 
    work_list.sort(lambda e1,e2: sort_function(e1[1],e2[1])) 
    yield work_list[0][1] 
    # update the first element of the list 
    try: 
     i, e = work_list[0] 
     e = next(i) 
     work_list[0] = (i, e) 
    except StopIteration: 
     work_list = work_list[1:] 

中測試該程序(包括產生任何一個迭代)中,我使用

def iter_vowels(): 
    for v in 'aeiouy': 
    yield v 

def iter_consonnants(): 
    for c in 'bcdfghjklmnpqrstvwxz': 
    yield c 

def no_letters(): 
    if 1==2:  # not elegant, but.. 
    yield "?" # .."yield" must appear to make this a generator 

def test(): 
    i1 = iter_vowels() 
    i2 = iter_consonnants() 
    i3 = no_letters() 
    sf = lambda x,y: cmp(x,y) 
    for r in iselect((i1,i2,i3), sf): 
    print (r) 

test() 
5

more-itertools該包裝具有用於迭代器peekable包裝。如果我正確理解你的問題,看起來這應該允許一個非常乾淨的解決方案。您需要查看一組迭代器的當前值,並通過調用next()來修改所選迭代器。

from more_itertools import peekable 

# the implementation of iselect can be very clean if 
# the iterators are peekable 
def iselect(peekable_iters, selector): 
    """ 
    Parameters 
    ---------- 
    peekable_iters: list of peekables 
     This is the list of iterators which have been wrapped using 
     more-itertools peekable interface. 
    selector: function 
     A function that takes a list of values as input, and returns 
     the index of the selected value. 
    """ 
    while True: 
     peeked_vals = [it.peek(None) for it in peekable_iters] 
     selected_idx = selector(peeked_vals) # raises StopIteration 
     yield next(peekable_iters[selected_idx]) 

測試此代碼:

# sample input iterators for testing 
# assume python 3.x so range function returns iterable 
iters = [range(i,5) for i in range(4)] 

# the following could be encapsulated... 
peekables = [peekable(it) for it in iters] 

# sample selection function, returns index of minimum 
# value among those being compared, or StopIteration if 
# one of the lists contains None 
def selector_func(vals_list): 
    if None in vals_list: 
     raise StopIteration 
    else: 
     return vals_list.index(min(vals_list)) 

for val in iselect(peekables, selector_func): 
    print(val)  

輸出:

0 
1 
1 
2 
2 
2 
3 
3 
3 
3 
4 
2

您可以使用itertools。鏈預先設置最後item放回iterator

import itertools as IT 
iterator = IT.chain([item], iterator) 

而且與許多迭代器:

items = map(next, iterators) 
idx = f(*items) 
iterators = [IT.chain([item], iterator) if i != idx else iterator 
      for i, (item, iterator) in enumerate(zip(items, iterators))] 

例如,

import itertools as IT 

def iselect(f, *iterators): 
    iterators = map(iter, iterators) 
    while True: 
     try: 
      items = map(next, iterators) 
     except StopIteration: 
      return 
     idx = f(*items) 
     iterators = [IT.chain([item], iterator) if i != idx else iterator 
        for i, (item, iterator) in enumerate(zip(items, iterators))] 
     yield items[idx] 

def foo(*args): 
    return sorted(range(len(args)), key=args.__getitem__)[0] 

i1 = range(4) 
i2 = range(4) 
i3 = range(4) 
for item in iselect(foo, i1, i2, i3): 
    print(item) 

產生

0 
0 
0 
1 
1 
1 
2 
2 
2 
3