如何在Scala中压扁树?

时间:2014-01-04 17:08:43

标签: scala tree functional-programming

编写flatten(lol: List[List[T]]): List[T]非常容易,它会将列表列表转换为新列表。其他“扁平”收藏品(例如Set)似乎也提供flatten

现在我想知道我是否可以为flatten定义Tree[T](定义为TTree[T] s列表。

3 个答案:

答案 0 :(得分:4)

这不是完美的,只是举个例子。您需要做的就是以深度优先或广度优先的方式遍历树并收集结果。与列表flatten几乎相同。

1)定义一个树结构(我知道,我知道这不是最好的方法:)):

scala> case class Node[T](value: T, left: Option[Node[T]] = None,
     |  right: Option[Node[T]] = None)
defined class Node

2)创建一个小树:

scala> val tree = Node(13,
     |   Some(Node(8,
     |     Some(Node(1)), Some(Node(11)))), 
     |   Some(Node(17,
     |     Some(Node(15)), Some(Node(25))))
     | )
tree: Node[Int] = Node(13,Some(Node(8,Some(Node(1,None,None)),Some(Node(11,None,None)))),Some(Node(17,Some(Node(15,None,None)),Some(Node(25,None,None)))))

3)实现可以遍历树的函数:

scala> def observe[T](node: Node[T], f: Node[T] => Unit): Unit = {
     |   f(node)
     |   node.left foreach { observe(_, f) }
     |   node.right foreach { observe(_, f) }
     | }
observe: [T](node: Node[T], f: Node[T] => Unit)Unit

4)用它来定义一个打印所有值的函数:

scala> def printall = observe(tree, (n: Node[_]) => println(n.value))
printall: Unit

5)最后,定义flatten函数:

scala> def flatten[T](node: Node[T]): List[T] = {
     |   def flatten[T](node: Option[Node[T]]): List[T] =
     |     node match {
     |       case Some(n) =>
     |         n.value :: flatten(n.left) ::: flatten(n.right)
     |       case None => Nil
     |     }
     |  
     |  flatten(Some(node))
     | }
flatten: [T](node: Node[T])List[T]

6)我们来测试一下。首先打印所有元素:

scala> printall
13
8
1
11
17
15
25

7)运行flatten

scala> flatten(tree)
res1: List[Int] = List(13, 8, 1, 11, 17, 15, 25)

这是一种像树遍历这样的通用树算法。我让它返回T而不是Node s,根据需要更改它。

答案 1 :(得分:3)

我不确定你想如何准确地定义它,但是你可以看看Scalaz Tree的实现:
https://github.com/scalaz/scalaz/blob/scalaz-seven/core/src/main/scala/scalaz/Tree.scala

如果你想让flatten返回所有Tree节点的列表,那么Scalaz已经为你提供了你想要的东西:

def flatten: Stream[A] 

结果类型是Stream而不是List,但我想这不是问题 如果您想要更复杂的东西,那么您可以使用现有的flatMap

来实现它
def flatMap[B](f: A => Tree[B]): Tree[B]

假设您Tree类型为Tree[Tree[A]],并希望将其展平为Tree[A]

def flatten1: Tree[A] = flatMap(identity)

这也适用于其他更奇怪的场景。例如,您可以拥有Tree[List[A]],并希望在不影响树结构本身的情况下展平Lists内的所有内容:

def flatten2[B]: Tree[List[B]] = flatMap(l => leaf(l.flatten))

看起来它按预期工作:

scala> node(List(List(1)), Stream(node(List(List(2)), Stream(leaf(List(List(3, 4), List(5))))), leaf(List(List(4)))))
res20: scalaz.Tree[List[List[Int]]] = <tree>

scala> res20.flatMap(l => leaf(l.flatten)).drawTree
res23: String = 
"List(1)
|
+- List(2)
|  |
|  `- List(3, 4, 5)
|
`- List(4)
"

值得注意的是,scalaz Tree也是Monad。如果您将查看scalaz / tests / src / test / scala / scalaz / TreeTest.scala,您将看到Tree实现了哪些法则:

checkAll("Tree", equal.laws[Tree[Int]])
checkAll("Tree", traverse1.laws[Tree])
checkAll("Tree", applicative.laws[Tree])
checkAll("Tree", comonad.laws[Tree])

我不知道为什么monad不在这里,但是如果你要添加checkAll("Tree", monad.laws[Tree])并再次运行测试,它们就会通过。

答案 2 :(得分:2)

如果我正确理解了这个问题,你想要像这样定义树:

case class Tree[T]( value:T, kids:List[Tree[T]] )

首先,由于性能影响,我不想在解决方案中使用:::。其次,我想做一些更通用的事情 - 为类型定义折叠运算符,可以用于各种事情 - 然后只需使用折叠来定义flatten

case class Tree[T]( value:T, kids:List[Tree[T]] ) {
  def /:[A]( init:A )( f: (A,T) => A ):A =
    ( f(init,value) /: kids )( (soFar,kid) => ( soFar /: kid )(f) )
  def flatten =
    ( List.empty[T] /: this )( (soFar,value) => value::soFar ).reverse
}

这是一个测试:

scala> val t = Tree( 1, List( Tree( 2, List( Tree(3,Nil), Tree(4,Nil) ) ), Tree(5,Nil), Tree( 6, List( Tree(7,Nil) ) ) ) )
t: Tree[Int] = Tree(1,List(Tree(2,List(Tree(3,List()), Tree(4,List()))), Tree(5,List()), Tree(6,List(Tree(7,List())))))

scala> t.flatten
res15: List[Int] = List(1, 2, 3, 4, 5, 6, 7)