对此进行编码的一种好方法是依靠 Data.Foldable 提供的遍历。
{-# LANGUAGE DeriveFunctor, DeriveFoldable #-}
import Data.Foldable
import Data.Monoid
我们可以使用扩展自动派生它的一个实例,但我们需要重新排序 Node 构造函数的字段,以便为我们提供按顺序遍历。
当我们这样做时,我们应该消除对数据类型本身的约束。它们实际上并没有提供任何好处,并且在 Haskell 2011 中已从语言中删除。(当您想要使用此类约束时,您应该将它们放在类的实例上,而不是放在数据类型上。)
data BST a
= Void
| Node
{ left :: BST a
, val :: a
, right :: BST a
} deriving (Eq, Ord, Read, Show, Foldable)
首先,我们定义严格排序的列表意味着什么。
sorted :: Ord a => [a] -> Bool
sorted [] = True
sorted [x] = True
sorted (x:xs) = x < head xs && sorted xs
-- head is safe because of the preceeding match.
然后我们就可以使用上面helpertoList
提供的方法了。Data.Foldable
isBST :: Ord a => BST a -> Bool
isBST = sorted . toList
我们也可以更直接地实现这一点,就像你问的那样。由于我们删除了对数据类型的虚假约束,我们可以简化折叠的定义。
cata :: (b -> a -> b -> b) -> b -> BST a -> b
cata _ z Void = z
cata f z (Node l x r) = f (cata f z l) x (cata f z r)
现在我们需要一个数据类型来模拟我们的变质结果,即我们要么没有节点(Z
),要么有一系列严格递增的节点(T
),要么失败(X
)
data T a = Z | T a a | X deriving Eq
然后我们可以isBST
直接实现
isBST' :: Ord a => BST a -> Bool
isBST' b = cata phi Z b /= X where
phi X _ _ = X
phi _ _ X = X
phi Z a Z = T a a
phi Z a (T b c) = if a < b then T a c else X
phi (T a b) c Z = if b < c then T a c else X
phi (T a b) c (T d e) = if b < c && c < d then T a e else X
这有点乏味,所以也许将我们组成临时状态的方式分解一下会更好:
cons :: Ord a => a -> T a -> T a
cons _ X = X
cons a Z = T a a
cons a (T b c) = if a < b then T a c else X
instance Ord a => Monoid (T a) where
mempty = Z
Z `mappend` a = a
a `mappend` Z = a
X `mappend` _ = X
_ `mappend` X = X
T a b `mappend` T c d = if b < c then T a d else X
isBST'' :: Ord a => BST a -> Bool
isBST'' b = cata phi Z b /= X where
phi l a r = l `mappend` cons a r
就个人而言,我可能只使用 Foldable 实例。