I want to define a generalized tail recursive tree traversal that works for all kinds of multi-way trees. This works fine with pre-order and level-order, but I'm having trouble to implement post order traversals. Here is the multi-way tree I am working with:
Desired order: EKFBCGHIJDA
As long as I don't care about tail recursion post order traversal is easy:
const postOrder = ([x, xs]) => {
xs.forEach(postOrder);
console.log(`${x}`);
};
const Node = (x, ...xs) => ([x, xs]);
const tree = Node("a",
Node("b",
Node("e"),
Node("f",
Node("k"))),
Node("c"),
Node("d",
Node("g"),
Node("h"),
Node("i"),
Node("j")));
postOrder(tree);
The tail recursive approach, on the other hand, is quite cumbersome:
const postOrder = (p, q) => node => {
const rec = ({[p]: x, [q]: forest}, stack) => {
if (forest.length > 0) {
const [node, ...forest_] = forest;
stack.unshift(...forest_, Node(x));
return rec(node, stack);
}
else {
console.log(x);
if (stack.length > 0) {
const node = stack.shift();
return rec(node, stack);
}
else return null;
}
};
return rec(node, []);
};
const Node = (x, ...xs) => ([x, xs]);
const tree = Node("a",
Node("b",
Node("e"),
Node("f",
Node("k"))),
Node("c"),
Node("d",
Node("g"),
Node("h"),
Node("i"),
Node("j")));
postOrder(0, 1) (tree);
In particular, I'd like to avoid creating new nodes so that I can traverse arbitrary trees without having to know anything about their constructors. Is there a way to do this and still remain tail recursive?
stack-safe
My first answer solves this problem by writing our own functional iterator protocol. Admittedly, I was eager to share this approach as it's something I've explored in the past. Writing your own data structures is really fun and it can yield creative solutions to your problem - and you'd be bored if I gave out the easy answers first, wouldn't you?
Full solution included below for other readers...
mutual recursion
Somehow it's your questions that allow me to canvas my most inspired works. Back in the headspace of tree traversals, I came up with this sort of pseudo-applicative sum type
Now
andLater
.Later
does not have a proper tail call but I thought the solution was too neat not to share itMutual recursion demonstration
We start by writing
Node.value
andNode.children
which get the two values from your NodeNext, we create a generic
Iterator
type. This one imitates the native iterable behavior, only our iterators are persistent (immutable)Lastly, we can implement
PostorderIterator
And we can see it working with your
tree
hereProgram demonstration
The variadic
children
field makes the algorithm a little more complicated compare to a Node type that only hasleft
andright
fieldsThe simplified implementation of these iterators makes them a bit easier to compare. Writing support for variadic children in the other iterators is left as an exercise to the reader
And a very special
LevelorderIterator
, just because I think you can handle itOver-engineered? Guilty. You can swap out the interfaces above for nothing but JavaScript primitives. Here we trade the lazy stream for an eager array of values