Создание дерева сумм двоичного дерева scala
для домашнего задания я написал код 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
функция, которая создает новое дерево, где узлы имеют значения, равные сумме значений его дочерних элементов плюс его собственное значение.
Я нахожу его довольно уродливым, и мне интересно, есть ли лучший способ сделать это. Если я использую рекурсию, которая работает сверху вниз, это было бы проще, но я не мог придумать такой функции.
Я должен использовать тег
4 ответов
abstract class Tree
case class Node(value: Int, left: Tree, right: Tree) extends Tree
case object Nil extends Tree
- дерево не должно быть классом case, кроме того, использование класса case в качестве не-листового узла устарело из-за возможного ошибочного поведения автоматически генерируемых методов.
-
Nil
является одноэлементным и лучше всего определяется как case-объект вместо case-класса. -
дополнительно рассмотрим квалификационный супер класс
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
}
далее небольшое улучшение parametrising fold
метод Tree
(fold[Tree]
). Поскольку тип Scala inferer работает через выражение последовательно слева направо, сообщая ему, что мы будем иметь дело с деревом, позволяет нам опускать информацию о типе при определении литерала функции, который передается 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))
}
}
рекурсия, которую вы придумали, - это единственное возможное направление, которое позволяет вам пересечь дерево и создать измененную копию неизменяемой структуры данных. Любые листовые узлы должны быть созданы сначала перед добавлением в корень, потому что отдельные узлы дерева неизменяемы, и все объекты, необходимые для построения узла, должны быть известны до построения: листовые узлы должны быть созданы перед созданием корневого узла.
как пишет Влад, ваше решение имеет единственную общую форму, которую вы можете иметь с такой складкой.
все еще есть способ избавиться от соответствия значений узлов, а не только факторизовать его. И лично я предпочел бы, чтобы так и было.
вы используете матч потому что не каждый результат, который вы получаете от рекурсивной складки, несет с собой сумму. Да, не каждое дерево может нести его, Нил не имеет места для ценности, но ваша складка не ограничивается деревьями, это это?
вот так:
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
в стандартной библиотеке.
ваше решение, вероятно, более эффективно (конечно, использует меньше стека), но вот рекурсивное решение, 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
}
вы, вероятно, уже сдали домашнее задание, но я думаю, что все равно стоит отметить, что ваш код (и код в ответах других людей) выглядит как прямой результат того, как вы смоделировали двоичные деревья. Если, вместо использования алгебраического типа данных (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
helper определяется как:
def valueOf[A](ot: Option[Tree[A]], df: A): A = ot map (_.value) getOrElse df
нет соответствия шаблону в любом месте - все из - за хорошее рекурсивное определение бинарных деревьев.