Я хочу сделать следующий рекурсивный метод хвост-рекурсивным, но я не уверен, возможно ли это вообще, поскольку метод вызывает себя внутри flatMap.

@tailrec
def loop(nodes: List[Node], myNodes: List[MyNode]): List[MyNode] = nodes match {
  case Nil => myNodes
  case _ =>
    nodes.flatMap { n =>
      val myNode = MyNode(data = n.data)
      loop(n.getChildNodes().toList, myNode :: myNodes)
    }
}

loop(nodes, List())

К сожалению, я получаю следующую ошибку:

could not optimize @tailrec annotated method loop: it contains a recursive call not in tail position
[error]           case _ =>
[error]                  ^
[error] one error found
0
John Doe 28 Май 2017 в 23:45

2 ответа

Лучший ответ

Как сказал @SteffenSchmitz, вы не можете сделать рекурсивный вызов внутри flatMap. Действительно, в случае, когда в списке есть по крайней мере два Node s, ваш метод будет иметь по крайней мере два вызова к себе, поэтому он не может быть tailrec. Тем не менее, вы можете сделать то же самое, что ваша логика делает здесь с хвостовой рекурсией.

То, что делает ваш код, копирует данные в Node s в nodes, которые, похоже, представляют собой древовидную структуру в List[MyNode].

В конце вы получите список всех предков для каждого из листьев исходного дерева.

Например, если ваше дерево

  A
 / \
B   C
   / \
  D   E

Вы получите List(B, A, D, C, A, E, C, A) (здесь я предполагаю, что data является Char, для простоты). Первые B, A являются предками для B, следующие D, C, A являются предками для D, а последние - для E.

Вы можете сделать то же самое со следующим tailrec:

@tailrec
def tailrecLoop(nodesWithAncestors: List[(Node, List[MyNode])], acc: List[MyNode]): List[MyNode] = {
  nodesWithAncestors.headOption match {
    case None => acc
    case Some((node, ancestors)) if node.getChildNodes().toList.isEmpty =>
      loop(nodes.tail, acc ::: myNode :: ancestors)
    case Some((node, ancestors)) =>
      val myNode = MyNode(data = node.data)
      val childrenWithAncestors = 
        node.getChildNodes().toList.map(_ -> (myNode :: ancestors))
      loop(childrenWithAncestors ++ nodes.tail, acc)
  }
}

def loop(nodes: List[Node]) = tailrecLoop(nodes.map(_ -> Nil), Nil)

Ключевым моментом здесь является помещение необработанных узлов (nodesWithAncestors) в очередь для последующей обработки посредством дополнительных вызовов.

4
Cyrille Corpet 29 Май 2017 в 14:20

Невозможно сделать вызов внутри хвостовой рекурсии плоской карты. Последний вызов в вашей функции должен быть loop ().

1
Steffen Schmitz 28 Май 2017 в 20:48