«Стек вызовов» и «рекурсия» - это просто популярные шаблоны проектирования, которые впоследствии были включены в большинство языков программирования (и, таким образом, стали в основном «невидимыми»). Ничто не мешает вам переопределить обе структуры данных кучи. Итак, вот «очевидное» решение в стиле ретро TAOCP 1960-х годов:
trait Node { val name: String }
case class BranchNode(name: String, children: List[Node]) extends Node
case class LeafNode(name: String) extends Node
def mapLeaves(root: Node, f: LeafNode => LeafNode): Node = {
case class Frame(name: String, mapped: List[Node], todos: List[Node])
@annotation.tailrec
def step(stack: List[Frame]): Node = stack match {
// "return / pop a stack-frame"
case Frame(name, done, Nil) :: tail => {
val ret = BranchNode(name, done.reverse)
tail match {
case Nil => ret
case Frame(tn, td, tt) :: more => {
step(Frame(tn, ret :: td, tt) :: more)
}
}
}
case Frame(name, done, x :: xs) :: tail => x match {
// "recursion base"
case l @ LeafNode(_) => step(Frame(name, f(l) :: done, xs) :: tail)
// "recursive call"
case BranchNode(n, cs) => step(Frame(n, Nil, cs) :: Frame(name, done, xs) :: tail)
}
case Nil => throw new Error("shouldn't happen")
}
root match {
case l @ LeafNode(_) => f(l)
case b @ BranchNode(n, cs) => step(List(Frame(n, Nil, cs)))
}
}
Хвосто-рекурсивная функция step
принимает преобразованный стек со "кадрами стека". «Фрейм стека» хранит имя узла ветви, который обрабатывается в данный момент, список дочерних узлов, которые уже были обработаны, и список оставшихся узлов, которые все еще должны быть обработаны позже. Это примерно соответствует фактическому стековому фрейму вашей рекурсивной функции mapLeaves
.
С этой структурой данных,
- возврат из рекурсивных вызовов соответствует деконструкции
Frame
объекта и либо возврату окончательного результата, либо, по крайней мере, уменьшению stack
на один кадр.
- рекурсивные вызовы соответствуют шагу, который добавляет
Frame
к stack
- базовый случай (при вызове
f
на листьях) не создает и не удаляет какие-либо кадры
Как только вы понимаете, как обычно невидимые стековые фреймы представляются явно, перевод становится простым и в основном механическим.
Пример: * * тысяча двадцать-пять
val example = BranchNode("x", List(
BranchNode("y", List(
LeafNode("a"),
LeafNode("b")
)),
BranchNode("z", List(
LeafNode("c"),
BranchNode("v", List(
LeafNode("d"),
LeafNode("e")
))
))
))
println(mapLeaves(example, { case LeafNode(n) => LeafNode(n.toUpperCase) }))
Вывод (с отступом):
BranchNode(x,List(
BranchNode(y,List(
LeafNode(A),
LeafNode(B)
)),
BranchNode(z, List(
LeafNode(C),
BranchNode(v,List(
LeafNode(D),
LeafNode(E)
))
))
))