Haskell,如何计算树的所有节点(模式匹配)

Haskell,如何计算树的所有节点(模式匹配),haskell,pattern-matching,Haskell,Pattern Matching,问题在标题中:) 我的代码: data tree a = Leaf a | Node (tree a) (tree a) treeo = Node((Node(Leaf 1)(Node (Leaf 10)(Leaf 11)))) (Node(Leaf 12)(Leaf 13)) -- count all leafs: lcounter(Leaf a) = 1 lcounter(Node a b)= lcounter a + lcounter b -- cou

问题在标题中:)

我的代码:

data tree a = Leaf a | Node (tree a) (tree a)

treeo = Node((Node(Leaf 1)(Node (Leaf 10)(Leaf 11))))
                (Node(Leaf 12)(Leaf 13))

-- count all leafs: 
lcounter(Leaf a) = 1
lcounter(Node a b)= lcounter a + lcounter b
-- count all nodes?:

首先请注意,数据类型定义应以大写开头:

data Tree a = Leaf a | Node (Tree a) (Tree a)
要计算所有节点的数量,基本上已经是您所做的,在树数据构造函数上进行简单的模式匹配:

countNodes :: Tree a -> Int
countNodes (Leaf a) = 0
countNodes (Node left right) = 1 + countNodes left + countNodes right
以你的例子:

let tree = Node((Node (Leaf 1) (Node (Leaf 10) (Leaf 11))))(Node (Leaf 12) (Leaf 13))
countNodes tree -- 4

首先请注意,数据类型定义应以大写开头:

data Tree a = Leaf a | Node (Tree a) (Tree a)
要计算所有节点的数量,基本上已经是您所做的,在树数据构造函数上进行简单的模式匹配:

countNodes :: Tree a -> Int
countNodes (Leaf a) = 0
countNodes (Node left right) = 1 + countNodes left + countNodes right
以你的例子:

let tree = Node((Node (Leaf 1) (Node (Leaf 10) (Leaf 11))))(Node (Leaf 12) (Leaf 13))
countNodes tree -- 4
让机器来做这项工作。这两个计数函数都是一个更一般的概念,折叠的实例。我将编写一小部分代码来折叠一棵树,并使用它免费定义两个所需的计数函数

首先,我将稍微概括一下您的
类型,在
节点
s处内部标记为
b
s,在
s处外部标记为
a
s

data Tree a b = Leaf a | Node (Tree a b) b (Tree a b)
原始的
类型相当于
树a()

此版本的
是一个名为的有用类的实例
Bifoldable
泛化为使用两个类型参数处理数据类型

instance Bifoldable Tree where
    bifoldMap f g (Leaf a) = f a
    bifoldMap f g (Node l x r) = bifoldMap f g l `mappend` g x `mappend` bifoldMap f g r
bifoldMap::monoidm=>(a->m)->(b->m)->树a b->m
使用。它识别树中的所有
a
s和
b
s,将
f
g
应用于它们以获得
m
,然后使用
mappend
将结果结构压缩为单个值

这是我将要编写的唯一一段非平凡的代码。其他一切都只是操纵类型
countLeaves
countNodes
都会从
bifoldMap
的牙膏管中挤出来。(有一些可以为您生成
Bifoldable
的实例,因此您甚至不需要编写完成繁重工作的代码!)

每个
Bifoldable
自动以两种方式成为
可折叠的。将
双折叠
转换为
可折叠
,从而聚合其第二个参数

newtype WrappedBifunctor p a b = WrapBifunctor { unwrapBifunctor :: p a b }

instance Bifoldable p => Foldable (WrappedBifunctor p a) where
    foldMap f = bifoldMap (const mempty) f . unwrapBifunctor
这已经足够让我们计算节点数了。计数节点与计数
b
type参数的出现次数相同,这正是
length
对任何
Foldable
所做的。我们所要做的就是将树包装成可折叠的

-- i lied, this is nontrivial too - but it's already in the standard library
length :: Foldable t => t a -> Int
length = foldl' (\c _ -> c+1) 0

countNodes :: Tree a b -> Int
countNodes = length . WrapBifunctor

