【问题标题】:Traverse tree in a functional way以功能方式遍历树
【发布时间】:2016-05-04 12:24:35
【问题描述】:

我已经在 Scala 中实现了一个基本的可变树,我想以一种函数式的方式遍历它以搜索一个元素,但我不知道如何实现它。如果可能的话,我还希望算法是尾递归的。

树是一个结构,它有一个值和一个叶子列表,叶子也是树。

任何帮助将不胜感激。

这是我拥有的代码(专注于 getOpt 方法):

package main

import scala.collection.mutable.ListBuffer

sealed trait Tree[Node] {

  val node: Node

  val parent: Option[Tree[Node]]

  val children: ListBuffer[Tree[Node]]

  def add(n: Node): Tree[Node]

  def size: Int

  def getOpt(p: (Node) => Boolean): Option[Tree[Node]]

  override def toString = {
    s"""[$node${if (children.isEmpty) "" else s", Children: $children"}]"""
  }
}

case class ConcreteTree(override val node: Int) extends Tree[Int] {

  override val children = ListBuffer[Tree[Int]]()

  override val parent: Option[Tree[Int]] = None

  override def add(n: Int): ConcreteTree = {
    val newNode = new ConcreteTree(n) {override val parent: Option[Tree[Int]] = Some(this)}
    children += newNode
    newNode
  }

  override def size: Int = {
    def _size(t: Tree[Int]): Int = {
      1 + t.children.foldLeft(0)((sum, tree) => sum + _size(tree))
    }
    _size(this)
  }

  // This method is not correct
  override def getOpt(p: (Int) => Boolean): Option[Tree[Int]] = {
    def _getOpt(tree: Tree[Int], p: (Int) => Boolean): Option[Tree[Int]] = {
      tree.children.map {
        t =>
          if(p(t.node)) Some(t) else t.children.map(_getOpt(_, p))
      }
    }
  }
}

object Main {

  def main(args: Array[String]) {
    val tree1 = ConcreteTree(1)
    val tree2 = tree1.add(2)
    val tree3 = tree1.add(3)

    println(tree1.getOpt(_ == 2))
  }
}

@doertsch 的回答是我目前最好的方法。

【问题讨论】:

  • 一个简单的按顺序遍历应该可以。除非树是有序的
  • 你试过写代码吗?
  • @Archeg 是的,我知道如何以 OOP 方式进行操作,但我不知道如何以功能方式进行操作。例如,a 不能映射到叶子列表上,因为我想返回 Option[Tree[]] 但它会导致 ListBuffer[Option[Tree[]]] 并且我也想要找到结果时完成。
  • @vicaba 那么请发布您尝试过的代码,我们会尽力帮助您解决问题。
  • @Archeg 我已经添加了代码。

标签: algorithm scala data-structures tree functional-programming


【解决方案1】:

我实际上会寻求更灵活的东西并实现一个通用函数来生成扁平树的惰性流,这样你以后的很多工作就会变得容易得多。像这样的:

def traverse[Node](tree: Tree[Node]): Stream[Tree[Node]] = 
  tree #:: (tree.children map traverse).fold(Stream.Empty)(_ ++ _)

那么你的getOpt 会变成这样:

override def getOpt(p: (Int) => Boolean): Option[Tree[Int]] =
  traverse(tree) find {x => p(x.node)}

进一步简化,如果您只对没有Tree 结构的数据感兴趣,您可以只获取节点流,给您:

def nodes[Node](tree: Tree[Node]): Stream[Node] = traverse(tree) map (_.node)

def getNode(p: (Int) => Boolean): Option[Int] = nodes(tree) find p

这为非常简洁易读的代码开辟了其他可能性,例如nodes(tree) filter (_ > 3)nodes(tree).sumnodes(tree) contains 12 和类似的表达式。

【讨论】:

  • 您好 Karl,2 个问题:1. 遍历函数的返回类型似乎是 Stream[Tree[_ <: rncomponent stream def traverse tree map>) 2. + + 运算符未定义。以下对我不起作用。 def ++(t0: Tree[RNComponent], t1: Tree[RNComponent]): Stream[Tree[ <: rncomponent t0 t1>
【解决方案2】:

使用existsfind 方法(每个List 提供),您可以实现“找到结果时完成”行为。 (尽管可能会在内部争辩说,这些并没有以完全功能的方式实现:https://github.com/scala/scala/blob/5adc400f5ece336f3f5ff19691204975d41e652e/src/library/scala/collection/LinearSeqOptimized.scala#L88

您的代码可能如下所示:

case class Tree(nodeValue: Long, children: List[Tree]) {

  def containsValue(search: Long): Boolean =
    search == nodeValue || children.exists(_.containsValue(search))

  def findSubTreeWithNodeValue(search: Long): Option[Tree] =
    if (search == nodeValue)
      Some(this)
    else
      children.find(_.containsValue(search)).
        flatMap(_.findSubTreeWithNodeValue(search))
}

在最后两行,find 应用程序将返回当前节点的正确子树,如果存在一个,flatMap 部分将从它递归提取正确的子树,或者离开 None如果未找到该值,则结果不变。

然而,这个有一个令人讨厌的特点,即两次执行部分遍历,一次用于确定结果是否存在,一次用于从包含它的树中提取它。可能有办法以更有效的方式解决此问题,但我目前无法弄清楚......

【讨论】:

  • 谢谢,我没有将答案标记为解决方案,因此有人可以提出“更好”的解决方案。
【解决方案3】:

我想,你正在寻找这样的东西:

@tailrec
def findInTree[T](value: T, stack: List[Node[T]]): Option[Node[T]] = stack match {
   case Nil => None
   case Node(value) :: _ => stack.headOption
   case head :: tail => findInTree(value, head.children ++ tail)
}

这不会两次遍历树的各个部分,并且也是尾递归的。 为了清楚起见,我稍微更改了您的类定义,但这是相同的想法。 你可以这样称呼它:findInTree(valueToFind, List(root))

【讨论】:

    【解决方案4】:

    当将集合转换为view 时,所有像map 这样的转换器都是惰性实现的。这意味着仅根据需要处理元素。这应该可以解决您的问题:

    override def getOpt(p: (Int) => Boolean): Option[Tree[Int]] = {
      if (p(node)) Some(this)
      else children.view.map(_.getOpt(p)).find(_.isDefined).getOrElse(None)
    }
    

    所以我们正在(懒惰地)映射孩子,将它们变成搜索节点的Options。随后我们发现第一个这样的Option 不是None。最后的getOrElse(None) 是“展平”嵌套选项所必需的,因为find 本身返回一个Option

    我并没有实际运行代码,所以请原谅小错误。但是,一般方法应该已经很清楚了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-18
      • 2015-11-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-01-02
      相关资源
      最近更新 更多