Skip to content

Commit ebdad34

Browse files
author
Abhijit Sarkar
committed
Complete Multiway Trees
1 parent 8d6d635 commit ebdad34

File tree

14 files changed

+238
-6
lines changed

14 files changed

+238
-6
lines changed

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -138,15 +138,15 @@ P66 (***) Layout a binary tree (3).
138138

139139
P70B Omitted; we can only create well-formed trees.
140140

141-
P70C (*) Count the nodes of a multiway tree.
141+
[P70C](mtree/src/P70C.scala) (*) Count the nodes of a multiway tree.
142142

143-
P70 (**) Tree construction from a node string.
143+
[P70](mtree/src/P70.scala) (**) Tree construction from a node string.
144144

145-
P71 (*) Determine the internal path length of a tree.
145+
[P71](mtree/src/P71.scala) (*) Determine the internal path length of a tree.
146146

147-
P72 (*) Construct the postorder sequence of the tree nodes.
147+
[P72](mtree/src/P72.scala) (*) Construct the postorder sequence of the tree nodes.
148148

149-
P73 (**) Lisp-like tree representation.
149+
[P73](mtree/src/P73.scala) (**) Lisp-like tree representation.
150150

151151
## Graphs
152152

bintree/src/DList.scala renamed to mtree/src/DList.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package bintree
1+
package mtree
22

33
final case class DList[A](run: List[A] => List[A]):
44
// O(1)

mtree/src/MTree.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package mtree
2+
3+
final case class MTree[+T](value: T, children: Seq[MTree[T]] = Nil):
4+
override def toString: String =
5+
s"M(${value.toString} {${children.map(_.toString).mkString(",")}})"

mtree/src/P70.scala

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package mtree
2+
3+
// P70 (**) Tree construction from a node string.
4+
//
5+
// We suppose that the nodes of a multiway tree contain single characters.
6+
// In the depth-first order sequence of its nodes, a special character ^
7+
// has been inserted whenever, during the tree traversal, the move is a
8+
// backtrack to the previous level.
9+
//
10+
// By this rule, the tree in the figure opposite is represented as:
11+
//
12+
// afg^^c^bd^e^^^
13+
// Define the syntax of the string and write a function string2MTree to construct
14+
// an MTree from a String.  Make the function an implicit conversion from String.
15+
// Write the reverse function, and make it the toString method of MTree.
16+
//
17+
// scala> MTree('a', List(MTree('f', List(MTree('g'))), MTree('c'), MTree('b', List(MTree('d'), MTree('e'))))).toString
18+
// res0: String = afg^^c^bd^e^^^
19+
object P70:
20+
def string2MTree(s: String): MTree[Char] =
21+
/*
22+
First recursive dfs call collects the children,
23+
second recursive dfs call collects the siblings.
24+
25+
x=g, xs=^^c^bd^e^^^, ys=^c^bd^e^^^, zs=c^bd^e^^^
26+
x=e, xs=^^^, ys=^^, zs=^
27+
x=d, xs=^e^^^, ys=e^^^, zs=^
28+
x=b, xs=d^e^^^, ys=^, zs=
29+
x=c, xs=^bd^e^^^, ys=bd^e^^^, zs=
30+
x=f, xs=g^^c^bd^e^^^, ys=c^bd^e^^^, zs=
31+
x=a, xs=fg^^c^bd^e^^^, ys=, zs=
32+
*/
33+
def loop(s: String): (List[MTree[Char]], String) =
34+
s match
35+
case "" => (Nil, "")
36+
case s"^$xs" => (Nil, xs)
37+
case _ =>
38+
val (x, xs) = (s.head, s.tail)
39+
val (children, ys) = loop(xs)
40+
val (siblings, zs) = loop(ys)
41+
(MTree(x, children) :: siblings, zs)
42+
43+
loop(s)._1.head
44+
45+
def tree2String(t: MTree[Char]): String =
46+
def loop(acc: DList[Char], t: MTree[Char]): DList[Char] =
47+
val xs = t.children.foldLeft(DList.empty[Char])(loop)
48+
val ys = DList.singleton(t.value) ++ xs ++ DList.singleton('^')
49+
acc ++ ys
50+
51+
loop(DList.empty[Char], t).toList.mkString

mtree/src/P70C.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package mtree
2+
3+
// P70C (*) Count the nodes of a multiway tree.
4+
// Write a method nodeCount which counts the nodes of a given multiway tree.
5+
//
6+
// scala> MTree('a', List(MTree('f'))).nodeCount
7+
// res0: Int = 2
8+
object P70C:
9+
extension [A](t: MTree[A])
10+
def nodeCount: Int = 1 + t.children.foldLeft(0)((acc, c) => acc + c.nodeCount)

