请考虑以下对二进制树和一元树的定义,一个函数flatten
,它将二进制树和一元树转换为列表(例如flatten (Node (Leaf 10) 11 (Leaf 20))
为[10,11,20]
)和一个函数{{ 1}},它将列表转换为二叉树(按照此处描述的特定方式(Defining a function from lists to binary and unary trees),并在下图中说明):
reverseflatten
data Tree a = Leaf a | Node (Tree a) a (Tree a) | UNode a (Tree a) deriving (Show)
flatten :: Tree a -> [a]
flatten (Leaf x) = [x]
flatten (Node l x r) = flatten l ++ [x] ++ flatten r
flatten (UNode l x) = [l] ++ flatten x
reverseflatten :: [a] -> Tree a
reverseflatten [x] = (Leaf x)
reverseflatten [x,y] = UNode x (Leaf y)
reverseflatten [x,y,z] = Node (Leaf x) y (Leaf z)
reverseflatten (x:y:xs) = revflat2 (x:y:xs)
revflat2 :: [a] -> Tree a
revflat2 [x] = (Leaf x)
revflat2 [x,y] = UNode y (Leaf x)
revflat2 [x,y,z] = Node (Leaf x) y (Leaf z)
revflat2 (x:y:xs) = Node (Leaf x) y (revflat2 ([head $ tail xs] ++ [head xs] ++ tail (tail xs)))
是reverseflatten [1..5]
,但是Node (Leaf 1) 2 (Node (Leaf 4) 3 (Leaf 5)
的返回值与(reverseflatten(flatten(reverseflatten [1..5])))
相同。如何修改reverseflatten [1..5]
,使flatten
与reverseflatten x: xs
相同?
(reverseflatten(flatten(reverseflatten x:xs)))
在下图中形成了一系列树。
例如,图片中的reverseflatten
构成树2,reverseflatten [x,y,z]
构成树3,reverseflatten [x,y,z, x']
构成树4,reverseflatten [x,y,z, x', y']
构成树5,reverseflatten [x,y,z, x', y', z']
构成树6等等。
我想要的是reverseflatten [x,y,z, x', y', z', x'']
与reverseflatten x: xs
相同。因此,我需要设计(reverseflatten(flatten(reverseflatten x:xs)))
才能起到这种作用。
我做了以下尝试(假设情况flatten
分为flatten Node l x r
是叶子的情况和不是情况):
r
但这会产生:
flatten :: Tree a -> [a]
flatten (Leaf x) = [x]
flatten (UNode l x) = [l] ++ flatten x
flatten (Node l x r)
| r == Leaf y = [l, x, r]
| otherwise = flatten (Node l x (revflat2 ([head $ tail r] ++ [head r] ++ tail (tail r)))
答案 0 :(得分:3)
我认为您的问题是树的第一个节点与其他节点没有相同的模式,就像您查看Tree1时,它进入[x,y,z],而Tree4中则进入[x,y ,[x',z,y']]。
您可以看到子节点的排序不符合第一个子节点的排序,这就是为什么有些人指出它感觉不自然的原因。要解决此问题,您可以将reverseFlattening的定义更改为具有恒定模式(我认为您不想要)的定义,或者更改展平图以考虑此奇怪的模式:
data Tree a = Leaf a | Node (Tree a) a (Tree a) | UNode a (Tree a) deriving (Show)
reverseFlatten :: [a] -> Tree a
reverseFlatten [x] = (Leaf x)
reverseFlatten [x,y] = UNode y (Leaf x)
reverseFlatten [x,y,z] = Node (Leaf x) y (Leaf z)
reverseFlatten (x:y:xs) = Node (Leaf x) y (reverseFlatten ((xs !! 1) : (head xs) : (drop 2 xs)))
flatten :: Tree a -> [a]
flatten (Leaf x) = [x]
flatten (UNode l (Leaf x)) = [l,x]
flatten (Node (Leaf l) x r) = l : x : flattenRest r
flattenRest :: Tree a -> [a]
flattenRest (Leaf x) = [x]
flattenRest (UNode l (Leaf x)) = [l,x]
flattenRest (Node (Leaf l) x r) = x : l : flattenRest r
请注意,我扩展了UNode和左侧节点的模式匹配,因为您已经知道它将是一棵左侧的树,因此如果您已经知道结果将是什么,则无需调用函数。
答案 1 :(得分:1)
首先,我们可以将您的规范reverseflatten (flatten (reverseflatten (x : xs))) = reverseflatten (x : xs)
作为QuickCheck属性来实现。
我们通过flatten
和reverseflatten
对其进行参数化,因此很容易插入不同的实现。
我们将元素类型专用于Int
,因为我们必须告诉QuickCheck在某个时候生成什么。
类型变量a
的真正含义是Tree Int
,但泛型将在以后有用。
import Test.QuickCheck
prop_flat :: (Eq a, Show a) =>
(a -> [Int]) -> ([Int] -> a) -> (Int, [Int]) -> Property
prop_flat f rf (x0, xs0) =
(rf . f . rf) xs === rf xs
where
xs = x0 : xs0
-- Also remember to derive both Show and Eq on Tree.
通过将其应用于错误的实现,我们可以检查它是否具有重要意义。
ghci> quickCheck $ prop_flat flatten reverseflatten
*** Failed! Falsifiable (after 5 tests and 8 shrinks):
(0,[0,0,1,0])
Node (Leaf 0) 0 (Node (Leaf 0) 1 (Leaf 0)) /= Node (Leaf 0) 0 (Node (Leaf 1) 0 (Leaf 0))
现在flatten
的实现需要分为两个阶段,例如reverseflatten
,因为根的行为不同于其他节点:
在根Node (Leaf x) y (Leaf z)
→[x, y, z]
,
但在内部节点中,Node (Leaf x) y (Leaf z)
→[y, x, z]
还要注意,您显示的所有树以及可以由reverseflatten
实际生成的树都向右倾斜,因此我们实际上只知道对模式Leaf x
,{{ 1}}和UNode x (Leaf y)
,但不能使用其他模式,例如Node (Leaf x) y r
或UNode x (Node ...)
。因此,考虑到Node (Node ...) y r
的整个领域,Tree
是非常不完整的:
flatten1
尽管存在偏见,但QuickCheck同意:
flatten1 :: Tree a -> [a]
flatten1 (Leaf x) = [x]
flatten1 (UNode x (Leaf y)) = [x, y]
flatten1 (Node (Leaf x) y r) = x : y : flatten1' r
flatten1' :: Tree a -> [a]
flatten1' (Leaf x) = [x]
flatten1' (UNode x (Leaf y)) = [x, y]
flatten1' (Node (Leaf y) x r) = x : y : flatten1' r
通过稍微概括一下模式就可以获得全部功能,但是如上面的测试所示,该规范并未涵盖这些额外情况。每当我们在嵌套的ghci> quickCheck $ prop_flat flatten1 reverseflatten
+++ OK, passed 100 tests.
上进行模式匹配时,我们只会获取整棵树Leaf y
并将其展平。如果确实是ys
,则它将被展平为一个单例列表,因此保留了原始语义。
ys = Leaf y
我们可以在其域的未指定部分上任意泛泛地泛泛功能,我们还可以限制其域以使其与规范完全匹配。这导致了另一种类型定义:在所有示例中,flatten2 :: Tree a -> [a]
flatten2 (Leaf x) = [x]
flatten2 (UNode x ys) = x : flatten2 ys
flatten2 (Node xs y r) = flatten2 xs ++ y : flatten2' r
flatten2' :: Tree a -> [a]
flatten2' (Leaf x) = [x]
flatten2' (UNode x ys) = x : flatten2' ys
flatten2' (Node ys x r) = x : flatten2' ys ++ flatten2' r
仅具有叶子子树,而类似的UNode
仅具有叶子作为左子树,因此我们将这些叶子解压缩到构造函数中。>
Node
data Tree' a = Leaf' a | UNode' a a | Node' a a (Tree' a)
deriving (Eq, Show)
的实现是flatten'
的直接改编:
flatten1
flatten' :: Tree' a -> [a]
flatten' (Leaf' x) = [x]
flatten' (UNode' x y) = [x, y]
flatten' (Node' x y r) = x : y : f'' r
f'' :: Tree' a -> [a]
f'' (Leaf' x) = [x]
f'' (UNode' x y) = [x, y]
f'' (Node' x y r) = y : x : f'' r
类似地改编自reverseflatten'
的重构版本。
reverseflatten
QuickCheck验证:
reverseflatten' :: [a] -> Tree' a
reverseflatten' (x : []) = Leaf' x
reverseflatten' (x : y : []) = UNode' x y
reverseflatten' (x : y : z : r) = Node' x y (rf'' z r)
rf'' :: a -> [a] -> Tree' a
rf'' x [] = Leaf' x
rf'' x (y : []) = UNode' x y
rf'' x (y : z : r) = Node' y x (rf'' z r)
答案 2 :(得分:1)
让我们假设一个稍微强一些的属性,只是不加思索地进行计算,看看它能为我们带来什么。也就是说,更强大的属性是,只要xs
不为空,我们就有:
flatten (reverseflatten xs) = xs
根据reverseflatten
的定义,有四种情况需要考虑。首先是这样:
flatten (reverseflatten [x]) = [x]
flatten (Leaf x) = [x]
下一步:
flatten (reverseflatten [x,y]) = [x,y]
flatten (UNode x (Leaf y)) = [x,y]
然后:
flatten (reverseflatten [x,y,z]) = [x,y,z]
flatten (Node (Leaf x) y (Leaf z)) = [x,y,z]
最后:
flatten (reverseflatten (x:y:xs)) = x:y:xs
flatten (revflat2 (x:y:xs)) = x:y:xs
由于先前的模式已经捕获了xs
与[]
或[_]
匹配的情况,因此我们只需要考虑revflat2
的一种情况,即{{ 1}}至少具有两个元素。
xs
啊哈!为此,最好有一个具有新属性的助手,即:
flatten (revflat2 (x:y:w:z:xs)) = x:y:w:z:xs
flatten (Node (Leaf x) y (revflat2 (z:w:xs))) = x:y:w:z:xs
(当然,我们实际上将使用名称flatten2 (revflat2 (z:w:xs)) = w:z:xs
和x
代替y
和w
。)
再一次让我们计算而无需思考。 z
有三种情况,即xs
,[]
和更长的情况。当[_]
为xs
时:
[]
对于flatten2 (revflat2 [x,y]) = [y,x]
flatten2 (UNode y (Leaf x)) = [y,x]
:
[_]
更长的时间:
flatten2 (revflat2 [x,y,z]) = [y,x,z]
flatten2 (Node (Leaf x) y (Leaf z)) = [y,x,z]
根据归纳假设,我们有flatten2 (revflat2 (x:y:w:z:xs)) = y:x:w:z:xs
flatten2 (Node (Leaf x) y (revflat2 (z:w:xs))) = y:x:w:z:xs
,因此最后一个等式可以变为:
flatten2 (revflat2 (z:w:xs)) = w:z:xs
现在,我们可以将所有这些案例的所有最后一行都收录下来,然后编写一个程序:
flatten2 (Node (Leaf x) y rest) = y:x:flatten2 rest
这是最好的程序吗?没有!特别是,这是局部的-当flatten (Leaf x) = [x]
flatten (UNode x (Leaf y)) = [x,y]
flatten (Node (Leaf x) y (Leaf z)) = [x,y,z]
flatten (Node (Leaf x) y rest) = x:y:flatten2 rest
flatten2 (UNode y (Leaf x)) = [y,x]
flatten2 (Node (Leaf x) y (Leaf z)) = [y,x,z]
flatten2 (Node (Leaf x) y rest) = y:x:flatten2 rest
或flatten
的第一个树参数不存在时,您可以自由选择flatten2
和Node
的内容UNode
(但是无论您做出什么选择都不会影响您关心的属性)以及Leaf
应该如何处理叶子。如果您在这里做出明智的选择,则可能会合并许多模式。
但是此过程的优点是完全 机械的:您可以获取您感兴趣的属性,摇动曲柄,并获得具有该属性的函数(或相互矛盾的方程式,告诉您它是不可能,为什么)。仅当您拥有某些有用的东西时,您才需要凝视并思考什么会使它更漂亮或更美好。是的,方程式推理!