terra-route 0.0.6 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
1
  import { Position } from "geojson";
2
2
 
3
+ /** Distance measured in kilometers */
3
4
  export const haversineDistance = (pointOne: Position, pointTwo: Position): number => {
4
5
  const toRadians = (latOrLng: number) => (latOrLng * Math.PI) / 180;
5
6
 
@@ -22,4 +23,4 @@ export const haversineDistance = (pointOne: Position, pointTwo: Position): numbe
22
23
  const distance = radius * c;
23
24
 
24
25
  return distance / 1000;
25
- }
26
+ }
@@ -0,0 +1,98 @@
1
+ import { FibonacciHeap } from "./fibonacci-heap";
2
+
3
+ describe("FibonacciHeap", () => {
4
+ it("should report size zero for a new heap", () => {
5
+ const heap = new FibonacciHeap();
6
+ expect(heap.size()).toBe(0);
7
+ });
8
+
9
+ it("should insert a single element and extract it as the minimum", () => {
10
+ const heap = new FibonacciHeap();
11
+ heap.insert(42, 42);
12
+ expect(heap.size()).toBe(1);
13
+ expect(heap.extractMin()).toBe(42);
14
+ expect(heap.size()).toBe(0);
15
+ });
16
+
17
+ it("should always extract the smallest key first when inserting ascending keys", () => {
18
+ const heap = new FibonacciHeap();
19
+ for (let key = 1; key <= 10; key += 1) {
20
+ heap.insert(key, key);
21
+ }
22
+ expect(heap.extractMin()).toBe(1);
23
+ });
24
+
25
+ it("should extract elements in ascending order regardless of insertion order", () => {
26
+ const heap = new FibonacciHeap();
27
+ const keys = [5, 3, 8, 1, 7, 2, 4, 6];
28
+ keys.forEach((key) => heap.insert(key, key));
29
+
30
+ const extracted: number[] = [];
31
+ while (heap.size() > 0) {
32
+ const value = heap.extractMin();
33
+ // value is typed as number | null but should never be null inside the loop
34
+ extracted.push(value as number);
35
+ }
36
+
37
+ expect(extracted).toEqual(keys.slice().sort((first, second) => first - second));
38
+ });
39
+
40
+ it("should handle duplicate keys correctly and still extract all values", () => {
41
+ const heap = new FibonacciHeap();
42
+ heap.insert(10, 100);
43
+ heap.insert(10, 200);
44
+ heap.insert(5, 50);
45
+
46
+ expect(heap.extractMin()).toBe(50); // key 5
47
+ // the two duplicate keys (10) can come out in any order for value
48
+ const remaining = [heap.extractMin(), heap.extractMin()].sort();
49
+ expect(remaining).toEqual([100, 200]);
50
+ expect(heap.extractMin()).toBeNull();
51
+ });
52
+
53
+ it("should return null when extracting from an empty heap", () => {
54
+ const heap = new FibonacciHeap();
55
+ expect(heap.extractMin()).toBeNull();
56
+ });
57
+
58
+ it("should update size correctly after mixed insert and extract operations", () => {
59
+ const heap = new FibonacciHeap();
60
+ heap.insert(20, 20);
61
+ heap.insert(5, 5);
62
+ heap.insert(15, 15);
63
+ expect(heap.size()).toBe(3);
64
+
65
+ heap.extractMin(); // removes value 5
66
+ expect(heap.size()).toBe(2);
67
+
68
+ heap.extractMin(); // removes value 15
69
+ heap.extractMin(); // removes value 20
70
+ expect(heap.size()).toBe(0);
71
+ });
72
+
73
+ it("should cope with a larger randomised workload", () => {
74
+ const heap = new FibonacciHeap();
75
+ const elementCount = 1000;
76
+ const insertedKeys: number[] = [];
77
+
78
+ for (let index = 0; index < elementCount; index += 1) {
79
+ // random key in range [0, 9999]
80
+ const randomKey = Math.floor(Math.random() * 10000);
81
+ insertedKeys.push(randomKey);
82
+ heap.insert(randomKey, randomKey);
83
+ }
84
+ insertedKeys.sort((first, second) => first - second);
85
+
86
+ const extracted: number[] = [];
87
+ let extractedValue: number | null;
88
+ do {
89
+ extractedValue = heap.extractMin();
90
+ if (extractedValue !== null) {
91
+ extracted.push(extractedValue);
92
+ }
93
+ } while (extractedValue !== null);
94
+
95
+ expect(extracted).toEqual(insertedKeys);
96
+ expect(heap.size()).toBe(0);
97
+ });
98
+ });
@@ -0,0 +1,210 @@
1
+ import { Heap } from "./heap";
2
+
3
+ /**
4
+ * Internal node representation for the Fibonacci heap, expressed as a plain
5
+ * JavaScript object (no dedicated class).
6
+ */
7
+ interface FibonacciNode {
8
+ key: number;
9
+ value: number;
10
+ degree: number;
11
+ parent: FibonacciNode | null;
12
+ child: FibonacciNode | null;
13
+ next: FibonacciNode; // clockwise pointer in circular list
14
+ prev: FibonacciNode; // counter‑clockwise pointer in circular list
15
+ mark: boolean; // used by decrease‑key (not implemented here)
16
+ }
17
+
18
+ /**
19
+ * Fibonacci heap that conforms to the Terra Route `Heap` interface.
20
+ * Only the heap itself is a class; each node is a lightweight object created
21
+ * inline where needed.
22
+ */
23
+ export class FibonacciHeap implements Heap {
24
+ private minNode: FibonacciNode | null = null;
25
+ private totalNodes = 0;
26
+
27
+ /**
28
+ * Inserts a new `(key, value)` pair into the heap.
29
+ */
30
+ public insert(key: number, value: number): void {
31
+ // ---------------- inline node creation ---------------- //
32
+ const node: FibonacciNode = {
33
+ key,
34
+ value,
35
+ degree: 0,
36
+ parent: null,
37
+ child: null,
38
+ next: null as unknown as FibonacciNode,
39
+ prev: null as unknown as FibonacciNode,
40
+ mark: false,
41
+ };
42
+ node.next = node;
43
+ node.prev = node;
44
+ // ------------------------------------------------------- //
45
+
46
+ if (this.minNode === null) {
47
+ this.minNode = node;
48
+ } else {
49
+ // splice node into the circular doubly‑linked root list right after `minNode`
50
+ node.prev = this.minNode;
51
+ node.next = this.minNode.next;
52
+ this.minNode.next.prev = node;
53
+ this.minNode.next = node;
54
+
55
+ if (node.key < this.minNode.key) {
56
+ this.minNode = node;
57
+ }
58
+ }
59
+
60
+ this.totalNodes += 1;
61
+ }
62
+
63
+ /**
64
+ * Removes and returns the value associated with the minimum key. Returns
65
+ * `null` if the heap is empty.
66
+ */
67
+ public extractMin(): number | null {
68
+ const oldMin = this.minNode;
69
+ if (oldMin === null) {
70
+ return null;
71
+ }
72
+
73
+ // Promote each child of `oldMin` to the root list
74
+ if (oldMin.child !== null) {
75
+ let childStart = oldMin.child;
76
+ const children: FibonacciNode[] = [];
77
+ do {
78
+ children.push(childStart);
79
+ childStart = childStart.next;
80
+ } while (childStart !== oldMin.child);
81
+
82
+ for (const child of children) {
83
+ // detach from child list
84
+ child.parent = null;
85
+ child.prev.next = child.next;
86
+ child.next.prev = child.prev;
87
+
88
+ // add to root list right after `oldMin`
89
+ child.prev = this.minNode!;
90
+ child.next = this.minNode!.next;
91
+ this.minNode!.next.prev = child;
92
+ this.minNode!.next = child;
93
+ }
94
+ }
95
+
96
+ // Remove `oldMin` from root list
97
+ oldMin.prev.next = oldMin.next;
98
+ oldMin.next.prev = oldMin.prev;
99
+
100
+ if (oldMin === oldMin.next) {
101
+ // The heap had exactly one root node and is now empty
102
+ this.minNode = null;
103
+ } else {
104
+ this.minNode = oldMin.next;
105
+ this.consolidate();
106
+ }
107
+
108
+ this.totalNodes -= 1;
109
+ return oldMin.value;
110
+ }
111
+
112
+ /** Returns the number of elements stored in the heap. */
113
+ public size(): number {
114
+ return this.totalNodes;
115
+ }
116
+
117
+ // --------------------------- internal helpers --------------------------- //
118
+
119
+ /**
120
+ * Merges trees of equal degree so that at most one root of each degree
121
+ * remains. Runs in `O(log n)` time and is called after `extractMin`.
122
+ */
123
+ private consolidate(): void {
124
+ if (this.minNode === null) {
125
+ return;
126
+ }
127
+
128
+ // upper bound for the degree of any node is ⌊log₂ n⌋
129
+ const upperBound = Math.floor(Math.log2(this.totalNodes)) + 2;
130
+ const degreeTable: Array<FibonacciNode | null> = new Array(upperBound).fill(null);
131
+
132
+ // snapshot of the current root list prior to mutation
133
+ const rootList: FibonacciNode[] = [];
134
+ let currentRoot = this.minNode;
135
+ do {
136
+ rootList.push(currentRoot);
137
+ currentRoot = currentRoot.next;
138
+ } while (currentRoot !== this.minNode);
139
+
140
+ for (const root of rootList) {
141
+ let x = root;
142
+ let degree = x.degree;
143
+
144
+ while (degreeTable[degree] !== null) {
145
+ let y = degreeTable[degree] as FibonacciNode;
146
+ if (y.key < x.key) {
147
+ // ensure `x` has the smaller key
148
+ const temporary = x;
149
+ x = y;
150
+ y = temporary;
151
+ }
152
+ this.link(y, x);
153
+ degreeTable[degree] = null;
154
+ degree += 1;
155
+ }
156
+ degreeTable[degree] = x;
157
+ }
158
+
159
+ // rebuild root list and identify new minimum
160
+ this.minNode = null;
161
+ for (const node of degreeTable) {
162
+ if (node === null) {
163
+ continue;
164
+ }
165
+
166
+ if (this.minNode === null) {
167
+ this.minNode = node;
168
+ node.prev = node;
169
+ node.next = node;
170
+ } else {
171
+ // insert `node` right after current min
172
+ node.prev = this.minNode;
173
+ node.next = this.minNode.next;
174
+ this.minNode.next.prev = node;
175
+ this.minNode.next = node;
176
+
177
+ if (node.key < this.minNode.key) {
178
+ this.minNode = node;
179
+ }
180
+ }
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Makes `child` a child of `parent`. Assumes both nodes are roots of equal
186
+ * degree. Runs in `O(1)` time.
187
+ */
188
+ private link(child: FibonacciNode, parent: FibonacciNode): void {
189
+ // detach `child` from the root list
190
+ child.prev.next = child.next;
191
+ child.next.prev = child.prev;
192
+
193
+ // make `child` a child of `parent`
194
+ child.parent = parent;
195
+ child.mark = false;
196
+
197
+ if (parent.child === null) {
198
+ parent.child = child;
199
+ child.prev = child;
200
+ child.next = child;
201
+ } else {
202
+ child.prev = parent.child;
203
+ child.next = parent.child.next;
204
+ parent.child.next.prev = child;
205
+ parent.child.next = child;
206
+ }
207
+
208
+ parent.degree += 1;
209
+ }
210
+ }
@@ -0,0 +1,10 @@
1
+ export interface Heap {
2
+ insert: (key: number, value: number) => void;
3
+ extractMin: () => number | null;
4
+ size: () => number;
5
+ }
6
+
7
+ export interface HeapConstructor {
8
+ // new() returns an instance that implements ExampleInterface
9
+ new(): Heap;
10
+ }
@@ -0,0 +1,127 @@
1
+ import { MinHeap } from "./min-heap";
2
+
3
+ describe("MinHeap", () => {
4
+ let minHeap: MinHeap;
5
+
6
+ beforeEach(() => {
7
+ minHeap = new MinHeap();
8
+ });
9
+
10
+ it("inserts and extracts a single element", () => {
11
+ expect(minHeap.size()).toBe(0);
12
+
13
+ minHeap.insert(5, 123);
14
+
15
+ expect(minHeap.size()).toBe(1);
16
+ expect(minHeap.extractMin()).toBe(123);
17
+ expect(minHeap.size()).toBe(0);
18
+ });
19
+
20
+ it("extracts null when the heap is empty", () => {
21
+ expect(minHeap.extractMin()).toBeNull();
22
+ expect(minHeap.size()).toBe(0);
23
+ });
24
+
25
+ it("maintains proper order for ascending key inserts", () => {
26
+ const keys = [1, 2, 3, 4, 5];
27
+ keys.forEach((key) => {
28
+ minHeap.insert(key, key + 100);
29
+ });
30
+
31
+ // Expect the smallest key to be extracted first
32
+ expect(minHeap.extractMin()).toBe(101); // key=1 => value=101
33
+ expect(minHeap.extractMin()).toBe(102); // key=2 => value=102
34
+ expect(minHeap.extractMin()).toBe(103);
35
+ expect(minHeap.extractMin()).toBe(104);
36
+ expect(minHeap.extractMin()).toBe(105);
37
+ expect(minHeap.extractMin()).toBeNull();
38
+ });
39
+
40
+ it("maintains proper order for descending key inserts", () => {
41
+ const keys = [5, 4, 3, 2, 1];
42
+ keys.forEach((key) => {
43
+ minHeap.insert(key, key + 200);
44
+ });
45
+
46
+ // The smallest key is 1
47
+ expect(minHeap.extractMin()).toBe(201);
48
+ // Next smallest is 2
49
+ expect(minHeap.extractMin()).toBe(202);
50
+ // Then 3, 4, 5
51
+ expect(minHeap.extractMin()).toBe(203);
52
+ expect(minHeap.extractMin()).toBe(204);
53
+ expect(minHeap.extractMin()).toBe(205);
54
+ expect(minHeap.extractMin()).toBeNull();
55
+ });
56
+
57
+ it("handles duplicate keys by respecting insertion order", () => {
58
+ // Insert multiple items with the same key but different values
59
+ minHeap.insert(10, 1);
60
+ minHeap.insert(10, 2);
61
+ minHeap.insert(10, 3);
62
+
63
+ // The heap should prioritize the earliest inserted item first
64
+ expect(minHeap.extractMin()).toBe(1);
65
+ expect(minHeap.extractMin()).toBe(2);
66
+ expect(minHeap.extractMin()).toBe(3);
67
+ expect(minHeap.extractMin()).toBeNull();
68
+ });
69
+
70
+ it("handles a mix of negative and positive keys", () => {
71
+ const items = [
72
+ { key: -10, value: 1 },
73
+ { key: 0, value: 2 },
74
+ { key: 10, value: 3 },
75
+ { key: -5, value: 4 },
76
+ { key: 5, value: 5 },
77
+ ];
78
+ items.forEach((item) => minHeap.insert(item.key, item.value));
79
+
80
+ // Order of extracted values should respect key order (lowest key first)
81
+ expect(minHeap.extractMin()).toBe(1); // key = -10
82
+ expect(minHeap.extractMin()).toBe(4); // key = -5
83
+ expect(minHeap.extractMin()).toBe(2); // key = 0
84
+ expect(minHeap.extractMin()).toBe(5); // key = 5
85
+ expect(minHeap.extractMin()).toBe(3); // key = 10
86
+ expect(minHeap.extractMin()).toBeNull();
87
+ });
88
+
89
+ it("reflects correct size throughout inserts and extracts", () => {
90
+ expect(minHeap.size()).toBe(0);
91
+
92
+ minHeap.insert(2, 100);
93
+ minHeap.insert(1, 200);
94
+ minHeap.insert(3, 300);
95
+
96
+ expect(minHeap.size()).toBe(3);
97
+
98
+ minHeap.extractMin(); // remove key=1
99
+ expect(minHeap.size()).toBe(2);
100
+
101
+ minHeap.extractMin(); // remove key=2
102
+ expect(minHeap.size()).toBe(1);
103
+
104
+ minHeap.extractMin(); // remove key=3
105
+ expect(minHeap.size()).toBe(0);
106
+
107
+ // Now empty, should return null
108
+ expect(minHeap.extractMin()).toBeNull();
109
+ expect(minHeap.size()).toBe(0);
110
+ });
111
+
112
+ it("handles random insertion order consistently", () => {
113
+ const items = [5, 1, 4, 1, 3, 2, 8, 0, -1];
114
+ items.forEach((k) => {
115
+ minHeap.insert(k, k + 1000);
116
+ });
117
+
118
+ // Sort items to check expected order
119
+ const sorted = [...items].sort((a, b) => a - b);
120
+ sorted.forEach((expectedKey) => {
121
+ const extractedValue = minHeap.extractMin();
122
+ const expectedValue = expectedKey + 1000;
123
+ expect(extractedValue).toBe(expectedValue);
124
+ });
125
+ expect(minHeap.extractMin()).toBeNull();
126
+ });
127
+ });
@@ -0,0 +1,90 @@
1
+ import { Heap } from "./heap";
2
+
3
+ export class MinHeap implements Heap {
4
+ private heap: Array<{ key: number; value: number; index: number }> = [];
5
+ private insertCounter = 0;
6
+
7
+ insert(key: number, value: number): void {
8
+ const node = { key, value, index: this.insertCounter++ };
9
+ let idx = this.heap.length;
10
+ this.heap.push(node);
11
+
12
+ // Optimized Bubble Up
13
+ while (idx > 0) {
14
+ const parentIdx = (idx - 1) >>> 1; // Fast Math.floor((idx - 1) / 2)
15
+ const parent = this.heap[parentIdx];
16
+ if (node.key > parent.key || (node.key === parent.key && node.index > parent.index)) break;
17
+ this.heap[idx] = parent;
18
+ idx = parentIdx;
19
+ }
20
+ this.heap[idx] = node;
21
+ }
22
+
23
+ extractMin(): number | null {
24
+ const length = this.heap.length;
25
+ if (length === 0) return null;
26
+
27
+ const minNode = this.heap[0];
28
+ const endNode = this.heap.pop()!;
29
+
30
+ if (length > 1) {
31
+ this.heap[0] = endNode;
32
+ this.bubbleDown(0);
33
+ }
34
+
35
+ return minNode.value;
36
+ }
37
+
38
+ size(): number {
39
+ return this.heap.length;
40
+ }
41
+
42
+ private bubbleDown(idx: number): void {
43
+ const { heap } = this;
44
+ const length = heap.length;
45
+ // Grab the parent node once, then move it down only if needed
46
+ const node = heap[idx];
47
+ const nodeKey = node.key;
48
+ const nodeIndex = node.index;
49
+
50
+ // eslint-disable-next-line no-constant-condition
51
+ while (true) {
52
+ // Calculate left and right child indexes
53
+ const leftIdx = (idx << 1) + 1;
54
+ if (leftIdx >= length) {
55
+ // No children => we’re already in place
56
+ break;
57
+ }
58
+
59
+ // Assume left child is the smaller one by default
60
+ let smallestIdx = leftIdx;
61
+ let smallestKey = heap[leftIdx].key;
62
+ let smallestIndex = heap[leftIdx].index;
63
+
64
+ const rightIdx = leftIdx + 1;
65
+ if (rightIdx < length) {
66
+ // Compare left child vs. right child
67
+ const rightKey = heap[rightIdx].key;
68
+ const rightIndex = heap[rightIdx].index;
69
+ if (rightKey < smallestKey || (rightKey === smallestKey && rightIndex < smallestIndex)) {
70
+ smallestIdx = rightIdx;
71
+ smallestKey = rightKey;
72
+ smallestIndex = rightIndex;
73
+ }
74
+ }
75
+
76
+ // Compare the smaller child with the parent
77
+ if (smallestKey < nodeKey || (smallestKey === nodeKey && smallestIndex < nodeIndex)) {
78
+ // Swap the smaller child up
79
+ heap[idx] = heap[smallestIdx];
80
+ idx = smallestIdx;
81
+ } else {
82
+ // We’re in the correct position now, so stop
83
+ break;
84
+ }
85
+ }
86
+
87
+ // Place the original node in its final position
88
+ heap[idx] = node;
89
+ }
90
+ }
@@ -0,0 +1,101 @@
1
+ import { PairingHeap } from "./pairing-heap";
2
+
3
+ describe("PairingHeap", () => {
4
+ it("starts empty", () => {
5
+ const heap = new PairingHeap();
6
+ expect(heap.size()).toBe(0);
7
+ expect(heap.extractMin()).toBeNull();
8
+ });
9
+
10
+ it("inserts elements and extracts them in ascending key order", () => {
11
+ const heap = new PairingHeap();
12
+
13
+ heap.insert(10, 10);
14
+ heap.insert(5, 5);
15
+ heap.insert(20, 20);
16
+
17
+ expect(heap.size()).toBe(3);
18
+
19
+ expect(heap.extractMin()).toBe(5);
20
+ expect(heap.extractMin()).toBe(10);
21
+ expect(heap.extractMin()).toBe(20);
22
+
23
+ expect(heap.size()).toBe(0);
24
+ expect(heap.extractMin()).toBeNull();
25
+ });
26
+
27
+ it("handles duplicate keys correctly", () => {
28
+ const heap = new PairingHeap();
29
+
30
+ heap.insert(7, 70);
31
+ heap.insert(7, 71);
32
+ heap.insert(3, 30);
33
+
34
+ // The two smallest keys are equal (3 and 7). The order of equal‑key
35
+ // removals is not defined, but each extracted value must correspond
36
+ // to the minimum remaining key.
37
+ const first = heap.extractMin();
38
+ expect(first).toBe(30);
39
+
40
+ const second = heap.extractMin();
41
+ const third = heap.extractMin();
42
+ const extractedValues = [second, third].sort((a, b) => (a as number) - (b as number));
43
+
44
+ expect(extractedValues).toEqual([70, 71]);
45
+ });
46
+
47
+ it("works with negative keys", () => {
48
+ const heap = new PairingHeap();
49
+
50
+ heap.insert(-2, -2);
51
+ heap.insert(-10, -10);
52
+ heap.insert(0, 0);
53
+
54
+ expect(heap.extractMin()).toBe(-10);
55
+ expect(heap.extractMin()).toBe(-2);
56
+ expect(heap.extractMin()).toBe(0);
57
+ });
58
+
59
+ it("maintains size correctly through mixed operations", () => {
60
+ const heap = new PairingHeap();
61
+
62
+ heap.insert(4, 4);
63
+ heap.insert(1, 1);
64
+ heap.insert(3, 3);
65
+ expect(heap.size()).toBe(3);
66
+
67
+ heap.extractMin(); // removes 1
68
+ expect(heap.size()).toBe(2);
69
+
70
+ heap.insert(2, 2);
71
+ expect(heap.size()).toBe(3);
72
+
73
+ heap.extractMin(); // removes 2
74
+ heap.extractMin(); // removes 3
75
+ heap.extractMin(); // removes 4
76
+ expect(heap.size()).toBe(0);
77
+ });
78
+
79
+ it("can handle a large random workload and still produce sorted output", () => {
80
+ const heap = new PairingHeap();
81
+
82
+ const randomValues: number[] = [];
83
+ const elementCount = 10_000;
84
+
85
+ for (let index = 0; index < elementCount; index += 1) {
86
+ const key = Math.floor(Math.random() * 1_000_000) - 500_000;
87
+ randomValues.push(key);
88
+ heap.insert(key, key);
89
+ }
90
+
91
+ randomValues.sort((a, b) => a - b);
92
+
93
+ for (let index = 0; index < elementCount; index += 1) {
94
+ const extracted = heap.extractMin();
95
+ expect(extracted).toBe(randomValues[index]);
96
+ }
97
+
98
+ expect(heap.size()).toBe(0);
99
+ expect(heap.extractMin()).toBeNull();
100
+ });
101
+ });