按构造函数排序,忽略(部分)值

时间:2012-01-01 06:51:20

标签: haskell

假设我有

data Foo = A String Int | B Int

我想取一个xs :: [Foo]并对其进行排序,使得所有A都在开头,按字符串排序,但按照它们出现在列表中的顺序排列,并且然后按照他们出现的顺序将所有的B放在最后。

特别是,我想创建一个包含每个字符串的第一个A和第一个B的新列表。

我是通过定义一个将Foo转换为(Int, String)并使用sortBy和groupBy的函数来实现此目的。

有更清洁的方法吗?优选地,推广至至少10个构建体。

可以输入吗?还有什么更好的东西?

编辑:这用于处理在其他地方使用的Foo列表。已经有一个正常排序的Ord实例。

4 个答案:

答案 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