Создание дерева сумм двоичного дерева 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
  1. дерево не должно быть классом case, кроме того, использование класса case в качестве не-листового узла устарело из-за возможного ошибочного поведения автоматически генерируемых методов.
  2. Nil является одноэлементным и лучше всего определяется как case-объект вместо case-класса.
  3. дополнительно рассмотрим квалификационный супер класс 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

нет соответствия шаблону в любом месте - все из - за хорошее рекурсивное определение бинарных деревьев.