(这可能极端矫枉过正,但这是一个有趣的问题!)
提供的答案存在 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 或诸如Ptr 和Array 等不可表示类型的列表进行排序。通过使用一种使用可能的构造函数的数量来进一步降低线性常数的算法,它可能会变得更有效率,但IntMap 现在必须这样做:-)