【问题标题】:Haskell equivalent to Scala's groupByHaskell 相当于 Scala 的 groupBy
【发布时间】:2013-03-14 14:28:26
【问题描述】:

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])]

【问题讨论】:

    标签: haskell


    【解决方案1】:

    您可以很容易地自己编写函数,但如果您想要一个有效的解决方案,您需要在分类器函数的结果上放置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 这样的哈希映射,而不是对预期的线性时间进行排序。

    【讨论】:

    • 我对此进行了轻微修改,以提供同时对值应用函数的 C# 选项:myGroupBy f g xs = map (f . head &&& g) . groupBy ((==) `on` f) . sortBy (compare `on` f) $ xs
    • @Impredicative:这看起来确实很有用!
    • @Impredicative: myCSharpGroupby f g xs = map (second g) $ myGroupBy f xs 也可以使用
    • 不是等价的,因为: 1. 额外的Ord 要求; 2. 更差的时间复杂度
    【解决方案2】:

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

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

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

    map (\xs -> (f (head xs), xs)) . scalaGroupBy f
    

    【讨论】:

    • using 函数定义在哪里?
    • @NiklasB。好问题,Hoogle 似乎没有找到它。然而我发誓它曾经在那里?!就像比较 f 是 f x f y,所以使用 f 应该是 f x == f y
    • 所以基本上equaling 什么的。我认为Data.Function.on 是这些概念的概括,因为comparing = on compareusing = on (==)
    • @NiklasB。我已将其替换为 (==) on,也许我在这里混淆了一些东西。
    【解决方案3】:

    这不是 List 库中的函数。

    可以写成sortBy和groupBy的组合。

    【讨论】:

      【解决方案4】:

      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)
      

      【讨论】:

      • 我不喜欢head——我想出了foldr go [] where go (k, x) (k', xs) | k == k' = (k, x:xs); go (k, x) kxs = (k, [x]) : kxs,但也许这不是更清楚。
      • (我知道head 永远不会崩溃。但我更喜欢语法永远不会崩溃的代码,而不必考虑它)
      • @BenMillwood,您的代码没有进行类型检查。我对在groupgroupBy 产生的子列表上使用head 有同样的疑虑,但现在我已经习惯了。
      • 哦,哎呀。 foldr go [] where go (k, x) ((k', xs) : ys) | k == k' = (k, x:xs) : ys; go (k, x) kxs = (k, [x]) : kxs
      【解决方案5】:

      无论是否排序,此解决方案都将按 (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])]

      【讨论】:

        【解决方案6】:

        由于 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]
        

        【讨论】:

          【解决方案7】:

          我们还可以在列表解析中使用类似于 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 的其他答案。

          【讨论】:

            猜你喜欢
            • 2010-11-20
            • 2012-05-13
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-09-17
            • 1970-01-01
            • 1970-01-01
            • 2017-04-16
            相关资源
            最近更新 更多