2011-05-21 52 views
9

.NET框架中的LINQ库确实有一个非常有用的函数GroupBy,我一直在使用它。 其在Haskell类型会是什么样子GroupBy函数来自Haskell中的.NET

Ord b => (a-> b) -> [a] -> [(b, [a])] 

其目的是基于给定的分类功能f项目分为桶,与含有类似的项目每个桶,这是(b, l)使得对于l任何项目xf x == b。它在.NET中的性能是O(N),因为它使用散列表,但在Haskell中,我确定O(N * log(N))。

我在标准的Haskell库中找不到类似的东西。另外,我的标准功能方面实现多少有些笨重:

myGroupBy :: Ord k => (a -> k) -> [a] -> [(k, [a])] 
myGroupBy f = map toFst 
     . groupBy ((==) `on` fst) 
     . sortBy (comparing fst) 
     . map (\a -> (f a, a)) 
    where 
     toFst [email protected]((k,_):_) = (k, map snd l) 

这绝对不是我想看到我之间特定问题的代码。

我的问题是:我怎样才能实现这个功能很好地利用标准库到他们最大?

此外,似乎没有这样的标准功能暗示它可能很少有经验的Haskellers需要,因为他们可能知道一些更好的方法。真的吗?什么可以用来以更好的方式实现类似的功能?

另外,考虑到groupBy已经采取了什么名称呢? :)

+1

及其在Haskell类型是'奥德B =>(A-> B) - >并[a] - > [(二,[a])]' – 2011-05-27 09:26:48

+0

噢,我的...而且没有人注意到! – Rotsor 2011-05-27 10:59:06

回答

3

使用Data.Map作为中间结构:

import Control.Arrow ((&&&)) 
import qualified Data.Map as M 

myGroupBy f = M.toList . M.fromListWith (++) . map (f &&& return) 

map操作接通输入列表与含有元素单列表成对密钥的列表。 M.fromListWith (++)将其变为Data.Map,当两个项目具有相同的密钥时连接,并且M.toList将对再次取出。

请注意,这将反转列表,因此必要时进行调整。如果例如只想要每个组中的元素的总和,则将return(++)替换为其他类似monoid的操作也是容易的。

+0

这个工程!但是,有一点区别:它会颠倒结果列表。很好地使用箭头顺便说一句。他们甚至开始对我有意义! – Rotsor 2011-05-21 04:21:42

+0

啊,是的。你可以通过使用'flip(++)'或者仅仅使用'map(second reverse)',另一个箭头例子来进行后处理来补救这个问题:)后者可能会更有效,因为你避免了可能的O(n^2)列表连接。 – hammar 2011-05-21 04:27:20

+0

@Rotsor:如果没有其他事情发生,你总是可以反转结果列表。这也可能比Data.List版本更快,因为“Data.Map”按照它的顺序排序,而“groupBy”只有一个相等的谓词(想想这意味着......)。 – 2011-05-21 04:28:20