我有一个关于以函数式风格编写递归算法的问题。我将在这里使用 Scala 作为示例,但这个问题适用于任何函数式语言。
我正在对n叉树进行深度优先枚举,其中每个节点都有一个标签和可变数量的子节点。这是一个打印叶节点标签的简单实现。
case class Node[T](label:T, ns:Node[T]*)
def dfs[T](r:Node[T]):Seq[T] = {
if (r.ns.isEmpty) Seq(r.label) else for (n<-r.ns;c<-dfs(n)) yield c
}
val r = Node('a, Node('b, Node('d), Node('e, Node('f))), Node('c))
dfs(r) // returns Seq[Symbol] = ArrayBuffer('d, 'f, 'c)
现在说,有时我希望能够通过抛出异常来放弃解析超大树。这在功能语言中可能吗?具体来说,这可能不使用可变状态吗?这似乎取决于您所说的“超大”是什么意思。这是该算法的纯函数版本,当它尝试处理深度为 3 或更大的树时会引发异常。
def dfs[T](r:Node[T], d:Int = 0):Seq[T] = {
require(d < 3)
if (r.ns.isEmpty) Seq(r.label) else for (n<-r.ns;c<-dfs(n, d+1)) yield c
}
但是,如果一棵树因为太宽而不是太深而变得过大怎么办?具体来说,如果我想在第n次递归调用函数时抛出异常,dfs()
而不管递归有多深,该怎么办?我能看到如何做到这一点的唯一方法是拥有一个可变计数器,每次调用都会递增。如果没有可变变量,我看不到如何做到这一点。
我是函数式编程的新手,并且一直在假设你可以用可变状态做的任何事情都可以在没有的情况下完成,但我在这里看不到答案。我唯一能想到的就是编写一个版本,dfs()
以深度优先的顺序返回树中所有节点的视图。
dfs[T](r:Node[T]):TraversableView[T, Traversable[_]] = ...
然后我可以通过说来施加我的限制dfs(r).take(n)
,但我不知道如何编写这个函数。在 Python 中,我只是在yield
访问节点时创建一个生成器,但我不知道如何在 Scala 中实现相同的效果。(Scala 等效于 Python 样式的yield
语句似乎是作为参数传入的访问者函数,但我不知道如何编写其中的一个来生成序列视图。)
编辑接近答案。
Stream
这是一个以深度优先顺序返回节点的函数。
def dfs[T](r: Node[T]): Stream[Node[T]] = {
(r #:: Stream.empty /: r.ns)(_ ++ dfs(_))
}
差不多就是这样。唯一的问题是Stream
memoizes所有结果,这很浪费内存。我想要一个可遍历的视图。以下是想法,但没有编译。
def dfs[T](r: Node[T]): TraversableView[Node[T], Traversable[Node[T]]] = {
(Traversable(r).view /: r.ns)(_ ++ dfs(_))
}
它为操作员提供了“found TraversableView[Node[T], Traversable[Node[T]]]
, requiredTraversableView[Node[T], Traversable[_]]
错误++
。如果我将返回类型更改为TraversableView[Node[T], Traversable[_]]
,我会在“found”和“required”子句切换时遇到同样的问题。所以有一些魔法类型的变体咒语我没有点燃然而,但这已经很接近了。