diff --git a/README.md b/README.md index d2be407..fb419d1 100644 --- a/README.md +++ b/README.md @@ -138,15 +138,15 @@ P66 (***) Layout a binary tree (3). P70B Omitted; we can only create well-formed trees. -P70C (*) Count the nodes of a multiway tree. +[P70C](mtree/src/P70C.scala) (*) Count the nodes of a multiway tree. -P70 (**) Tree construction from a node string. +[P70](mtree/src/P70.scala) (**) Tree construction from a node string. -P71 (*) Determine the internal path length of a tree. +[P71](mtree/src/P71.scala) (*) Determine the internal path length of a tree. -P72 (*) Construct the postorder sequence of the tree nodes. +[P72](mtree/src/P72.scala) (*) Construct the postorder sequence of the tree nodes. -P73 (**) Lisp-like tree representation. +[P73](mtree/src/P73.scala) (**) Lisp-like tree representation. ## Graphs diff --git a/bintree/src/DList.scala b/mtree/src/DList.scala similarity index 96% rename from bintree/src/DList.scala rename to mtree/src/DList.scala index 81ab010..0755172 100644 --- a/bintree/src/DList.scala +++ b/mtree/src/DList.scala @@ -1,4 +1,4 @@ -package bintree +package mtree final case class DList[A](run: List[A] => List[A]): // O(1) diff --git a/mtree/src/MTree.scala b/mtree/src/MTree.scala new file mode 100644 index 0000000..81641b0 --- /dev/null +++ b/mtree/src/MTree.scala @@ -0,0 +1,5 @@ +package mtree + +final case class MTree[+T](value: T, children: Seq[MTree[T]] = Nil): + override def toString: String = + s"M(${value.toString} {${children.map(_.toString).mkString(",")}})" diff --git a/mtree/src/P70.scala b/mtree/src/P70.scala new file mode 100644 index 0000000..c7ea96c --- /dev/null +++ b/mtree/src/P70.scala @@ -0,0 +1,51 @@ +package mtree + +// P70 (**) Tree construction from a node string. +// +// We suppose that the nodes of a multiway tree contain single characters. +// In the depth-first order sequence of its nodes, a special character ^ +// has been inserted whenever, during the tree traversal, the move is a +// backtrack to the previous level. +// +// By this rule, the tree in the figure opposite is represented as: +// +// afg^^c^bd^e^^^ +// Define the syntax of the string and write a function string2MTree to construct +// an MTree from a String.  Make the function an implicit conversion from String. +// Write the reverse function, and make it the toString method of MTree. +// +// scala> MTree('a', List(MTree('f', List(MTree('g'))), MTree('c'), MTree('b', List(MTree('d'), MTree('e'))))).toString +// res0: String = afg^^c^bd^e^^^ +object P70: + def string2MTree(s: String): MTree[Char] = + /* + First recursive dfs call collects the children, + second recursive dfs call collects the siblings. + + x=g, xs=^^c^bd^e^^^, ys=^c^bd^e^^^, zs=c^bd^e^^^ + x=e, xs=^^^, ys=^^, zs=^ + x=d, xs=^e^^^, ys=e^^^, zs=^ + x=b, xs=d^e^^^, ys=^, zs= + x=c, xs=^bd^e^^^, ys=bd^e^^^, zs= + x=f, xs=g^^c^bd^e^^^, ys=c^bd^e^^^, zs= + x=a, xs=fg^^c^bd^e^^^, ys=, zs= + */ + def loop(s: String): (List[MTree[Char]], String) = + s match + case "" => (Nil, "") + case s"^$xs" => (Nil, xs) + case _ => + val (x, xs) = (s.head, s.tail) + val (children, ys) = loop(xs) + val (siblings, zs) = loop(ys) + (MTree(x, children) :: siblings, zs) + + loop(s)._1.head + + def tree2String(t: MTree[Char]): String = + def loop(acc: DList[Char], t: MTree[Char]): DList[Char] = + val xs = t.children.foldLeft(DList.empty[Char])(loop) + val ys = DList.singleton(t.value) ++ xs ++ DList.singleton('^') + acc ++ ys + + loop(DList.empty[Char], t).toList.mkString diff --git a/mtree/src/P70C.scala b/mtree/src/P70C.scala new file mode 100644 index 0000000..2712ac1 --- /dev/null +++ b/mtree/src/P70C.scala @@ -0,0 +1,10 @@ +package mtree + +// P70C (*) Count the nodes of a multiway tree. +// Write a method nodeCount which counts the nodes of a given multiway tree. +// +// scala> MTree('a', List(MTree('f'))).nodeCount +// res0: Int = 2 +object P70C: + extension [A](t: MTree[A]) + def nodeCount: Int = 1 + t.children.foldLeft(0)((acc, c) => acc + c.nodeCount) diff --git a/mtree/src/P71.scala b/mtree/src/P71.scala new file mode 100644 index 0000000..0aa8429 --- /dev/null +++ b/mtree/src/P71.scala @@ -0,0 +1,25 @@ +package mtree + +// P71 (*) Determine the internal path length of a tree. +// We define the internal path length of a multiway tree as the +// total sum of the path lengths from the root to all nodes of the tree. +// By this definition, the tree in the figure of problem P70 has an +// internal path length of 9.  Write a method internalPathLength to return that sum. +// +// scala> "afg^^c^bd^e^^^".internalPathLength +// res0: Int = 9 +/* +ANSWER: We observe that the path length is equal to the number of +nodes in a path from the root to a leaf, with a node counted only +once. So, path length of abd = 3, but abd + abe is 4, not 6. + +The catch is to pass the _same_ accoumulated value to all the +children of a node. + */ +object P71: + private def loop[A](acc: Int, t: MTree[A]): Int = + acc + t.children.map(loop(acc + 1, _)).sum + + extension [A](t: MTree[A]) + def internalPathLength: Int = + loop(0, t) diff --git a/mtree/src/P72.scala b/mtree/src/P72.scala new file mode 100644 index 0000000..0d73450 --- /dev/null +++ b/mtree/src/P72.scala @@ -0,0 +1,16 @@ +package mtree + +// P72 (*) Construct the postorder sequence of the tree nodes. +// +// Write a method postorder which constructs the postorder sequence +// of the nodes of a multiway tree.  The result should be a List. +// +// scala> "afg^^c^bd^e^^^".postorder +// res0: List[Char] = List(g, f, c, d, e, b, a) +object P72: + private def loop[A](acc: List[A], t: MTree[A]): List[A] = + t.children.foldRight(t.value :: acc)((tree, xs) => loop(xs, tree)) + + extension [A](t: MTree[A]) + def postorder: List[A] = + loop(Nil, t) diff --git a/mtree/src/P73.scala b/mtree/src/P73.scala new file mode 100644 index 0000000..beb87dd --- /dev/null +++ b/mtree/src/P73.scala @@ -0,0 +1,57 @@ +package mtree + +// P73 (**) Lisp-like tree representation. +// There is a particular notation for multiway trees in Lisp. +// Lisp is a prominent functional programming language. +// In Lisp almost everything is a list. +// +// Our example tree would be represented in Lisp as (a (f g) c (b d e)). +// The following pictures give some more examples. +// +// Note that in the "lispy" notation a node with successors (children) in +// the tree is always the first element in a list, followed by its children. +// The "lispy" representation of a multiway tree is a sequence of atoms and +// parentheses '(' and ')', with the atoms separated by spaces. +// We can represent this syntax as a Scala String. +// Write a method lispyTree which constructs a "lispy string" from an MTree. +// +// scala> MTree("a", List(MTree("b", List(MTree("c"))))).lispyTree +// res0: String = (a (b c)) +// +// As a second, even more interesting, exercise try to write a method that +// takes a "lispy" string and turns it into a multiway tree. +object P73: + private def loop(s: String, i: Int): (MTree[Char], Int) = + if i >= s.length() then throw StringIndexOutOfBoundsException(s"String index out of range: $i") + else if s(i).isLetter then (MTree(s(i)), i + 1) + else if s(i) == '(' then + val x = s(i + 1) + val xs = IndexedSeq.unfold(i + 2)(j => + Option.when(j < s.length() && s(j) != ')') { + val x = loop(s, j) + ((x, x._2)) + } + ) + (MTree(x, xs.map(_._1)), xs.last._2 + 1) + else loop(s, i + 1) + + def lispyString2Tree(s: String): MTree[Char] = + loop(s, 0)._1 + + extension [A](t: MTree[Char]) + def lispyTree: String = + def loop(acc: DList[Char], t: MTree[Char]): DList[Char] = + if t.children.isEmpty then + acc + ++ DList.singleton(' ') + ++ DList.singleton(t.value) + else + val xs = t.children.foldLeft(DList.empty[Char])(loop) + acc + ++ DList.singleton(' ') + ++ DList.singleton('(') + ++ DList.singleton(t.value) + ++ xs + ++ DList.singleton(')') + + loop(DList.empty[Char], t).toList.tail.mkString diff --git a/mtree/src/package.scala b/mtree/src/package.scala new file mode 100644 index 0000000..3abc2ad --- /dev/null +++ b/mtree/src/package.scala @@ -0,0 +1,3 @@ +package mtree + +given Conversion[String, MTree[Char]] = s => P70.string2MTree(s) diff --git a/mtree/test/src/P70CSpec.scala b/mtree/test/src/P70CSpec.scala new file mode 100644 index 0000000..9321e05 --- /dev/null +++ b/mtree/test/src/P70CSpec.scala @@ -0,0 +1,9 @@ +package mtree + +import org.scalatest.funspec.AnyFunSpec +import org.scalatest.matchers.should.Matchers.shouldBe +import P70C.nodeCount + +class P70CSpec extends AnyFunSpec: + it("count the nodes of a multiway tree"): + MTree('a', List(MTree('f'))).nodeCount shouldBe 2 diff --git a/mtree/test/src/P70Spec.scala b/mtree/test/src/P70Spec.scala new file mode 100644 index 0000000..ed76085 --- /dev/null +++ b/mtree/test/src/P70Spec.scala @@ -0,0 +1,15 @@ +package mtree + +import org.scalatest.funspec.AnyFunSpec +import org.scalatest.matchers.should.Matchers.shouldBe + +class P70Spec extends AnyFunSpec: + val tree = MTree( + 'a', + List(MTree('f', List(MTree('g'))), MTree('c'), MTree('b', List(MTree('d'), MTree('e')))) + ) + it("tree construction from a node string"): + P70.string2MTree("afg^^c^bd^e^^^") shouldBe tree + + it("tree to string"): + P70.tree2String(tree) shouldBe "afg^^c^bd^e^^^" diff --git a/mtree/test/src/P71Spec.scala b/mtree/test/src/P71Spec.scala new file mode 100644 index 0000000..5c7458a --- /dev/null +++ b/mtree/test/src/P71Spec.scala @@ -0,0 +1,10 @@ +package mtree + +import org.scalatest.funspec.AnyFunSpec +import org.scalatest.matchers.should.Matchers.shouldBe +import mtree.P71.internalPathLength +import scala.language.implicitConversions + +class P71Spec extends AnyFunSpec: + it("internal path length of a tree"): + "afg^^c^bd^e^^^".internalPathLength shouldBe 9 diff --git a/mtree/test/src/P72Spec.scala b/mtree/test/src/P72Spec.scala new file mode 100644 index 0000000..9e22110 --- /dev/null +++ b/mtree/test/src/P72Spec.scala @@ -0,0 +1,10 @@ +package mtree + +import org.scalatest.funspec.AnyFunSpec +import org.scalatest.matchers.should.Matchers.shouldBe +import mtree.P72.postorder +import scala.language.implicitConversions + +class P72Spec extends AnyFunSpec: + it("postorder sequence of the tree nodes"): + "afg^^c^bd^e^^^".postorder shouldBe List('g', 'f', 'c', 'd', 'e', 'b', 'a') diff --git a/mtree/test/src/P73Spec.scala b/mtree/test/src/P73Spec.scala new file mode 100644 index 0000000..450ef58 --- /dev/null +++ b/mtree/test/src/P73Spec.scala @@ -0,0 +1,21 @@ +package mtree + +import org.scalatest.funspec.AnyFunSpec +import org.scalatest.matchers.should.Matchers.shouldBe +import mtree.P73.lispyTree +import scala.language.implicitConversions + +class P73Spec extends AnyFunSpec: + it("lisp-like tree representation"): + val data = List( + ("a^", "a"), + ("ab^^", "(a b)"), + ("abc^^^", "(a (b c))"), + ("bd^e^^", "(b d e)"), + ("afg^^c^bd^e^^^", "(a (f g) c (b d e))") + ) + + data.foreach { (s, expected) => + s.lispyTree shouldBe expected + P73.lispyString2Tree(expected) shouldBe P70.string2MTree(s) + }