如何在itertools.takewhile()之后不要错过下一个元素

时间:2015-06-03 09:11:53

标签: python itertools

假设我们希望处理迭代器并希望通过块来处理它 每个块的逻辑取决于先前计算的块,因此groupby()没有帮助。

在这种情况下,我们的朋友是itertools.takewhile():

while True:
    chunk = itertools.takewhile(getNewChunkLogic(), myIterator)
    process(chunk)

问题是takewhile()需要经过满足新块逻辑的最后一个元素,因此“吃掉”下一个块的第一个元素。

有各种解决方案,包括包装或单词C ungetc()等。
我的问题是:是否有优雅解决方案?

3 个答案:

答案 0 :(得分:10)

takewhile()确实需要查看下一个元素以确定何时切换行为。

您可以使用跟踪最后看到的元素的包装器,并且可以重置'备份一个元素:

_sentinel = object()

class OneStepBuffered(object):
    def __init__(self, it):
        self._it = iter(it)
        self._last = _sentinel
        self._next = _sentinel
    def __iter__(self):
        return self
    def __next__(self):
        if self._next is not _sentinel:
            next_val, self._next = self._next, _sentinel
            return next_val
        try:
            self._last = next(self._it)
            return self._last
        except StopIteration:
            self._last = self._next = _sentinel
            raise
    next = __next__  # Python 2 compatibility
    def step_back(self):
        if self._last is _sentinel:
            raise ValueError("Can't back up a step")
        self._next, self._last = self._last, _sentinel

在使用takewhile()

之前将迭代器包装在此迭代器中
myIterator = OneStepBuffered(myIterator)
while True:
    chunk = itertools.takewhile(getNewChunkLogic(), myIterator)
    process(chunk)
    myIterator.step_back()

演示:

>>> from itertools import takewhile
>>> test_list = range(10)
>>> iterator = OneStepBuffered(test_list)
>>> list(takewhile(lambda i: i < 5, iterator))
[0, 1, 2, 3, 4]
>>> iterator.step_back()
>>> list(iterator)
[5, 6, 7, 8, 9]

答案 1 :(得分:0)

鉴于可调用GetNewChunkLogic()将在第一个块上报告True,然后在False报告。{ 以下代码段

  1. 解决了下一步的其他步骤&#39;问题takewhile
  2. 很优雅,因为你不必实现后退一步逻辑。
  3. def partition(pred, iterable):
        'Use a predicate to partition entries into true entries and false entries'
        # partition(is_odd, range(10)) -->  1 3 5 7 9 and 0 2 4 6 8
        t1, t2 = tee(iterable)
        return filter(pred, t1), filterfalse(pred, t2)
    
    while True:
        head, tail = partition(GetNewChunkLogic(), myIterator)
        process(head)
        myIterator = tail
    

    但是,最优雅的方法是将GetNewChunkLogic修改为生成器并删除while循环。

答案 2 :(得分:0)

这是另一种方法。当谓词失败时产生一个值 (sentinel),但 before 产生值本身。然后按不是 sentinel 的值分组。

此处,group_by_predicate 需要一个返回谓词 (pred_gen) 的函数。每次谓词失败时都会重新创建:

from itertools import groupby


def group_by_predicate(predicate_gen, _iter):
    sentinel = object()
    def _group_with_sentinel():
        pred = predicate_gen()
        for n in _iter:
            while not pred(n):
                yield sentinel
                pred = predicate_gen()
            yield n
            
    g = _group_with_sentinel()
    for k, g in groupby(g, lambda s: s!=sentinel):
        if k:
            yield g

然后可以这样使用:

def less_than_gen(maxn):
    """Return a predicate that returns true while the sum of inputs is < maxn"""
    def pred(i):
        pred.count += i
        return pred.count < maxn
    pred.count = 0
    return pred

data = iter(list(range(9)) * 3)

for g in group_by_predicate(lambda: less_than_gen(15), data):
    print(list(g))

输出总和都小于 15 的数字组:

[0, 1, 2, 3, 4]
[5, 6]
[7]
[8, 0, 1, 2, 3]
[4, 5]
[6, 7]
[8, 0, 1, 2, 3]
[4, 5]
[6, 7]
[8]