【问题标题】:Grouping data types by constructor in Haskell在 Haskell 中按构造函数对数据类型进行分组
【发布时间】:2014-12-17 12:07:09
【问题描述】:

给定这种数据类型

data Val = X Int | Y Bool | Z Double deriving (Eq, Show)

和一个列表,例如

let vals = [X 1, Z 2.7, Y True, X 2, Z 3.14, Y True]

如何将vals 中的元素分组到这个列表中,

[[X 1,X 2],[Y True,Y True],[Z 2.7, Z 3.14]]

【问题讨论】:

    标签: haskell types generic-programming


    【解决方案1】:

    我有以下:

    data Val = X Int | Y Bool | Z Double deriving (Eq, Ord, Show)
    
    vals :: [Val]
    vals = [X 1, Z 2.7, Y True, X 2, Z 3.14, Y True]
    
    valCtorEq :: Val -> Val -> Bool
    valCtorEq (X _) (X _) = True
    valCtorEq (Y _) (Y _) = True
    valCtorEq (Z _) (Z _) = True
    valCtorEq _ _ = False
    

    然后:

    *Main Data.List> groupBy valCtorEq $ sort vals
    [[X 1,X 2],[Y True,Y True],[Z 2.7,Z 3.14]]
    

    【讨论】:

    • 不幸的是,这不是 OP 所要求的。目的是按构造函数对值进行分组,而不是按相等。
    • @PetrPudlák 你说得非常正确,我使用了错误的文件并且没有读取输出......我会编辑。
    【解决方案2】:

    要添加到@RamonSnir 的答案,还可以使用“Scrap your Boilerplate”框架自动构造按构造函数对数据类型进行分组的函数:

    {-# LANGUAGE DeriveDataTypeable #-}
    
    import Data.Data
    import Data.Function (on)
    import Data.List (groupBy, sort)
    
    data Val = X Int | Y Bool | Z Double
      deriving (Eq, Ord, Show, Typeable, Data)
    
    vals :: [Val]
    vals = [X 1, Z 2.7, Y True, X 2, Z 3.14, Y True]
    
    main :: IO ()
    main = print $ groupBy (on (==) toConstr) $ sort vals
    

    两个重要的部分是:

    • 导出TypeableData,以及
    • 使用toConstr 获取在特定值中使用的构造函数的表示形式。

    【讨论】:

      【解决方案3】:

      (这可能极端矫枉过正,但这是一个有趣的问题!)

      提供的答案存在 3 个小问题:

      • 如果所考虑的类型不在Ord 中怎么办(例如,某处有函数)?
      • 另外,这个操作在列表的长度上应该是 O(n log n) 吗?
      • 最后,提供的示例不足以确定分组是否应该是稳定的,即:分组[X 2, X 1]的结果是否应该是[X 1, X 2](如果你使用基于sort的解决方案)还是应按原始顺序保留元素?

      所以这是我能想到的最通用的解决方案。它是稳定的,它不需要Ord(实际上你甚至不需要接触原始数据类型)并且它在大约 O(n * min(n,W)) 时间内运行,其中 W 是你的机器(在我的机器上,它是 64)。也就是说,一旦列表的长度超过 64 个元素,它就是线性的(我说“大约”,因为分组的元素仍然需要从差异列表中重构)。

      {-# LANGUAGE DeriveDataTypeable, StandaloneDeriving #-} 
      
      import Data.Data
      import qualified Data.IntMap as IM
      
      groupByConstructor :: Data a => [a] -> [[a]]
      groupByConstructor = map ($ []) . IM.elems . IM.fromListWith (flip (.)) 
          . map (\a -> (constrIndexOf a, (a:))) where constrIndexOf = constrIndex . toConstr
      
      -- definition of Val as originally posed, without Ord:
      data Val = X Int | Y Bool | Z Double deriving (Eq, Show)
      
      deriving instance Typeable Val
      deriving instance Data Val
      
      -- new example:
      vals = [X 2, Z 2.7, Y True, X 1, Z 3.14, Y False, Z 0.2]
      

      现在groupByConstructor vals 给出了[[X 2, X 1],[Y True, Y False],[Z 2.7, Z 3.14, Z 0.2]],我认为它应该这样做。


      它不适用于对Ints、Chars、Floats 或诸如PtrArray 等不可表示类型的列表进行排序。通过使用一种使用可能的构造函数的数量来进一步降低线性常数的算法,它可能会变得更有效率,但IntMap 现在必须这样做:-)

      【讨论】:

        猜你喜欢
        • 2013-08-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多