【问题标题】:How to use Python iterators elegantly如何优雅地使用 Python 迭代器
【发布时间】:2011-05-23 08:55:09
【问题描述】:

我正在尝试更多地使用迭代器进行循环,因为我听说它比索引循环更快。我不确定的一件事是如何很好地处理序列的结尾。我能想到的方法是使用tryexcept StopIteration,这对我来说看起来很难看。

更具体地说,假设我们被要求打印两个排序列表ab 的合并排序列表。我会写以下内容

aNull = False
I = iter(a)
try:
    tmp = I.next()
except StopIteration:
    aNull = True

for x in b:
    if aNull:
        print x
    else:
        if x < tmp:
            print x
        else:
            print tmp,x
            try:
                tmp = I.next()
            except StopIteration:
                aNull = True

while not aNull:
    print tmp
    try:
        tmp = I.next()
    except StopIteration:
        aNull = True

你会如何编码让它更整洁?

【问题讨论】:

  • 该代码几乎难以辨认。描述它应该做什么。
  • a 和 b 是两个排序列表。任务是以非递减顺序打印这两个列表的元素
  • 所以你需要合并两个排序的列表。

标签: python loops iterator


【解决方案1】:

我认为更对称地处理ab 会更容易阅读。此外,使用 Python 2.6 中的内置 next 函数和默认值避免了处理 StopIteration 的需要:

def merge(a, b):
    """Merges two iterators a and b, returning a single iterator that yields
    the elements of a and b in non-decreasing order.  a and b are assumed to each
    yield their elements in non-decreasing order."""

    done = object()
    aNext = next(a, done)
    bNext = next(b, done)

    while (aNext is not done) or (bNext is not done):
        if (bNext is done) or ((aNext is not done) and (aNext < bNext)):
            yield aNext
            aNext = next(a, done)
        else:
            yield bNext
            bNext = next(b, done)

for i in merge(iter(a), iter(b)):
    print i

以下函数概括了适用于任意多个迭代器的方法。

def merge(*iterators):
    """Merges a collection of iterators, returning a single iterator that yields
    the elements of the original iterators in non-decreasing order.  Each of
    the original iterators is assumed to yield its elements in non-decreasing
    order."""

    done = object()
    n = [next(it, done) for it in iterators]

    while any(v is not done for v in n):
        v, i = min((v, i) for (i, v) in enumerate(n) if v is not done)
        yield v
        n[i] = next(iterators[i], done)

【讨论】:

  • 当然,如果你真的想合并两个列表,你应该使用标准库函数heapq.merge
  • 如果作为生成器完成会更好 - 将 a 和 b 传入其中并用 yield 替换 print 语句。然后你可以对结果做任何你想做的事情,它仍然是一个迭代器。
  • @neil 同意。我想到了这一点,但认为这个例子的额外复杂性不值得。但既然你也提到了,我想我会按照你的建议重写它。
【解决方案2】:

你错过了迭代器的全部意义。您无需手动调用I.next(),您只需遍历I

for tmp in I:
    print tmp

已编辑

要合并两个迭代器,请使用itertools 模块中非常方便的函数。你想要的大概是izip:

merged = []
for x, y in itertools.izip(a, b):
    if x < y:
        merged.append(x)
        merged.append(y)
    else:
        merged.append(y)
        merged.append(x)

再次编辑

正如 cmets 中所指出的,这实际上不起作用,因为列表 a 中的多个项目可能小于列表 b 中的下一个项目。但是,我意识到还有另一个内置函数可以处理这个问题:heapq.merge

【讨论】:

  • 我不明白如何使用 for 合并两个迭代器。
  • 那行不通 - 一个迭代器可能有多个项目,两个迭代器之间有两个。
  • @neil 是的,我刚刚意识到这一点。将不得不考虑更多。
【解决方案3】:

函数 sorted 与列表和迭代器一起使用。也许这不是您想要的,但以下代码有效。

a.expand(b) print sorted(iter(a))

【讨论】:

  • 排序转换 iter(a) 成一个列表,然后排序,所以你没有使用生成器...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-07
相关资源
最近更新 更多