Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Recursive Generators in JavaScript

I am trying to write a recursive generator for an in order traversal.

class Tree {   *inOrderTraversal() {     function* helper(node) {       if (node.left !== null) {         // this line is executed, but helper is not being called         helper(node.left);        }       yield node.value;       if (node.right !== null) {         helper(node.right);       }     }      for (let i of helper(this.root)) {       yield i;     }   }   // other methods omitted } 

And I am calling the generator like so:

const tree = new Tree(); tree.add(2); tree.add(1); tree.add(3);  for (let i of tree.inOrderTraversal()) {     console.log(i); // only prints 2 } 

Why is the generator only yielding 2? Why is it at least not yielding 1 before 2?

How can I fix this?

If it helps, I am transpiling the code using babel.

babel --optional runtime test.js | node

like image 544
robbmj Avatar asked Sep 25 '15 19:09

robbmj


People also ask

Can generators be recursive?

Yes you can have recursive generators. However, they suffer from the same recursion depth limit as other recursive functions.

What is recursive method in JavaScript?

Recursion is a process of calling itself. A function that calls itself is called a recursive function. The syntax for recursive function is: function recurse() { // function code recurse(); // function code } recurse();

Are there generators in JavaScript?

In ECMAScript 2015, generators were introduced to the JavaScript language. A generator is a process that can be paused and resumed and can yield multiple values. A generator in JavaScript consists of a generator function, which returns an iterable Generator object.


2 Answers

The problem was not with the recursion. Your function did call itself recursively, it just didn't yield values outside. When you call helper(), you get an iterator as a return value, but you wanted the iterated values of that iterator to be yielded. If you want to yield recursively, you need to yield *. Try like this:

  * inOrderTraversal() {     function* helper(node) {       if (node.left !== null) {         // this line is executed, but helper is not being called         yield * helper(node.left);        }       yield node.value;       if (node.right !== null) {         yield * helper(node.right);       }     }      for (let i of helper(this.root)) {       yield i;     }   } 

And while you're at it, you can replace the for loop with:

yield * helper(this.root) 
like image 145
Amit Avatar answered Oct 06 '22 06:10

Amit


helper(node.left); does call the function and create the generator, but the generator function body is never executed because the generator is never advanced. To forward all of its values to the current generator, you can use the yield* keyword, which works just like the

for (let i of helper(this.root))     yield i; 

you've used in your inOrderTraversal method. And indeed, that should've been a yield* just as well - or even better, there is no reason to make inOrderTraversal a generator function when it can just be a normal method that returns a generator:

class Tree {   inOrderTraversal() {     function* helper(node) {       if (node.left !== null)         yield* helper(node.left);       yield node.value;       if (node.right !== null)         yield* helper(node.right);     }      return helper(this.root);   }   … // other methods } 
like image 27
Bergi Avatar answered Oct 06 '22 06:10

Bergi