J'avais un programme impératif qui désérialise un arbre binaire à partir d'un tableau. C'est un algorithme BFS. Je me demandais comment faire cela dans Scala avec des concepts de programmation fonctionnelle.
class TreeNode { constructor(val){ this.val = val; this.left = this.right = null; } } function makeTree(arr){ let root = new TreeNode(arr[0]); let q = [root]; let index = 1; while(q.length){ let node = q.splice(0,1)[0]; if(arr[index] != null){ node.left = new TreeNode(arr[index]); q.push(node.left); } index++; if(arr[index] != null){ node.right = new TreeNode(arr[index]); q.push(node.right); } index++; } return root; }
Pour référence c'est la version impérative en Javascript. L'algorithme est décrit ici. https://support.leetcode.com/hc/en-us/articles/360011883654-What-does-1-null-2-3-mean-in-binary-tree-representation- a >
class TreeNode(_value: Int = 0, _left: TreeNode = null, _right: TreeNode = null) { val value: Int = _value val left: TreeNode = _left val right: TreeNode = _right } def createTree(list: Array[Int]): TreeNode = ???
4 Réponses :
Tout d'abord, vous pouvez utiliser une classe de cas pour simplifier votre classe d'arbre, et vous devez utiliser Option
au lieu de null
:
private def buildTree(row: Int, col: Int, map: Map[(Int, Int), Int]): Option[Tree] = { map.get((row, col)).map{value => Tree(value, buildTree(row + 1, col * 2, map), buildTree(row + 1, col * 2 + 1, map)) } }
Ensuite, le principal problème ici est que votre arbre est immuable, il doit être construit avec un parcours post-ordre en profondeur d'abord, et votre format de sérialisation nécessite un parcours d'ordre de niveau en largeur premier. Vous devez donc d'abord désérialiser en une structure de données qui peut ensuite être parcourue dans le premier ordre en profondeur. Ce qui suit utilise une carte de (ligne, colonne) à la valeur du nœud:
@scala.annotation.tailrec private def bfsTraverse( serialized: List[Option[Int]], queue: Queue[(Int, Int)], map: Map[(Int, Int), Int]): Map[(Int, Int), Int] = { val ((row, col), queueTail) = queue.dequeue if (serialized.isEmpty) { map } else if (serialized.head.isEmpty) { bfsTraverse(serialized.tail, queueTail, map) } else { val newQueue = queueTail.enqueueAll(List((row + 1, col * 2), (row + 1, col * 2 + 1))) val newMap = map + ((row, col) -> serialized.head.get) bfsTraverse(serialized.tail, newQueue, newMap) } }
Vous pouvez maintenant utiliser la sortie de cette fonction pour construire votre Tree code >:
case class Tree(value: Int, left: Option[Tree], right: Option[Tree])
Jolie approche
Cette solution est un peu verbeuse mais utilise certains concepts fonctionnels et définit minutieusement les structures de données.
processValues
exécute de manière récursive les tâches équivalentes à la fonction makeTree
que vous avez fournie. match
et case
remplace la vérification de la nullité ou de sous-types différents, et le compilateur peut rechercher une clause de correspondance non exhaustive. import scala.annotation.tailrec sealed trait TreeNode[T] sealed trait MutableTreeNode[T] object MutableTreeNode { final case class Empty[T]() extends MutableTreeNode[T] case class Branch[T](val value: T) extends MutableTreeNode[T] { protected var left: MutableTreeNode[T] = Empty() protected var right: MutableTreeNode[T] = Empty() def setLeft(newLeft: T): Branch[T] = { left = Branch(newLeft) left.asInstanceOf[Branch[T]] // shouldn't be necessary but compiler requires it } def setRight(newRight: T): Branch[T] = { right = Branch(newRight) right.asInstanceOf[Branch[T]] } override def toString: String = this.toImmutable().toString /* converts given node to immutable version */ private def toImmutable(node: MutableTreeNode[T]): TreeNode[T] = { node match { case Empty() => TreeNode.Empty() case b@Branch(value) => TreeNode.Branch(value, toImmutable(b.left), toImmutable(b.right)) } } def toImmutable():TreeNode[T] = toImmutable(this) } /** * Modifies nodes inside of queue */ @tailrec def processValues[T](values: Seq[Option[T]], queue: Seq[MutableTreeNode.Branch[T]]): Unit = { (queue, values) match { case (Nil, _) => () case (_, Nil) => () case (qHead :: qTail, Some(vLeft) :: Some(vRight) :: vTail) => processValues(vTail, qTail :+ qHead.setLeft(vLeft) :+ qHead.setRight(vRight)) case (qHead :: qTail, Some(vLeft) :: None :: vTail) => processValues(vTail, qTail :+ qHead.setLeft(vLeft)) case (qHead :: qTail, None :: Some(vRight) :: vTail) => processValues(vTail, qTail :+ qHead.setRight(vRight)) case (qHead :: qTail, None :: None :: vTail) => processValues(vTail, qTail) } } } object TreeNode { final case class Empty[T]() extends TreeNode[T] final case class Branch[T](value: T, left: TreeNode[T], right: TreeNode[T]) extends TreeNode[T] def deserialize[T](values: Seq[Option[T]]): TreeNode[T] = { values match { case Some(headVal) :: tail => val root: MutableTreeNode.Branch[T] = MutableTreeNode.Branch(headVal) MutableTreeNode.processValues(tail, Seq(root)) root.toImmutable() case Nil => Empty() case _ => throw new RuntimeException("Invalid argument values") } } } object TreeNodeTest extends App { val input = Seq(Some(5), Some(4), Some(7), None, None, Some(2), None) val treeNode:TreeNode[Int] = TreeNode.deserialize(input) println(treeNode) }
Comme cela a été noté, Scala évite null
autant que possible, préférant Option
pour indiquer l'absence de valeur.
Les variables mutables sont également évitées, ce qui la rend beaucoup plus facile de construire un arbre B en profondeur plutôt qu'en largeur en premier.
Donc, tout ce dont vous avez vraiment besoin est une sérialisation en largeur en premier facile à utiliser --to -> traducteur profondeur-première-sérialisation.
Je l'ai fait en deux étapes.
Tree.fromBFS(Vector(Some('A'),None,Some('B'),Some('C'))).get //res0: Tree[Char] = Tree(A,None,Some(Tree(B,Some(Tree(C,None,None)),None)))
Maintenant paramétrons l'arborescence pour que nous puissions construire Int
arbres, Float
arbres, String
arbres, etc.
Nous allons également rendre le constructeur privé code> pour que la création de nœuds se fasse uniquement via des méthodes d'usine.
object Tree { private def BFS2full[A]( . . . //as above private def toDFS[A]( . . . //as above def fromDFS[A](dfs :IterableOnce[Option[A]]) :Option[Tree[A]] = { val itr = dfs.iterator def loop(): Option[Tree[A]] = Option.when(itr.hasNext)(itr.next()) .flatten .map(new Tree(_,loop(),loop())) loop() } def fromBFS[A](bfs:IndexedSeq[Option[A]]) :Option[Tree[A]] = fromDFS(toDFS(BFS2full(bfs))) }
Il ne reste plus qu'à fournir les méthodes d'usine.
case class Tree[A] private (value : A ,left : Option[Tree[A]] ,right : Option[Tree[A]])
test :
//from Breadth-First-Serialization to full tree representation def BFS2full[A](bfs:IndexedSeq[Option[A]]) :List[List[Option[A]]] = { val bfsLen = bfs.length if (bfs.isEmpty) Nil else List(bfs.head) :: List.unfold((List(bfs.head),1)){case (pr,idx) => Option.when(bfsLen > idx){ val ns = pr.foldLeft((List.empty[Option[A]],idx)){ case ((acc,x), None) => (acc ++ List(None,None), x) case ((acc,x), _) => (acc ++ List(bfs.lift(x).flatten ,bfs.lift(x+1).flatten), x+2) } (ns._1, ns) } } } //from full tree representation to Depth-First-Serialization def toDFS[A](lloa :List[List[Option[A]]] ,lvl :Int = 0) :List[Option[A]] = lloa match { case Nil => List(None, None) case List(None) :: Nil => List(None) case List( oa ) :: tl => oa :: toDFS(tl, lvl) case row :: tl => row.drop(lvl*2) match { case List(None,None,_*) => List(None, None) case List(None, ob ,_*) => None :: (ob::toDFS(tl,2*lvl + 1)) case List( oa ,None,_*) => (oa::toDFS(tl,2*lvl)) ++ List(None) case List( oa , ob ,_*) => (oa :: toDFS(tl, 2*lvl)) ++ (ob :: toDFS(tl,2*lvl + 1)) } }
Voici une solution de base. Il n'utilise pas de valeurs paresseuses ou de structures de données autres que Liste
, Option
et Soit
. Vous trouverez peut-être plus facile à comprendre à cause de cela ou plus difficile à cause de la verbosité.
J'ai défini Tree
comme ceci, juste pour rendre les choses plus faciles.
def makeTree[T](list: List[Option[T]]): Tree[T] = { def helper(f: RecFun[T], l: List[Option[T]]): Tree[T] = f(l) match { case (_, Right(tree)) => tree case (next, Left(f)) => helper(f, next) } list match { case Some(x) :: tail => helper(createRec(x), tail) case _ => Empty } } def createRec[T](data: T): RecFun[T] = { case None :: Nil | Nil => (Nil, Right(Node(data, Empty, Empty))) case Some(l) :: Nil => (Nil, Right(Node(data, Node(l, Empty, Empty), Empty))) case lo :: ro :: rest => (rest, (lo, ro) match { case (Some(l), Some(r)) => Left(waitForChildren(data, createRec(l), createRec(r))) case (Some(l), None) => Left(waitForChild(Node(data, _, Empty), createRec(l))) case (None, Some(r)) => Left(waitForChild(Node(data, Empty, _), createRec(r))) case (None, None) => Right(Node(data, Empty, Empty)) }) } def waitForChildren[T](data: T, leftF: RecFun[T], rightF: RecFun[T]): RecFun[T] = input => { val (next, res) = leftF(input) res match { case Right(tree) => (next, Left(waitForChild(Node(data, tree, _), rightF))) case Left(leftF2) => { val (next2, res2) = rightF(next) (next2, Left(res2 match { case Right(tree) => waitForChild(Node(data, _, tree), leftF2) case Left(rightF2) => waitForChildren(data, leftF2, rightF2) })) } } } def waitForChild[T](ctor: Tree[T] => Node[T], f: RecFun[T]): RecFun[T] = input => { val (next, res) = f(input) (next, res match { case Right(tree) => Right(ctor(tree)) case Left(recFun) => Left(waitForChild(ctor, recFun)) }) }
est-ce que cela aide? stackoverflow.com/questions/41347337
Est-ce que cela répond à votre question? Comment implémenter la première recherche en largeur dans Scala avec FP a>
Je ne suis pas d'accord. Ce n'est pas un double de la question liée suggérée et ne doit pas être fermé. La question liée concerne la recherche dans un arbre existant. Cette question concerne la construction d’un arbre à partir d’une description de données de largeur d’abord. Pas la męme chose.
La question est de construire un arbre à partir d'un tableau plutôt que d'itérer ou de rechercher l'arbre.
voici une réponse dans Haskell (et Prolog). :) en fonction de votre représentation, la construction de l'arbre à partir de sa séquence bfs peut être un no-op. par exemple. lorsque vous utilisez une représentation basée sur un tableau.
@user Je ne parle pas vraiment pas de lecture de Scala malheureusement. : (J'ai truqué une réponse une ou deux fois je pense, mais c'est tout.) Mais en réalité, il s'agit simplement de remplir la structure. Ce n'est encombrant que si votre langue n'a pas de mutation, comme Haskell. Scala a-t-il une mutation? Pouvez-vous définir un «champ» de votre «enregistrement» sur une nouvelle valeur? alors ce n'est pas un problème.
@user intéressant, merci. en le lisant superficiellement, cet extrait de code semble suivre le code Haskell (c'était la réécriture de @ dfeuer BTW). Peut-être que Scala ne fait pas de modèles paresseux comme le fait Haskell (ces
~
s)? en générallet ~ (a: ~ (b: c)) = t in ... a ... b ... c ...
est identique à... ( head t) ... (head (tail t)) .... (tail (tail t)) ....
. peut-être que le réécrire dans cette veine vous aidera?@WillNess Vous aviez raison, cela a à voir avec ces modèles irréfutables. Il existe des des solutions de contournement , mais mec, ça a l'air moche. Merci pour les idées quand même