假设我们希望处理迭代器并希望通过块来处理它
每个块的逻辑取决于先前计算的块,因此groupby()
没有帮助。
在这种情况下,我们的朋友是itertools.takewhile():
while True:
chunk = itertools.takewhile(getNewChunkLogic(), myIterator)
process(chunk)
问题是takewhile()
需要经过满足新块逻辑的最后一个元素,因此“吃掉”下一个块的第一个元素。
有各种解决方案,包括包装或单词C ungetc()
等。
我的问题是:是否有优雅解决方案?
答案 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
报告。{
以下代码段
takewhile
。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]