Python中的迭代器选择器

Python中的迭代器选择器,python,iterator,Python,Iterator,是否有一种标准的python方法可以从提供的迭代器列表中选择一个值,而不推进未选择的迭代器 对于两个迭代器,有类似的情况(不要太难判断:它很快就被组合在一起,只是为了说明这个想法): 请注意,如果使用类似的方式: [e1 if f(e1, e2) else e2 for (e1, e2) in zip(i1, i2)] 然后,未选择的迭代器每次都会前进,这不是我想要的。您可以将其发送回生成器: def iselect(i1, i2, f): while True: tr

是否有一种标准的python方法可以从提供的迭代器列表中选择一个值,而不推进未选择的迭代器

对于两个迭代器,有类似的情况(不要太难判断:它很快就被组合在一起,只是为了说明这个想法):

请注意,如果使用类似的方式:

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

然后,未选择的迭代器每次都会前进,这不是我想要的。

您可以将其发送回生成器:

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
我将使用一个“排序函数”来代替“选择函数”,它告诉我们哪个元素应该先去

程序首先创建一个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:]
为了测试这个程序(包括一个不产生任何结果的迭代器),我使用

该包有一个可查看的迭代器包装器。如果我对你的问题理解正确的话,这看起来应该是一个非常干净的解决方案。您需要查看一组迭代器的当前值,只需通过调用其上的next()来修改所选迭代器

测试此代码:

# 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

您可以使用itertools.chain将最后一个
放回
迭代器

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

顺便说一下,这将在两个迭代器中的一个被耗尽后退出,即使另一个迭代器还有更多的元素要去。这就是我想要的行为吗?@Reti43是的,这正是我需要的(符合
zip
izip
)我的理解是
send()。此外,
send()
仍会触发下一个要生成的值。如果迭代器是以一种你不能用send影响它们的方式构造的呢<代码>gen=(i代表xrange(5)中的i);下一代();gen.send(10)
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