Haskell相当于Scala的groupBy

时间:2013-03-14 14:28:27

标签: scala haskell

Scala在列表上有一个函数groupBy,它接受​​一个从列表项中提取键的函数,并返回另一个列表,其中的项是由键和生成该键的项列表组成的元组。换句话说,就像这样:

List(1,2,3,4,5,6,7,8,9).groupBy(_ % 2)
// List((0, List(2,4,6,8)), (1, List(1,3,5,7,9)))

(实际上,它看起来像在当前版本中提供了Map,但这并不重要。 C#有一个更有用的版本,允许你同时映射值(如果你的关键函数只是提取元组的一部分,那么非常有用)。

Haskell有一个groupBy,但它有些不同 - 根据一些比较函数对事物进行分组。

在我去写它之前,在Haskell中是否有相当于Scala的groupBy? Hoogle对于我希望签名看起来没有任何东西(下图),但我可能错了。

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

7 个答案:

答案 0 :(得分:17)

您可以自己轻松编写函数,但如果需要有效的解决方案,则需要对分类器函数的结果设置OrdHashable约束。例如:

import Control.Arrow ((&&&))
import Data.List
import Data.Function

myGroupBy :: (Ord b) => (a -> b) -> [a] -> [(b, [a])]
myGroupBy f = map (f . head &&& id)
                   . groupBy ((==) `on` f)
                   . sortBy (compare `on` f)

> myGroupBy (`mod` 2) [1..9]
[(0,[2,4,6,8]),(1,[1,3,5,7,9])]      

您还可以使用像Data.HashMap.Strict这样的哈希映射,而不是按预期的线性时间进行排序。

答案 1 :(得分:4)

具体来说,以下内容应该有效:

scalaGroupBy f = groupBy ((==) `on` f) . sortBy (comparing f)

模数,这不会得到每个组中f的结果,但如果你确实需要它,你可以随时使用

进行后处理
map (\xs -> (f (head xs), xs)) . scalaGroupBy f

答案 2 :(得分:3)

这不是列表库中的函数。

您可以将其编写为sortBy和groupBy的组合。

答案 3 :(得分:1)

trace中放置f表明,对于@Niklas解决方案,f对任何长度为2或更长的列表中的每个元素进行3次评估。我冒昧地修改它,以便f仅应用于每个元素一次。然而,目前尚不清楚创建和销毁元组的成本是否低于多次评估f的成本(因为f可以是任意的)。

import Control.Arrow ((&&&))
import Data.List
import Data.Function

myGroupBy' :: (Ord b) => (a -> b) -> [a] -> [(b, [a])]
myGroupBy' f = map (fst . head &&& map snd)
                   . groupBy ((==) `on` fst)
                   . sortBy (compare `on` fst)
                   . map (f &&& id)

答案 4 :(得分:0)

此解决方案将打破并按(f x)分组,无论它是否已排序

f = (`mod` (2::Int))

list = [1,3,4,6,8,9] :: [Int]


myGroupBy :: Eq t => (b -> t) -> [b] -> [(t, [b])]

myGroupBy f (z:zs) = reverse $ foldl (g f) [(f z,[z])] zs
  where
    -- folding function                        
    g f ((tx, xs):previous) y = if (tx == ty)
                           then (tx, y:xs):previous
                           else (ty, [y]):(tx, reverse xs):previous
        where ty = f y                        

main = print $ myGroupBy f list

结果:[(1,[1,3]),(0,[4,6,8]),(1,[9])]

答案 5 :(得分:0)

由于Scala groupBy返回一个不变的HashMap,不需要排序,因此相应的Haskell实现也应返回一个HashMap

import qualified Data.HashMap.Strict as M

scalaGroupBy :: (Eq k, Hashable k) => (v -> k) -> [v] -> M.HashMap k [v]
scalaGroupBy f l = M.fromListWith (++) [ (f a, [a]) | a <- l]

答案 6 :(得分:0)

我们也可以在列表推导中使用类似 SQL 的 then group by 语法,这需要 TransformListComp 语言扩展。

由于 Scala groupBy 返回 Map,我们可以调用 fromDistinctAscList 将列表推导式转换为 Map

$ stack repl --package containers
Prelude> :set -XTransformListComp
Prelude> import Data.Map.Strict ( fromDistinctAscList, Map )
Prelude Data.Map.Strict> import GHC.Exts ( groupWith, the )
Prelude Data.Map.Strict GHC.Exts> :{
Prelude Data.Map.Strict GHC.Exts| scalaGroupBy f l =
Prelude Data.Map.Strict GHC.Exts|   fromDistinctAscList
Prelude Data.Map.Strict GHC.Exts|     [ (the key, value)
Prelude Data.Map.Strict GHC.Exts|     | value <- l
Prelude Data.Map.Strict GHC.Exts|     , let key = f value
Prelude Data.Map.Strict GHC.Exts|     , then group by key using groupWith
Prelude Data.Map.Strict GHC.Exts|     ]
Prelude Data.Map.Strict GHC.Exts| :}
Prelude Data.Map.Strict GHC.Exts> :type scalaGroupBy
scalaGroupBy :: Ord b => (t -> b) -> [t] -> Map b [t]
Prelude Data.Map.Strict GHC.Exts> scalaGroupBy (`mod` 2) [1, 2, 3, 4, 5, 6, 7, 8, 9]
fromList [(0,[2,4,6,8]),(1,[1,3,5,7,9])]

与 Scala groupBy 的唯一区别是上面的实现返回一个排序映射而不是哈希映射。有关返回哈希映射的实现,请参阅我在 https://stackoverflow.com/a/64204797/955091 上的其他答案。