After lists, trees are the single most important data structure in computer science. Just about everything you do in your programming career will be related to trees. For example, JSON objects, XML, and HTML DOM are tree structures. When object-oriented JavaScript developers enter the realm of functional programming, they quickly learn how to use a map and reduce it to replace traditional for loops. Trees also have map and reduce methods.
In this shot, we are going to use a reduced method on a Binary Tree in order to reverse it in a functional way.
If we have a tree that looks like this:
We want to define a function, Reverse()
, that will return a tree that looks like this:
We are going to start from an ES6 Tree structure
:
class Tree {}// node classclass Node extends Tree {constructor(left, v, right) {super()this.v = v;this.left = left;this.right = right;}}// leaf classclass Leaf extends Tree {constructor(v) {super()this.v = v;}}
Now, we can represent the tree with objects like in Fig.1
:
new Node(new Node(new Leaf(7), 3, new Leaf(3)), 6, new Node(new Leaf(8), 4, new Leaf(1)));
The first thing we are going to define is a very simple matchWith method on the tree structure.
class Tree {}class Node extends Tree {constructor(left, v, right) {super()this.v = v;this.left = left;this.right = right;}matchWith( pattern) {return pattern.Node(this.left, this.v, this.right);}}class Leaf extends Tree {constructor(v) {super()this.v = v;}matchWith(pattern) {return pattern.Leaf(this.v);}}
This takes the pattern
object as an argument:
pattern =({
Leaf: v => {} ,
Node: (left, v, right) => { }
})
This function allows us to distinguish between Node and Leaf and is called pattern matching.
And that’s it !!! we are done. We can now define any type of recursive method on this tree. Now, we’re going to define the Reverse()
function recursively.
Take a look at the following Fiddle:
class Tree {}class Node extends Tree {constructor(left, v, right) {super()this.v = v;this.left = left;this.right = right;}matchWith(pattern) {return pattern.Node(this.left, this.v, this.right);}toString() {return `Node(${this.left.toString( )},${ (this.v)},${this.right.toString( )})`;}}class Leaf extends Tree {constructor(v) {super()this.v = v;}matchWith(pattern) {return pattern.Leaf(this.v);}toString() {return (`Leaf(${ this.v})`);}}Tree.prototype.reverse = function ( ) {return this.matchWith({Leaf: v => new Leaf(v) ,Node: (left, v, right) => new Node(right.reverse(),v,left.reverse())});}var treeInstance =new Node(new Node(new Leaf(7), 3, new Leaf(3)), 6, new Node(new Leaf(8), 4, new Leaf(1)));console.log(treeInstance.toString()) ;var reverse = treeInstance.reverse();console.log(reverse.toString())
All the magic happens here:
Tree.prototype.reverse = function ( ) {return this.matchWith({Leaf: v => new Leaf(v) ,Node: (left, v, right) => {return new Node(right.reverse(),v,left.reverse())}});}
Leaf: v => new Leaf(v)
new Node( right.reverse(), v, left.reverse())
This method of recursive definition is called structural induction.
Let’s do another example so that you can recognize the pattern.
Let’s say we want to count the nodes of the tree. This is how we will do it:
Tree.prototype.countNodes = function() {return this.matchWith({Leaf: v => 1,Node: (left, v, right) => left.countNodes()+ 1+ right.countNodes()});}