Generators in TypeScript
5 August 2019
I was working on a problem last week that required a doubly-linked list in TypeScript. I also got to work with generators for the first time.
Let’s begin by creating our NodeDef
object. This is the object that represents the nodes of our linked list. You’ll notice that it has both append
and prepend
functions, depending on where we are adding to our linked list.
class NodeDef {
index: number;
nextNode: NodeDef | undefined = undefined;
previousNode: NodeDef | undefined = undefined;
constructor(index: number) {
this.index = index;
}
append(node: NodeDef): void {
if (this.nextNode) {
let nextNode = this.nextNode;
node.nextNode = nextNode;
nextNode.previousNode = node;
}
this.nextNode = node;
node.previousNode = this;
}
prepend(node: NodeDef): void {
if (this.previousNode) {
let previousNode = this.previousNode;
node.previousNode = previousNode;
previousNode.nextNode = node;
}
this.previousNode = node;
node.nextNode = this;
}
toString(): string {
return `${this.index}`;
}
}
Now, let’s create a class that holds a generator. It will return an iterable of NodeDef
instances. We also need to be able to insert the node in its correct place. Specifically, the NodeDef
will be inserted in index
order.
class MyGenerator {
firstNode: NodeDef | undefined = undefined;
key: string;
get path(): NodeDef[] {
let arr: NodeDef[] = [];
for (let nextNode of this.getNextNode()) {
if (nextNode) {
arr.push(nextNode);
}
}
return arr;
}
constructor(key: string) {
this.key = key;
}
addNode(newNode: NodeDef): void {
let done: boolean = false;
// If the first node is not defined, then this node
// is the first to be added to the subject.
if (this.firstNode === undefined) {
this.firstNode = newNode;
done = true;
}
// If this new node has a lower index than the first node,
// then this node is the new first node.
else if (newNode.index < this.firstNode.index) {
this.firstNode.prepend(newNode);
this.firstNode = newNode;
done = true;
}
let currentNode: NodeDef = this.firstNode;
while (!done) {
// If the new node has a lower index than the current node,
// then this node should be prepended to the current node.
if (newNode.index < currentNode.index) {
currentNode.prepend(newNode);
done = true;
}
// If the current node does not have a next node,
// then append the new node to the current node.
else if (currentNode.nextNode === undefined) {
currentNode.append(newNode);
done = true;
}
// Otherwise, advance to the next node.
else {
currentNode = currentNode.nextNode;
done = false;
}
}
}
/**
* Generator function, returning NodeDef instances as long as
* there is still a next NodeDef in the linked list.
*/
*getNextNode(): Iterable<NodeDef | undefined> {
let nextNode = this.firstNode;
while (nextNode !== undefined) {
yield nextNode;
nextNode = nextNode.nextNode;
}
}
toString(): string {
return `Subject ${this.key}: ${this.path.join(", ")}`;
}
}
Finally, let’s create a generator and check out the output.
let generator = new MyGenerator("ABC");
for (let i = 0; i < 10; i++) {
let rand = Math.floor(Math.random() * 1e6);
let node = new NodeDef(rand);
generator.addNode(node);
}
console.log(generator.toString());
The console output will look like the following.
Subject ABC: 49454, 122178, 186983, 307018, 456169, 558372, 632665, 838801, 847234, 914944
You can find this code in the TypeScript Playground.