Linked list implementation in Deno, part 1
June 07, 2020
Since Deno has reached 1.0, I thought it might be fun to experiment a little with it, while also creating a linked list implementation in Typescript.
The linked list is often one of the first data structure developers learn because it can serve as the basis for other data structures, like stacks, queues, trees, tries, and graphs.
The concept is simple. You have a node with a value and a pointer to the next (and sometimes previous) node in the list. When a node also points at a previous node in the list, the list is called a doubly linked list.
NODE FOO NODE BAR NODE BAZ
------------------ ------------------ ------------------
| value: "foo" | | value: "bar" | | value: "baz" |
| next: BAR | ----> | next: BAZ | ----> | next: null |
| previous: null | | previous: FOO | | previous: BAR |
------------------ ------------------ ------------------
Linked list methods, part 1
In part 1 of our linked list series, we’ll create the following public methods:
- LinkedList.prototype.append
- Add a node to the end of the list.
- LinkedList.prototype.count
- Return the number of nodes in the list.
- LinkedList.prototype.head
- Return the first node in the list.
- LinkedList.prototype.isEmpty
- Return
true
if the list has no nodes.false
if it does. - LinkedList.prototype.tail
- Return the last node in the list.
Project Setup
Deno supports Typescript as a first class citizen, so for all the code that follows, we do not need a tsconfig.json
. I’ll work in a single file called linked-list.ts
.
Our Node
class
Because the list consists of a group of nodes, let’s define a generic node class first.
class Node<T> {
value: T;
next?: Node<T>;
previous?: Node<T>;
constructor(value: T) {
this.value = value;
}
}
Our LinkedList
class
Now, let’s create our generic LinkedList
class. We’ll start with a LinkedList.prototype.count
method.
Note: the '#
' here denotes a new ECMAScript syntax for adding private fields to a class, support for which was added in Typescript v3.8.
class LinkedList<T> {
#count = 0;
count(): number {
return this.#count;
}
}
We will need a few more properties and methods in order to do anything interesting with our linked list, so let’s finish laying the ground work by adding the head
and tail
private properties, in addition to the isEmpty
, head
, and tail
methods.
class LinkedList<T> {
#head?: Node<T>;
#tail?: Node<T>;
#count = 0;
isEmpty() {
return this.count() === 0;
}
head(): Node<T> | undefined {
return this.#head;
}
tail(): Node<T> | undefined {
return this.#tail;
}
count(): number {
return this.#count;
}
}
With that, let’s get started with the adding some real functionality to our implementation. We’ll begin with the append
method.
LinkedList.prototype.append
class LinkedList<T> {
...
append(value: T): void {
const node = new Node<T>(value);
const tail = this.tail();
if (tail) {
tail.next = node;
node.previous = tail;
this.#tail = node;
} else {
this.#head = node;
this.#tail = node;
}
this.#count++;
}
...
}
When we append a new node to a list that already contains nodes, there’re a few things we need to do, among them, we need to get a reference to the last node in the list, so we can update it’s next
property to point to our new node. Also, we’ll set the previous
property on the new node to point to last node in the list, which effectively means our new node has become the last node in the list. Thus, we update the tail
property accordingly.
If the list was empty, the new node will become both the head
and tail
nodes, so we update both properties, accordingly.
Finally, we increment the count
property because we’ve added a new node.
Our append
method is an O(1) operation because we’re keeping track of each node at the end of the list with the tail
property.
A (slight) detour into unit testing with Deno
Deno’s standard library has some assertion functions to facilitate testing. Let’s import assertEquals
, so we can test our implementation so far. Deno supports ES6 import
statements out-of-the-box with one caveat; instead of importing a package from a node_modules
directory, you pass it a fully qualified URL.
import { assertEquals } from "https://deno.land/[email protected]/testing/asserts.ts";
Let’s write some basic unit tests for what we have so far.
// LinkedList.prototype.append
(() => {
const list = new LinkedList<string>();
list.append("foo");
list.append("bar");
const head = list.head();
const tail = list.tail();
assertEquals(head!.value, "foo");
assertEquals(head!.next!.value, "bar");
assertEquals(tail!.value, "bar");
assertEquals(tail!.previous!.value, "foo");
})();
// LinkedList.prototype.count
(() => {
const list = new LinkedList<string>();
list.append("foo");
assertEquals(list.count(), 1);
list.append("bar");
assertEquals(list.count(), 2);
})();
// LinkedList.prototype.isEmpty
(() => {
const list = new LinkedList<string>();
assertEquals(list.isEmpty(), true);
list.append("foo");
assertEquals(list.isEmpty(), false);
})();
To run the tests, type the following in your shell. You should see no output from the command if all goes well and you have no failures.
$ deno run linked-list.ts
The complete code for this linked list implementation is available on Github.