Commit | Line | Data |
---|---|---|
95d1a734 JB |
1 | // Copyright Jerome Benoit. 2024. All Rights Reserved. |
2 | ||
bcfb06ce | 3 | /** |
95d1a734 JB |
4 | * Priority queue node. |
5 | * | |
6 | * @typeParam T - Type of priority queue node data. | |
bcfb06ce JB |
7 | * @internal |
8 | */ | |
95d1a734 | 9 | export interface PriorityQueueNode<T> { |
bcfb06ce JB |
10 | data: T |
11 | priority: number | |
12 | } | |
13 | ||
14 | /** | |
95d1a734 | 15 | * Priority queue. |
bcfb06ce JB |
16 | * |
17 | * @typeParam T - Type of priority queue data. | |
18 | * @internal | |
19 | */ | |
20 | export class PriorityQueue<T> { | |
21 | private nodeArray!: Array<PriorityQueueNode<T>> | |
bd8b441c | 22 | /** Prioritized bucket size. */ |
2107bc57 | 23 | private readonly bucketSize: number |
bcfb06ce JB |
24 | /** The size of the priority queue. */ |
25 | public size!: number | |
26 | /** The maximum size of the priority queue. */ | |
27 | public maxSize!: number | |
28 | ||
0d4e88b3 JB |
29 | /** |
30 | * The number of filled prioritized buckets. | |
31 | */ | |
32 | public get buckets (): number { | |
2107bc57 JB |
33 | return this.bucketSize === Infinity |
34 | ? 1 | |
35 | : Math.trunc(this.nodeArray.length / this.bucketSize) | |
0d4e88b3 JB |
36 | } |
37 | ||
bd8b441c | 38 | /** |
95d1a734 | 39 | * Constructs a priority queue. |
bd8b441c | 40 | * |
2107bc57 | 41 | * @param bucketSize - Prioritized bucket size. @defaultValue Infinity |
bd8b441c | 42 | */ |
2107bc57 JB |
43 | public constructor (bucketSize = Infinity) { |
44 | if (bucketSize !== Infinity && !Number.isSafeInteger(bucketSize)) { | |
45 | throw new TypeError('bucketSize must be an integer') | |
e41b0271 | 46 | } |
2107bc57 JB |
47 | if (bucketSize < 1) { |
48 | throw new RangeError('bucketSize must be greater than or equal to 1') | |
e41b0271 | 49 | } |
2107bc57 | 50 | this.bucketSize = bucketSize |
e41b0271 | 51 | this.clear() |
bcfb06ce JB |
52 | } |
53 | ||
54 | /** | |
55 | * Enqueue data into the priority queue. | |
56 | * | |
57 | * @param data - Data to enqueue. | |
58 | * @param priority - Priority of the data. Lower values have higher priority. | |
59 | * @returns The new size of the priority queue. | |
60 | */ | |
61 | public enqueue (data: T, priority?: number): number { | |
62 | priority = priority ?? 0 | |
2107bc57 JB |
63 | const startIndex = |
64 | this.bucketSize === Infinity ? 0 : this.buckets * this.bucketSize | |
bcfb06ce | 65 | let inserted = false |
e41b0271 JB |
66 | for (let index = startIndex; index < this.nodeArray.length; index++) { |
67 | if (this.nodeArray[index].priority > priority) { | |
bcfb06ce JB |
68 | this.nodeArray.splice(index, 0, { data, priority }) |
69 | inserted = true | |
70 | break | |
71 | } | |
72 | } | |
73 | if (!inserted) { | |
74 | this.nodeArray.push({ data, priority }) | |
75 | } | |
76 | return this.incrementSize() | |
77 | } | |
78 | ||
79 | /** | |
80 | * Dequeue data from the priority queue. | |
81 | * | |
95d1a734 | 82 | * @param bucket - The prioritized bucket to dequeue from. @defaultValue 0 |
bcfb06ce JB |
83 | * @returns The dequeued data or `undefined` if the priority queue is empty. |
84 | */ | |
95d1a734 | 85 | public dequeue (bucket = 0): T | undefined { |
2107bc57 | 86 | if (this.bucketSize !== Infinity && bucket > 0) { |
95d1a734 | 87 | while (bucket > 0) { |
2107bc57 | 88 | const index = bucket * this.bucketSize |
95d1a734 JB |
89 | // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition |
90 | if (this.nodeArray[index] != null) { | |
91 | --this.size | |
92 | return this.nodeArray.splice(index, 1)[0].data | |
93 | } | |
94 | --bucket | |
95 | } | |
96 | } | |
65729b19 | 97 | this.decrementSize() |
bcfb06ce JB |
98 | return this.nodeArray.shift()?.data |
99 | } | |
100 | ||
101 | /** | |
102 | * Peeks at the first data. | |
103 | * @returns The first data or `undefined` if the priority queue is empty. | |
104 | */ | |
105 | public peekFirst (): T | undefined { | |
106 | return this.nodeArray[0]?.data | |
107 | } | |
108 | ||
109 | /** | |
110 | * Peeks at the last data. | |
111 | * @returns The last data or `undefined` if the priority queue is empty. | |
112 | */ | |
113 | public peekLast (): T | undefined { | |
114 | return this.nodeArray[this.nodeArray.length - 1]?.data | |
115 | } | |
116 | ||
117 | /** | |
118 | * Clears the priority queue. | |
119 | */ | |
120 | public clear (): void { | |
121 | this.nodeArray = [] | |
122 | this.size = 0 | |
123 | this.maxSize = 0 | |
124 | } | |
125 | ||
126 | /** | |
95d1a734 JB |
127 | * Returns an iterator for the priority queue. |
128 | * | |
2be9b405 | 129 | * @returns An iterator for the priority queue. |
95d1a734 JB |
130 | * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols |
131 | */ | |
132 | [Symbol.iterator] (): Iterator<T> { | |
133 | let i = 0 | |
134 | return { | |
135 | next: () => { | |
136 | if (i >= this.nodeArray.length) { | |
137 | return { | |
138 | value: undefined, | |
139 | done: true | |
140 | } | |
141 | } | |
142 | const value = this.nodeArray[i].data | |
143 | i++ | |
144 | return { | |
145 | value, | |
146 | done: false | |
147 | } | |
148 | } | |
149 | } | |
150 | } | |
151 | ||
152 | /** | |
153 | * Increments the size of the priority queue. | |
bcfb06ce | 154 | * |
95d1a734 | 155 | * @returns The new size of the priority queue. |
bcfb06ce JB |
156 | */ |
157 | private incrementSize (): number { | |
158 | ++this.size | |
159 | if (this.size > this.maxSize) { | |
160 | this.maxSize = this.size | |
161 | } | |
162 | return this.size | |
163 | } | |
65729b19 JB |
164 | |
165 | /** | |
166 | * Decrements the size of the priority queue. | |
167 | * | |
168 | * @returns The new size of the priority queue. | |
169 | */ | |
170 | private decrementSize (): number { | |
171 | if (this.size > 0) { | |
172 | --this.size | |
173 | } | |
174 | return this.size | |
175 | } | |
bcfb06ce | 176 | } |