对于家庭作业,我写了一些scala代码,其中我有以下类和对象(用于建模二叉树):
object Tree {
def fold[B](t: Tree, e: B, n: (Int, B, B) => B): B = t match {
case Node(value, l, r) => n(value,fold(l,e,n),fold(r,e,n))
case _ => e
}
def sumTree(t: Tree): Tree =
fold(t, Nil(), (a, b: Tree, c: Tree) => {
val left = b match {
case Node(value, _, _) => value
case _ => 0
}
val right = c match {
case Node(value, _, _) => value
case _ => 0
}
Node(a+left+right,b,c)
})
}
abstract case class Tree
case class Node(value: Int, left: Tree, right: Tree) extends Tree
case class Nil extends Tree
我的问题是sumTree
函数创建了一个新树,其中节点的值等于其子节点的值加上它自己的值。
我发现它看起来很难看,我想知道是否有更好的方法来做到这一点。如果我使用自顶向下的递归,这会更容易,但我无法想出这样的函数。
我必须使用代码中的签名来实现fold
函数来计算sumTree
我觉得这可以用更好的方式实现,也许你有建议?
答案 0 :(得分:12)
首先,我相信,如果我可以这样说,你做得很好。我可以建议您对代码进行一些细微的更改:
abstract class Tree
case class Node(value: Int, left: Tree, right: Tree) extends Tree
case object Nil extends Tree
Nil
是一个单例,最好定义为case-object而不是case-class。 另外考虑使用Tree
来限定超级sealed
。 sealed
告诉编译器该类只能从同一源文件中继承。这样,只要后续匹配表达式不详尽,编译器就会发出警告 - 换句话说,不包括所有可能的情况。
密封抽象类树
可以对sumTree
进行下一步改进:
def sumTree(t: Tree) = {
// create a helper function to extract Tree value
val nodeValue: Tree=>Int = {
case Node(v,_,_) => v
case _ => 0
}
// parametrise fold with Tree to aid type inference further down the line
fold[Tree](t,Nil,(acc,l,r)=>Node(acc + nodeValue(l) + nodeValue(r) ,l,r))
}
nodeValue
辅助函数也可以定义为(上面使用的替代符号是可能的,因为大括号中的一系列案例被视为函数文字):
def nodeValue (t:Tree) = t match {
case Node(v,_,_) => v
case _ => 0
}
接下来的一点改进是使用fold
(Tree
)参数化fold[Tree]
方法。因为Scala类型inferer按顺序从左到右通过表达式告诉它我们将要处理Tree's让我们在定义函数文字时省略类型信息,该函数文字将进一步传递给fold
。
所以这里是完整的代码,包括建议:
sealed abstract class Tree
case class Node(value: Int, left: Tree, right: Tree) extends Tree
case object Nil extends Tree
object Tree {
def fold[B](t: Tree, e: B, n: (Int, B, B) => B): B = t match {
case Node(value, l, r) => n(value,fold(l,e,n),fold(r,e,n))
case _ => e
}
def sumTree(t: Tree) = {
val nodeValue: Tree=>Int = {
case Node(v,_,_) => v
case _ => 0
}
fold[Tree](t,Nil,(acc,l,r)=>Node(acc + nodeValue(l) + nodeValue(r) ,l,r))
}
}
您提出的递归是唯一可行的方向,它允许您遍历树并生成不可变数据结构的修改副本。在添加到根之前,必须首先创建任何叶节点,因为树的各个节点是不可变的,构造节点所需的所有对象必须在构造之前知道:在创建根之前需要创建叶节点节点
答案 1 :(得分:2)
正如弗拉德所写,你的解决方案是关于这种折叠的唯一一般形状。
仍有一种方法可以摆脱节点值匹配,而不仅仅是将其排除在外。我个人更喜欢这样。
您使用匹配,因为并非每次从递归折叠中获得的结果都会带有一个和。是的,不是每棵树都可以携带它,Nil没有价值的地方,但你的折叠不仅限于树木,是吗?
所以我们有:
case class TreePlus[A](value: A, tree: Tree)
现在我们可以这样折叠:
def sumTree(t: Tree) = fold[TreePlus[Int]](t, TreePlus(0, Nil), (v, l, r) => {
val sum = v+l.value+r.value
TreePlus(sum, Node(sum, l.tree, r.tree))
}.tree
当然,TreePlus
并不是真的需要,因为我们在标准库中有规范产品Tuple2
。
答案 2 :(得分:1)
您的解决方案可能更有效(当然使用更少的堆栈),但这是一个递归解决方案,fwiw
def sum( tree:Tree):Tree ={
tree match{
case Nil =>Nil
case Tree(a, b, c) =>val left = sum(b)
val right = sum(c)
Tree(a+total(left)+total(right), left, right)
}
}
def total(tree:Tree):Int = {
tree match{
case Nil => 0
case Tree(a, _, _) =>a
}
答案 3 :(得分:1)
你可能已经完成了你的作业,但我认为你的代码(以及其他人的答案中的代码)看起来像是你如何建模二叉树的直接结果。如果您使用了递归类型定义而不是使用代数数据类型(Tree
,Node
,Nil
),则不必使用模式匹配来分解你的二叉树。这是我对二叉树的定义:
case class Tree[A](value: A, left: Option[Tree[A]], right: Option[Tree[A]])
正如您所看到的那样,Node
或Nil
不需要{后者只是荣耀null
- 您在代码中不需要这样的内容,是吗? ?)。
根据这样的定义,fold
基本上是一个单行:
def fold[A,B](t: Tree[A], z: B)(op: (A, B, B) => B): B =
op(t.value, t.left map (fold(_, z)(op)) getOrElse z, t.right map (fold(_, z)(op)) getOrElse z)
sumTree
也很短而且很甜蜜:
def sumTree(tree: Tree[Int]) = fold(tree, None: Option[Tree[Int]]) { (value, left, right) =>
Some(Tree(value + valueOf(left, 0) + valueOf(right, 0), left , right))
}.get
其中valueOf
助手定义为:
def valueOf[A](ot: Option[Tree[A]], df: A): A = ot map (_.value) getOrElse df
在任何地方都不需要模式匹配 - 所有这些都是因为二进制树的递归定义很好。