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])]
答案 0 :(得分:17)
您可以自己轻松编写函数,但如果需要有效的解决方案,则需要对分类器函数的结果设置Ord
或Hashable
约束。例如:
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 上的其他答案。