2016-08-01 62 views
3

创建奥德情况下,给定的数据类型,如与NEWTYPE包装

data Foo = Bar | Baz | Qux 

,我希望有多个不同的排序为这种类型,是下列最常见/标准来实现这种方式?

newtype FooPriorityA = FooPriorityA { unFooPriorityA :: Foo } 

instance Ord FooPriorityA where 
    compare (FooPriorityA x) (FooPriorityA y) = f x `compare` f y 
     where f :: Foo -> Int 
       f Baz = 1 
       f Bar = 2 
       f Qux = 3 

newtype FooPriorityB = FooPriorityB ... and so on 

委托给Int的Ord实例就像那样疯狂?与写出compare的n^2比较,感觉更安全,而且工作量更少。

我可能忽略了这个“黑客”的任何明显的缺陷?它甚至是“黑客”?

回答

5

你只需要O(ñ)(2 ​ ñ - 1,要准确)定义来指定一个总订单,只要你按照优先顺序增加指定情况:

instance Ord FooPriorityA where 
    compare (FooPriorityA x) (FooPriorityA y) | x == y = EQ -- Use the Eq instance 
               -- Bar is the smallest 
               | x == Bar = LT 
               | y == Bar = GT 
               -- Baz is the next smallest 
               | x == Baz = LT 
               | y == Baz = GT 
               -- Proceed in the desired order. 
               -- You don't need to say anything 
               -- explicit about the largest value 

第一种情况(x == y)涵盖了O(n)种可能性。接下来的每个案例可能看起来都不完整,但它带有暗示的信息,即前面的每个案例都是错误的。例如,x == Bar = LT并不意味着其中x == Bar评估为LT;已经处理了x == Bar && y == Bar的情况,所以第二种情况实际上是暗含x == Bar && y /= Bar。同样,案例4(x == Baz)假定y /= Baz(由案例1的失败暗示匹配)和y /= Bar(暗示情况3的匹配失败)。因此,y的任何剩余可能值实际上大于Baz

你走下的列表越多,未处理的案例就越少。到最后,你不需要对最大的项目做任何明确的说明;所有关于它如何与其他对比的信息n - 上述情况已经捕获了1项。


另外,请记住,你的f定义为最小执行Enum类,然后你可以用它来定义Ord实例的实例中的一半(fromEnum)。

instance Enum FooPriorityA where 
    fromEnum Bar = 1 
    fromEnum Baz = 2 
    fromEnum Qux = 3 
    toEnum 1 = Bar 
    toEnum 2 = Baz 
    toEnum 3 = Qux 

instance Ord FooPriorityA where 
    compare x y = compare (fromEnum x) (fromEnum y) 
    -- compare = compare `on` Data.Function.fromEnum 
+0

这是一些很棒的信息,谢谢。关于我的'f'和'Enum'类型之间关系的观察很有意思。 – SimonF

7

这是绝对合理的。事实上,你可以使用the comparing function from Data.Ord使这更是直接:

instance Ord FooPriorityA where 
    compare (FooPriorityA x) (FooPriorityA y) = comparing f x y 
    where f :: Foo -> Int 
      f Baz = 1 
      f Bar = 2 
      f Qux = 3 

虽然根据自己的喜好,与compare版本可能会根据自己的喜好。

0

如何做一个函数来生成映射从Foo基于表示要订购名单上Int

import Data.List 

data Foo = Bar | Baz | Qux deriving Eq 

newtype FooPriorityA = FooPriorityA { unFooPriorityA :: Foo } deriving Eq 

fooOrdering :: [Foo] -> Foo -> Foo -> Ordering 
fooOrdering orderedList a b = f a `compare` f b 
    where f x = elemIndex x orderedList 

instance Ord FooPriorityA where 
    compare (FooPriorityA x) (FooPriorityA y) = fooOrdering [Baz, Bar, Qux] x y 

你甚至可以添加断言,以确保所有元素出现一次在列表中。

+0

我想我更喜欢的模式匹配内部的情况,主要是因为当你的'fooOrdering'节省一些字符,这使得它可以提供一些相当无意义的值,例如,[酒吧,酒吧,酒吧,酒吧]。而模式匹配会给出这种事情的警告。我想你可以完全填补Ints。 – SimonF

+0

@SimonF:我想你会通过添加一个断言来防止非感性的值,以确保所有的元素只出现在列表中一次。但是你是对的,编译器已经可以做到模式匹配。 – Thilo

+0

我不喜欢我的解决方案的另一件事是它必须在运行时为每次比较迭代列表。在编译时构建一个静态映射(或者至少在首次使用后记忆)会很好。 – Thilo