我在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回答是我目前最好的方法。
答案 0 :(得分:4)
我实际上会寻求更灵活的东西并实现一个通用函数来生成扁平树的惰性流,然后你的很多后期工作将变得更加容易。像这样:
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).sum
,nodes(tree) contains 12
和类似的表达式。
答案 1 :(得分:2)
使用方法exists
和find
(每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
结果不变。
答案 2 :(得分:2)
我想,你正在寻找这样的东西:
@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))
答案 3 :(得分:1)
将集合转换为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)
}
因此,我们将(懒惰地)映射到子项上,将它们转换为搜索节点的Option
s。随后我们发现第一个Option
不是None
。由于getOrElse(None)
本身会返回find
,因此需要最后Option
来“展平”嵌套选项。
我实际上没有运行代码,所以请原谅小错误。但是,一般方法应该变得清晰。