问题描述
我自己在 Haskell 中实现了列表,但是在比较两个列表是否相等时遇到了一个小问题,
data List a = Void | Cons a (List a) -- deriving (Show,Eq)
instance (Eq a) => Eq (List a) where
(==) a b = equalHelp a b
equalHelp :: (Eq a) => List a -> List a -> Bool
equalHelp Void Void = True
equalHelp a Void = False
equalHelp Void b = False
equalHelp (Cons a b) l = if (myElem a l) then (equalHelp b l) else False
myElem :: (Eq a) => a -> List a -> Bool
myElem a Void = False
myElem a (Cons c d) = if (a == c) then True
else myElem a d
例如,如果我有
l1 = (Cons 1 (Cons 2 (Cons 3 (Cons 4 (Cons 5 Void)))))
如果我执行 l1 == l1
,那么它不会打印 True
,而是打印 False
。
我错过了什么?
解决方法
您的 equalHelp
实现了 isSubset subset ofSet
谓词 -- 几乎,除了它总是滑入空的第二个列表案例然后失败的事实。
相反,在单例情况下停止递归:
isSubset :: (Eq a) => List a -> List a -> Bool
isSubset Void Void = True
isSubset a Void = False
isSubset Void b = False
isSubset (Cons a Void) l = if (myElem a l) then True else False
isSubset (Cons a b) l = if (myElem a l) then (isSubset b l) else False
你的第二个子句假设 a
是一个非空列表,因为前面的子句。但是,最好是每个子句的假设都是明确的,并且模式是相互排斥的(如果这样做不会使我们的代码过于冗长,从而降低可读性)。因此,我们把它写成
isSubset :: (Eq a) => List a -> List a -> Bool
isSubset Void Void = True
isSubset (Cons _ _) Void = False
isSubset Void (Cons _ _) = False
isSubset (Cons a Void) l = myElem a l
isSubset (Cons a b) l = myElem a l && isSubset b l
这里故意留下一个错误,你的代码的痕迹。其中一个子句是错误的:空集是 any set 的子集。因此,处理空集作为第一个参数的两个子句可以合并为一个子句。
我们还使用了代码简化
if A then True else False === A
if A then B else False === A && B
在此重写。还有
if A then True else C === A || C
您可以使用它来简化您的 myElem
定义。
另一种纠正代码的方法是将其视为定义列表的相等性直至元素排序。按照注释中的建议,在每次递归调用时也删除第二个参数的 head 元素不会起作用,因为我们需要删除找到的元素,而这些元素可能不是——也可能不是——head 元素。这可能不是很简单,也不是很有效,特别是如果您还选择允许元素的多样性。在这种情况下,最简单的方法是定义
equalUpToOrderingWithMults a b =
isSubset a b && isSubset b a
最直接的方法是放弃 myElem
调用,而是在递归的每个步骤中仅比较两个参数列表的头部元素,以获得“正常”,可以说,相等的概念。