【发布时间】:2021-12-07 19:26:03
【问题描述】:
我正在尝试编写一个生成器函数(或实现等效函数),它在 Python 中采用可迭代的 xs 并计算“运行”。 (这是 Bird 的 Thinking Functionally with Haskell 中的一个问题,我想使用 Python 的惰性特性将其翻译成 Python。)所以
list(iter(count_runs(['a', 'a', 'b', 'c', 'a', 'd', 'd'])))
# => [(2, 'a'), (1, 'b'), (1, c'), (1, 'a'), (2, 'd')]
在 Haskell 中是
countRuns :: [a] -> [(Int, a)]
countRuns [] = []
countRuns x:xs = (1 + length us, x):countRuns vs
where us, vs = span (==x) xs
在 Python 中,我想写一些类似的东西
from itertools import takewhile, dropwhile
def count_runs(xs):
# get first element x of xs, if it exists
us, vs = (takewhile(lambda y: y==x, xs),
dropwhile(lambda y: y==x, xs))
yield (1 + len(list(us)), x)
yield from count_runs(vs)
但问题是vs 已经是一个迭代器,所以如果我在下一次递归中调用takewhile 和dropwhile 会遇到麻烦。 (当我在下一次递归中调用list(takewhile(..., xs)) 时,它也会删除dropwhile(..., xs) 的第一个元素,因为它们都在查看同一个迭代器。
如何解决这个问题,获取第二行第一个元素的正确方法是什么?
【问题讨论】:
-
itertools.tee()也许? -
您并不需要
takewhile和dropwhile,因为Python 迭代器是可变的。但是,takewhile也没有使用模式匹配,因此它将消耗来自xs的非x值以停止产生值。 -
itertools.groupby已经完成了您需要的大部分工作。 -
我想我有点困惑。如果问题是执行
takewhile将导致dropwhile从您希望下一个takewhile开始的位置开始,那么就......根本不要打电话给dropwhile?us = takewhile(lambda y: y==x, xs); yield (1+len(list(us)), x); yield from count_runs(xs)(不把这个放在答案中,因为我不知道 Python,所以可能有一个微妙的理由不这样做。)
标签: python haskell generator code-translation lazy-sequences