【发布时间】:2017-04-10 04:51:10
【问题描述】:
在python中有groupby函数。
它的类型可以像这样用haskell表示groupby :: a->b->[a]->[(b, [a])]
因为它需要对数据进行排序,我们可以将其运行时间视为O(n*log(n))。
我可能不是唯一对此不满意的人,所以我找到了这个library
groupby 的这种实现需要对输入序列进行两次传递。所以我认为它的运行时间是O(n),但正如它在文档中所说的那样,它并不是真的懒惰,因为如果你不将密钥传递给它,它需要进行传递序列以从项目中收集所有唯一密钥.
所以我想,引用雷蒙德·赫廷格的话
一定有更好的办法!
所以我写了这个
from collections import defaultdict, deque
def groupby(sequence, key=lambda x: x):
buffers = defaultdict(deque)
kvs = ((key(item), item) for item in sequence)
seen_keys = set()
def subseq(k):
while True:
buffered = buffers[k]
if buffered:
yield buffered.popleft()
else:
next_key, value = next(kvs)
buffers[next_key].append(value)
while True:
try:
k, value = next(kvs)
except StopIteration:
for bk, group in buffers.items():
if group and bk not in seen_keys:
yield (bk, group)
raise StopIteration()
else:
buffers[k].append(value)
if k not in seen_keys:
seen_keys.add(k)
yield k, subseq(k)
如果您不熟悉 python,这个想法很简单。
创建key -> queue of elements的可变字典
尝试获取序列的下一个元素及其键值。
如果序列不为空,则根据其键将此值添加到组队列中。如果我们没有看到这个键产生一对 (key, iterable group),后者会从缓冲区或序列中获取键。如果我们已经看到这个,这个键什么也不做,然后循环。
如果序列结束,则意味着它的所有元素都已经放入缓冲区(并且可能已被消耗)。如果缓冲区不为空,我们将对其进行迭代并产生重命名(键,可迭代)对。
我已经对它及其工作进行了单元测试。而且它真的很懒(意味着它不会从序列中获取任何价值,直到消费者没有要求它),它的运行时间应该是O(n)。
我试过用haskell模拟这个函数,但没有找到。
可以在haskell中写这样的东西吗?如果是,请给出解决方案,如果不是,请解释原因。
【问题讨论】:
-
@leftaroundabout 是的,基本一样,但是类型是
a->b->[[a]]。我怎么知道哪个等价类是哪个?你看,我在 hoogle 上搜索了a->b->[(b, [a])] -
@leftaroundabout 另一方面,我可能会尝试阅读源代码并弄清楚如何更改它,以便它返回等价类的名称。我浏览了来源,从进口判断它使用可变状态,对吗?你认为没有可变状态这是可能的吗?
-
显然
[(b, [a])]类型不是你想要的——Haskell 链表不是 python 字典!正如您在下面的答案中看到的那样,您根本无法获得您所寻求的性能。你的 python 函数消耗并产生一个列表并不重要——它在内部使用可变性,你的 Haskell 函数也必须这样做——如果你完全在ST中工作,你仍然可以在最后产生一个纯值。 -
@user1685095 如果你想输入
[(b, [a])],你可以只映射结果列表以将[a]转换为(b, [a])。只需使用map (\l -> (key $ head l, l))
标签: python haskell iterator lazy-sequences