假设我有
data Foo = A String Int | B Int
我想取一个xs :: [Foo]
并对其进行排序,使得所有A
都在开头,按字符串排序,但按照它们出现在列表中的顺序排列,并且然后按照他们出现的顺序将所有的B放在最后。
特别是,我想创建一个包含每个字符串的第一个A和第一个B的新列表。
我是通过定义一个将Foo
转换为(Int, String)
并使用sortBy和groupBy的函数来实现此目的。
有更清洁的方法吗?优选地,推广至至少10个构建体。
可以输入吗?还有什么更好的东西?
编辑:这用于处理在其他地方使用的Foo
列表。已经有一个正常排序的Ord实例。
答案 0 :(得分:4)
您可以使用
sortBy (comparing foo)
其中foo
是一个将有趣的部分提取为可比较的部分的函数(例如Int
s)。
在该示例中,由于您希望A
按其Strings
排序,因此使用所需属性的Int
映射会过于复杂,因此我们使用复合目标类型
foo (A s _) = (0,s)
foo (B _) = (1,"")
将是一个可能的帮手。这或多或少等同于Tikhon Jelvis的建议,但它为自然Ord
实例留出了空间。
答案 1 :(得分:2)
为了更容易为具有大量构造函数的ADT构建比较函数,可以使用SYB将值映射到它们的构造函数索引:
{-# LANGUAGE DeriveDataTypeable #-}
import Data.Generics
data Foo = A String Int | B Int deriving (Show, Eq, Typeable, Data)
cIndex :: Data a => a -> Int
cIndex = constrIndex . toConstr
示例:
*Main Data.Generics> cIndex $ A "foo" 42
1
*Main Data.Generics> cIndex $ B 0
2
答案 2 :(得分:1)
修改:重新阅读您的问题后,我认为最好的选择是让Foo
成为Ord
的实例。我不认为有任何方法可以自动执行此操作,只需使用deriving
将创建不同的行为。
一旦Foo
成为Ord
的实例,您就可以使用sort
中的Data.List
。
在您的确切示例中,您可以执行以下操作:
data Foo = A String Int | B Int deriving (Eq)
instance Ord Foo where
(A _ _) <= (B _) = True
(A s _) <= (A s' _) = s <= s'
(B _) <= (B _) = True
当某个东西是Ord
的实例时,表示数据类型有一些排序。一旦我们知道如何订购某些东西,我们就可以在其上使用一堆现有的函数(例如sort
),它将按照您的需要运行。 Ord
中的任何内容都必须属于Eq
,这是deriving (Eq)
位自动执行的操作。
您还可以派生Ord
。但是,行为将不是您想要的 - 如果必须的话,它将按所有字段排序(例如,它将按顺序放置A
s具有相同的字符串他们的整数)。
进一步编辑:我正在考虑更多,并意识到我的解决方案可能在语义错误。
Ord
实例是关于整个数据类型的声明。例如,我说当派生的B
实例另有说明时,Eq
s总是彼此相等。
如果您所代表的始终的数据行为与此类似(即B
s全部相等且A
s具有相同的字符串全部相等)则为{ {1}}实例是有道理的。否则,您应该不实际执行此操作。
但是,您可以执行与此类似的操作:编写您自己的特殊Ord
函数(compare
),该函数完全封装您要执行的操作,然后使用Foo -> Foo -> Ordering
。这恰当地说明了您的特定排序是特殊的,而不是数据类型的自然排序。
答案 3 :(得分:1)
您可以使用一些模板haskell来填写缺少的传递案例。 mkTransitiveLt
创建给定案例的传递闭包(如果您将它们最少订购到最大)。这为您提供了一个小于的工作,可以将其转换为返回Ordering
的函数。
{-# LANGUAGE TemplateHaskell #-}
import MkTransitiveLt
import Data.List (sortBy)
data Foo = A String Int | B Int | C | D | E deriving(Show)
cmp a b = $(mkTransitiveLt [|
case (a, b) of
(A _ _, B _) -> True
(B _, C) -> True
(C, D) -> True
(D, E) -> True
(A s _, A s' _) -> s < s'
otherwise -> False|])
lt2Ord f a b =
case (f a b, f b a) of
(True, _) -> LT
(_, True) -> GT
otherwise -> EQ
main = print $ sortBy (lt2Ord cmp) [A "Z" 1, A "A" 1, B 1, A "A" 0, C]
生成:
[A "A" 1,A "A" 0,A "Z" 1,B 1,C]
必须在单独的模块中定义 mkTransitiveLt
:
module MkTransitiveLt (mkTransitiveLt)
where
import Language.Haskell.TH
mkTransitiveLt :: ExpQ -> ExpQ
mkTransitiveLt eq = do
CaseE e ms <- eq
return . CaseE e . reverse . foldl go [] $ ms
where
go ms m@(Match (TupP [a, b]) body decls) = (m:ms) ++
[Match (TupP [x, b]) body decls | Match (TupP [x, y]) _ _ <- ms, y == a]
go ms m = m:ms