mtree/src/P71.scala

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package mtree
2+
3+
// P71 (*) Determine the internal path length of a tree.
4+
// We define the internal path length of a multiway tree as the
5+
// total sum of the path lengths from the root to all nodes of the tree.
6+
// By this definition, the tree in the figure of problem P70 has an
7+
// internal path length of 9.  Write a method internalPathLength to return that sum.
8+
//
9+
// scala> "afg^^c^bd^e^^^".internalPathLength
10+
// res0: Int = 9
11+
/*
12+
ANSWER: We observe that the path length is equal to the number of
13+
nodes in a path from the root to a leaf, with a node counted only
14+
once. So, path length of abd = 3, but abd + abe is 4, not 6.
15+
16+
The catch is to pass the _same_ accoumulated value to all the
17+
children of a node.
18+
*/
19+
object P71:
20+
private def loop[A](acc: Int, t: MTree[A]): Int =
21+
acc + t.children.map(loop(acc + 1, _)).sum
22+
23+
extension [A](t: MTree[A])
24+
def internalPathLength: Int =
25+
loop(0, t)

mtree/src/P72.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package mtree
2+
3+
// P72 (*) Construct the postorder sequence of the tree nodes.
4+
//
5+
// Write a method postorder which constructs the postorder sequence
6+
// of the nodes of a multiway tree.  The result should be a List.
7+
//
8+
// scala> "afg^^c^bd^e^^^".postorder
9+
// res0: List[Char] = List(g, f, c, d, e, b, a)
10+
object P72:
11+
private def loop[A](acc: List[A], t: MTree[A]): List[A] =
12+
t.children.foldRight(t.value :: acc)((tree, xs) => loop(xs, tree))
13+
14+
extension [A](t: MTree[A])
15+
def postorder: List[A] =
16+
loop(Nil, t)

mtree/src/P73.scala

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package mtree
2+
3+
// P73 (**) Lisp-like tree representation.
4+
// There is a particular notation for multiway trees in Lisp.
5+
// Lisp is a prominent functional programming language.
6+
// In Lisp almost everything is a list.
7+
//
8+
// Our example tree would be represented in Lisp as (a (f g) c (b d e)).
9+
// The following pictures give some more examples.
10+
//
11+
// Note that in the "lispy" notation a node with successors (children) in
12+
// the tree is always the first element in a list, followed by its children.
13+
// The "lispy" representation of a multiway tree is a sequence of atoms and
14+
// parentheses '(' and ')', with the atoms separated by spaces.
15+
// We can represent this syntax as a Scala String.
16+
// Write a method lispyTree which constructs a "lispy string" from an MTree.
17+
//
18+
// scala> MTree("a", List(MTree("b", List(MTree("c"))))).lispyTree
19+
// res0: String = (a (b c))
20+
//
21+
// As a second, even more interesting, exercise try to write a method that
22+
// takes a "lispy" string and turns it into a multiway tree.
23+
object P73:
24+
private def loop(s: String, i: Int): (MTree[Char], Int) =
25+
if s(i).isSpaceChar then loop(s, i + 1)
26+
else if s(i).isLetter then (MTree(s(i)), i + 1)
27+
else if s(i) == '(' then
28+
val x = s(i + 1)
29+
val xs = IndexedSeq.unfold(i + 2)(j =>
30+
Option.when(j < s.length() && s(j) != ')') {
31+
val x = loop(s, j)
32+
((x, x._2))
33+
}
34+
)
35+
(MTree(x, xs.map(_._1)), xs.last._2 + 1)
36+
else throw IllegalStateException(s"s=$s, s($i)=${s(i)}")
37+
38+
def lispyString2Tree(s: String): MTree[Char] =
39+
loop(s, 0)._1
40+
41+
extension [A](t: MTree[Char])
42+
def lispyTree: String =
43+
def loop(acc: DList[Char], t: MTree[Char]): DList[Char] =
44+
if t.children.isEmpty then
45+
acc
46+
++ DList.singleton(' ')
47+
++ DList.singleton(t.value)
48+
else
49+
val xs = t.children.foldLeft(DList.empty[Char])(loop)
50+
acc
51+
++ DList.singleton(' ')
52+
++ DList.singleton('(')
53+
++ DList.singleton(t.value)
54+
++ xs
55+
++ DList.singleton(')')
56+
57+
loop(DList.empty[Char], t).toList.tail.mkString

mtree/src/package.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package mtree
2+
3+
given Conversion[String, MTree[Char]] = s => P70.string2MTree(s)

mtree/test/src/P70CSpec.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package mtree
2+
3+
import org.scalatest.funspec.AnyFunSpec
4+
import org.scalatest.matchers.should.Matchers.shouldBe
5+
import P70C.nodeCount
6+
7+
class P70CSpec extends AnyFunSpec:
8+
it("count the nodes of a multiway tree"):
9+
MTree('a', List(MTree('f'))).nodeCount shouldBe 2

0 commit comments

Comments
 (0)