Skip to content

Commit 99bbfb3

Browse files
authored
Merge pull request #2 from gwtw/1_strict
Enable strict null checks
2 parents 09ddf63 + ecfd1f0 commit 99bbfb3

14 files changed

+234
-73
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
A TypeScript implementation of the [AVL tree](http://www.growingwiththeweb.com/data-structures/avl-tree/overview/) data structure.
77

8+
Note that the primary purpose of this library is education but it should work in a production environment as well. It's certainly not as performant as it could be as it optimises for readability, abstraction and safety over raw performance.
9+
810
![](http://www.growingwiththeweb.com/images/data-structures/avl-tree/avl-tree.svg)
911

1012
## Features

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"build": "tsc",
77
"lint": "tslint --project .",
88
"test": "nyc mocha lib/test/*.test.js",
9+
"test-report": "nyc --reporter html mocha lib/test/*.test.js",
910
"watch": "tsc -w",
1011
"prepublish": "npm run build"
1112
},
@@ -35,7 +36,7 @@
3536
"nyc": "^11.4.1",
3637
"tslint": "^5.9.1",
3738
"tslint-consistent-codestyle": "^1.13.0",
38-
"typescript": "^2.8.3"
39+
"typescript": "^3.0.3"
3940
},
4041
"files": [
4142
"lib/**/*.js",

src/avlTree.ts

Lines changed: 39 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
*/
66

77
import { Node } from './node';
8-
9-
export type CompareFunction<K> = (a: K, b: K) => number;
8+
import { AvlTree as AvlTreeApi, CompareFunction } from '@tyriar/avl-tree';
109

1110
/**
1211
* Represents how balanced a node's left and right children are.
@@ -24,20 +23,19 @@ const enum BalanceState {
2423
UNBALANCED_LEFT
2524
}
2625

27-
export class AvlTree<K, V> {
28-
protected _root: Node<K, V> = null;
26+
export class AvlTree<K, V> implements AvlTreeApi<K, V> {
27+
protected _root: Node<K, V> | null = null;
2928
private _size: number = 0;
29+
private _compare: CompareFunction<K>;
3030

3131
/**
3232
* Creates a new AVL Tree.
3333
* @param _compare An optional custom compare function.
3434
*/
3535
constructor(
36-
private _compare?: CompareFunction<K>
36+
compare?: CompareFunction<K>
3737
) {
38-
if (!_compare) {
39-
this._compare = this._defaultCompare;
40-
}
38+
this._compare = compare ? compare : this._defaultCompare;
4139
}
4240

4341
/**
@@ -72,7 +70,7 @@ export class AvlTree<K, V> {
7270
* @param root The root of the tree to insert in.
7371
* @return The new tree root.
7472
*/
75-
private _insert(key: K, value: V, root: Node<K, V>): Node<K, V> {
73+
private _insert(key: K, value: V | undefined, root: Node<K, V> | null): Node<K, V> {
7674
// Perform regular BST insertion
7775
if (root === null) {
7876
return new Node(key, value);
@@ -93,23 +91,23 @@ export class AvlTree<K, V> {
9391
const balanceState = this._getBalanceState(root);
9492

9593
if (balanceState === BalanceState.UNBALANCED_LEFT) {
96-
if (this._compare(key, root.left.key) < 0) {
94+
if (this._compare(key, (<Node<K, V>>root.left).key) < 0) {
9795
// Left left case
9896
root = root.rotateRight();
9997
} else {
10098
// Left right case
101-
root.left = root.left.rotateLeft();
99+
root.left = (<Node<K, V>>root.left).rotateLeft();
102100
return root.rotateRight();
103101
}
104102
}
105103

106104
if (balanceState === BalanceState.UNBALANCED_RIGHT) {
107-
if (this._compare(key, root.right.key) > 0) {
105+
if (this._compare(key, (<Node<K, V>>root.right).key) > 0) {
108106
// Right right case
109107
root = root.rotateLeft();
110108
} else {
111109
// Right left case
112-
root.right = root.right.rotateRight();
110+
root.right = (<Node<K, V>>root.right).rotateRight();
113111
return root.rotateLeft();
114112
}
115113
}
@@ -132,7 +130,7 @@ export class AvlTree<K, V> {
132130
* @param root The root of the tree to delete from.
133131
* @return The new tree root.
134132
*/
135-
private _delete(key: K, root: Node<K, V>): Node<K, V> {
133+
private _delete(key: K, root: Node<K, V> | null): Node<K, V> | null {
136134
// Perform regular BST deletion
137135
if (root === null) {
138136
this._size++;
@@ -155,7 +153,7 @@ export class AvlTree<K, V> {
155153
root = root.left;
156154
} else {
157155
// Node has 2 children, get the in-order successor
158-
const inOrderSuccessor = this._minValueNode(root.right);
156+
const inOrderSuccessor = this._minValueNode(<Node<K, V>>root.right);
159157
root.key = inOrderSuccessor.key;
160158
root.value = inOrderSuccessor.value;
161159
root.right = this._delete(inOrderSuccessor.key, root.right);
@@ -172,25 +170,25 @@ export class AvlTree<K, V> {
172170

173171
if (balanceState === BalanceState.UNBALANCED_LEFT) {
174172
// Left left case
175-
if (this._getBalanceState(root.left) === BalanceState.BALANCED ||
176-
this._getBalanceState(root.left) === BalanceState.SLIGHTLY_UNBALANCED_LEFT) {
173+
if (this._getBalanceState((<Node<K, V>>root.left)) === BalanceState.BALANCED ||
174+
this._getBalanceState((<Node<K, V>>root.left)) === BalanceState.SLIGHTLY_UNBALANCED_LEFT) {
177175
return root.rotateRight();
178176
}
179177
// Left right case
180178
// this._getBalanceState(root.left) === BalanceState.SLIGHTLY_UNBALANCED_RIGHT
181-
root.left = root.left.rotateLeft();
179+
root.left = (<Node<K, V>>root.left).rotateLeft();
182180
return root.rotateRight();
183181
}
184182

185183
if (balanceState === BalanceState.UNBALANCED_RIGHT) {
186184
// Right right case
187-
if (this._getBalanceState(root.right) === BalanceState.BALANCED ||
188-
this._getBalanceState(root.right) === BalanceState.SLIGHTLY_UNBALANCED_RIGHT) {
185+
if (this._getBalanceState((<Node<K, V>>root.right)) === BalanceState.BALANCED ||
186+
this._getBalanceState((<Node<K, V>>root.right)) === BalanceState.SLIGHTLY_UNBALANCED_RIGHT) {
189187
return root.rotateLeft();
190188
}
191189
// Right left case
192190
// this._getBalanceState(root.right) === BalanceState.SLIGHTLY_UNBALANCED_LEFT
193-
root.right = root.right.rotateRight();
191+
root.right = (<Node<K, V>>root.right).rotateRight();
194192
return root.rotateLeft();
195193
}
196194

@@ -200,14 +198,20 @@ export class AvlTree<K, V> {
200198
/**
201199
* Gets the value of a node within the tree with a specific key.
202200
* @param key The key being searched for.
203-
* @return The value of the node or null if it doesn't exist.
201+
* @return The value of the node (which may be undefined), or null if it
202+
* doesn't exist.
204203
*/
205-
public get(key: K): V {
204+
public get(key: K): V | undefined | null {
206205
if (this._root === null) {
207206
return null;
208207
}
209208

210-
return this._get(key, this._root).value;
209+
const result = this._get(key, this._root);
210+
if (result === null) {
211+
return null;
212+
}
213+
214+
return result.value;
211215
}
212216

213217
/**
@@ -216,7 +220,7 @@ export class AvlTree<K, V> {
216220
* @param root The root of the tree to search in.
217221
* @return The value of the node or null if it doesn't exist.
218222
*/
219-
private _get(key: K, root: Node<K, V>): Node<K, V> {
223+
private _get(key: K, root: Node<K, V>): Node<K, V> | null {
220224
const result = this._compare(key, root.key);
221225
if (result === 0) {
222226
return root;
@@ -249,16 +253,22 @@ export class AvlTree<K, V> {
249253
}
250254

251255
/**
252-
* @return The minimum key in the tree.
256+
* @return The minimum key in the tree or null if there are no nodes.
253257
*/
254-
public findMinimum(): K {
258+
public findMinimum(): K | null {
259+
if (this._root === null) {
260+
return null;
261+
}
255262
return this._minValueNode(this._root).key;
256263
}
257264

258265
/**
259-
* Gets the maximum key in the tree.
266+
* Gets the maximum key in the tree or null if there are no nodes.
260267
*/
261-
public findMaximum(): K {
268+
public findMaximum(): K | null {
269+
if (this._root === null) {
270+
return null;
271+
}
262272
return this._maxValueNode(this._root).key;
263273
}
264274

src/node.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
*/
66

77
export class Node<K, V> {
8-
public left: Node<K, V> = null;
9-
public right: Node<K, V> = null;
10-
public height: number = null;
8+
public left: Node<K, V> | null = null;
9+
public right: Node<K, V> | null = null;
10+
public height: number | null = null;
1111

1212
/**
1313
* Creates a new AVL Tree node.
@@ -16,21 +16,22 @@ export class Node<K, V> {
1616
*/
1717
constructor(
1818
public key: K,
19-
public value: V
19+
public value: V | undefined
2020
) {
2121
}
2222

2323
/**
2424
* Performs a right rotate on this node.
2525
* @return The root of the sub-tree; the node where this node used to be.
26+
* @throws If Node.left is null.
2627
*/
2728
public rotateRight(): Node<K, V> {
2829
// b a
2930
// / \ / \
3031
// a e -> b.rotateRight() -> c b
3132
// / \ / \
3233
// c d d e
33-
const other = this.left;
34+
const other = <Node<K, V>>this.left;
3435
this.left = other.right;
3536
other.right = this;
3637
this.height = Math.max(this.leftHeight, this.rightHeight) + 1;
@@ -41,14 +42,15 @@ export class Node<K, V> {
4142
/**
4243
* Performs a left rotate on this node.
4344
* @return The root of the sub-tree; the node where this node used to be.
45+
* @throws If Node.right is null.
4446
*/
4547
public rotateLeft(): Node<K, V> {
4648
// a b
4749
// / \ / \
4850
// c b -> a.rotateLeft() -> a e
4951
// / \ / \
5052
// d e c d
51-
const other = this.right;
53+
const other = <Node<K, V>>this.right;
5254
this.right = other.left;
5355
other.left = this;
5456
this.height = Math.max(this.leftHeight, this.rightHeight) + 1;
@@ -62,10 +64,10 @@ export class Node<K, V> {
6264
* @return The height of the left child, or -1 if it doesn't exist.
6365
*/
6466
public get leftHeight(): number {
65-
if (!this.left) {
67+
if (this.left === null) {
6668
return -1;
6769
}
68-
return this.left.height;
70+
return this.left.height || 0;
6971
}
7072

7173
/**
@@ -74,9 +76,9 @@ export class Node<K, V> {
7476
* @return The height of the right child, or -1 if it doesn't exist.
7577
*/
7678
public get rightHeight(): number {
77-
if (!this.right) {
79+
if (this.right === null) {
7880
return -1;
7981
}
80-
return this.right.height;
82+
return this.right.height || 0;
8183
}
8284
}

src/test/custom-compare.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,16 @@ describe('Custom compare function', () => {
1212
assert.equal(tree.findMaximum(), 1);
1313
tree.delete(3);
1414
assert.equal(tree.size, 2);
15+
if (!tree.root) {
16+
assert.fail('tree.root must exist');
17+
return;
18+
}
1519
assert.equal(tree.root.key, 2);
1620
assert.equal(tree.root.left, null);
21+
if (!tree.root.right) {
22+
assert.fail('tree.root.right must exist');
23+
return;
24+
}
1725
assert.equal(tree.root.right.key, 1);
1826
});
1927

0 commit comments

Comments
 (0)