类似于pyk groupby in haskell

时间:2016-11-25 18:47:01

标签: python haskell iterator lazy-sequences

在python中有groupby函数。

它的类型可以用haskell表示,如groupby :: a->b->[a]->[(b, [a])] 因为需要对数据进行排序,我们可以将其运行时间视为O(n*log(n))

我可能不是唯一对此不满意的人,所以我发现了library groupby的这种实现需要在输入序列上进行两次传递。所以我认为它的运行时间是O(n),但正如它在文档中说的那样,它并不是非常懒惰,因为如果你不这么做的话。将密钥传递给它需要对序列进行传递以从项目中收集所有唯一键。

所以我想,引用Raymond Hetttinger

  

必须有更好的方法!

所以我写了这个

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的可变字典 尝试获取序列的下一个元素及其键值。 如果sequence不是空的,则根据其键将该值添加到组队列中。如果我们还没有看到这个密钥产生一对(密钥,可迭代组),那么后者将从缓冲区或序列中获取密钥。如果我们已经看到这个键,则不再做任何事情并循环。

如果序列结束,则意味着它的所有元素已经放入缓冲区(并且可能已被消耗)。如果缓冲区不是空的,我们迭代它们并产生重命名(密钥,可迭代)对。

我已经对它及其作品进行了单元测试。而且它真的很懒惰(意味着它不能从序列中获取任何价值,直到消费者无法获得它)并且它的运行时间应为O(n)

我已尝试使用此功能的类似功能,并且找不到任何功能。

有可能在haskell中写这样的东西吗?如果是,请显示解决方案,如果没有,请解释原因。

1 个答案:

答案 0 :(得分:0)

如果我理解正确,你想要的类型是

(a -> k) -> [a] -> [(k, [a])]

即,给定关键功能和项目列表,按键对项目进行分组。

在Haskell中有一个库函数groupBy,它可以做类似的事情。它假定您有一个排序列表,并将符合布尔条件的项分组到子列表中。我们可以用它来做你想做的事:

import Data.List
import Data.Ord

groupByKey :: (a -> k) -> [a] -> [(k, [a])]
groupByKey keyF xs = map getResult groups
   where
      keyPairs = map (\v -> (keyF v, v)) xs
      groups = groupBy (\v1 v2 -> fst v1 == fst v2) 
                  $ sortBy (comparing fst) keyPairs
      getResult xs = (fst $ head xs, map snd xs)

keyPairs是参数中每个元素的对(key, value)groups首先使用sortBy将此排序为关键顺序,然后将结果分组为共享相同密钥的子列表。 getResult将子列表转换为包含密钥(从head元素中获取)和原始值列表的对。 我们可以安全地使用head,因为groupBy永远不会提供空的子列表。