ghci> let myTree = Node (Node (Leaf 'a') () (Leaf 'b')) () (Node (Leaf 'c') () (Leaf 'd'))
ghci> countNodes myTree
3
数树叶怎么样?这次我们需要计算
类型参数的出现次数
WrappedBifunctor
在这里也有帮助:我们可以重新排列
Tree
的参数,使
a
成为第二个参数,然后将其包装起来-生成的
Foldable
实例将计数
a
s,而不是
b
s。为此,我们需要切换其参数,就像
flip
在值级别所做的那样:

newtype Flip p a b = Flip { runFlip :: p b a }

instance Bifoldable p => Bifoldable (Flip p) where
    bifoldMap f g = bifoldMap g f . runFlip
现在我们可以免费计算假期:

countLeaves :: Tree a b -> Int
countLeaves = length . WrapBifunctor . Flip

ghci> countLeaves myTree
4
最后,您可以使用
Bifoldable
类中的函数计算叶子和节点

bilength :: Bifoldable t => t a b -> Int
bilength = bifoldl' (\c _ -> c+1) (\c _ -> c+1) 0

countLeavesAndNodes :: Tree a b -> Int
countLeavesAndNodes = bilength
-- equivalent to, but more efficient than...
-- countLeavesAndNodes t = countLeaves t + countNodes t

哈斯克尔的哲学是一劳永逸地解决这样的问题,并将这些通用解决方案应用于具体案例。像
Flip
WrappedBifunctor
这样的新类型让我们来表示一般属性(比如“每个
Bifunctor
在翻转参数后仍然是一个
Bifunctor
),而不需要运行时开销。我们使用这些属性操纵类型类系统,编写代码来计算
a
s和
b
s

在继续学习Haskell的过程中,您将了解到当程序是一般模式的实例时如何发现。设置像上面的
双折叠
实例这样的通用结构可以很快提高代码重用的效率。

让机器来完成这项工作。这两个计数函数都是一个更一般的概念,折叠的实例。我将编写一小部分代码来折叠一棵树,并使用它免费定义两个所需的计数函数

首先,我将稍微概括一下您的
类型,在
节点
s处内部标记为
b
s,在
s处外部标记为
a
s

data Tree a b = Leaf a | Node (Tree a b) b (Tree a b)
原始的
类型相当于
树a()

此版本的
是一个名为的有用类的实例
Bifoldable
泛化为使用两个类型参数处理数据类型

instance Bifoldable Tree where
    bifoldMap f g (Leaf a) = f a
    bifoldMap f g (Node l x r) = bifoldMap f g l `mappend` g x `mappend` bifoldMap f g r
bifoldMap::monoidm=>(a->m)->(b->m)->树a b->m
使用。它识别树中的所有
a
s和
b
s,将
f
g
应用于它们以获得
m
,然后使用
mappend
将结果结构压缩为单个值

这是我将要编写的唯一一段非平凡的代码。其他一切都只是操纵类型
countLeaves
countNodes
都会从
bifoldMap
的牙膏管中挤出来。(有一些可以为您生成
Bifoldable
的实例,因此您甚至不需要编写完成繁重工作的代码!)

每个
Bifoldable
自动以两种方式成为
可折叠的。将
双折叠
转换为
可折叠
,从而聚合其第二个参数

newtype WrappedBifunctor p a b = WrapBifunctor { unwrapBifunctor :: p a b }

instance Bifoldable p => Foldable (WrappedBifunctor p a) where
    foldMap f = bifoldMap (const mempty) f . unwrapBifunctor
这已经足够让我们计算节点数了。计数节点与计数
b
type参数的出现次数相同,这正是
length
对任何
Foldable
所做的。我们所要做的就是将树包装成可折叠的

-- i lied, this is nontrivial too - but it's already in the standard library
length :: Foldable t => t a -> Int
length = foldl' (\c _ -> c+1) 0

countNodes :: Tree a b -> Int
countNodes = length . WrapBifunctor

ghci> let myTree = Node (Node (Leaf 'a') () (Leaf 'b')) () (Node (Leaf 'c') () (Leaf 'd'))
ghci> countNodes myTree
3
数树叶怎么样?这次我们需要计算
a
类型的出现次数