2016-04-03 4 views
11

Существует ли стандартный питоновский способ выбора значения из списка предоставленных итераторов без продвижения тех, которые не были выбраны?Селектор итератора в 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 = (i для i в xrange (5)); gen.next(); gen.send (10) '. – Reti43

1

Вместо «функции выбора», я хотел бы использовать «функцию сортировки», который говорит, какой элемент должен идти первым.

Программа начинается с создания списка из 2-х кортежей: (итератор, текущее значение). Поскольку один итератор может быть пустым, это должно быть сделано с помощью 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 
Смежные вопросы