2016-04-03 36 views
11

Czy istnieje standardowy pythonowy sposób wybierania wartości z listy podanych iteratorów bez przesuwania tych, które nie zostały wybrane?Selektor iteratora w języku Python

Coś w stylu tego na dwa iteratory (nie oceniam tego zbyt mocno: szybko został wyrzucony razem tylko do zilustrowania idei):

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 

pamiętać, że jeśli ktoś używa coś takiego zamiast :

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

następnie niewybrany Iterator przesuwa się za każdym razem, co nie jest tym, czego chcę.

+1

Nawiasem mówiąc, to zakończy się, gdy jeden z dwóch iteratorów zostanie wyczerpany, nawet jeśli drugi nadal będzie miał więcej elementów do wykonania. Czy to zamierzone zachowanie? – Reti43

+0

@ Reti43 Tak, dokładnie tego potrzebuję (zgodnie z 'zip' lub' izip') –

Odpowiedz

-1

Można wysłać go z powrotem do generatora:

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

Moje zrozumienie polega na tym, że 'send()' nie [wystarczy umieścić zwróconą wartość z powrotem do generatora, aby uzyskać ponownie następną ] (http://stackoverflow.com/questions/19302530/python-generator-send-function- purpose). Poza tym, 'send()' wyzwala i tak dalej następną wartość, która ma zostać uzyskana. A co, jeśli iteratory są skonstruowane w taki sposób, że nie można na nie wpłynąć przy wysyłaniu? 'gen = (i dla i w xrange (5)); gen.next(); gen.send (10) '. – Reti43

1

Zamiast „Funkcja wyboru”, użyłbym „funkcję sortowania”, który mówi, który element powinien iść pierwszy.

Program rozpoczyna się od utworzenia listy 2-krotnej: (iterator, aktualna wartość). Ponieważ jest możliwe, aby jeden iterator był pusty, musi to być wykonane przy pomocy try..catch (to znaczy, że nie może mieć zwartej postaci).

Po drugie, powtarzamy tak długo, jak istnieje co najmniej jeden iterator. Funkcja sortowania stawia element, który ma wyjść w pierwszej kolejności. Ten element jest "poddany". Następnie wywoływany jest iterator, aby uzyskać następny element. Jeśli nie ma więcej elementów, iterator zostanie usunięty z listy.

To daje następujący kod

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

przetestować ten program (w tym iterator że plony nic), użyłem

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

Pakiet more-itertools ma peekable otoki dla iteratorów. Wydaje się, że powinno to pozwolić na bardzo czyste rozwiązanie, jeśli dobrze zrozumiem twoje pytanie. Musisz zerknąć na bieżące wartości zestawu iteratorów i modyfikować tylko wybrany iterator przez wywołanie next() na nim.

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

przetestować ten kod:

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

wyjściowa:

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

można użyć itertools.Łańcuch do poprzedzić ostatni item powrotem na iterator:

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

iz wielu iteratorów:

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

Na przykład

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) 

daje

0 
0 
0 
1 
1 
1 
2 
2 
2 
3