]>
Commit | Line | Data |
---|---|---|
1 | // Copyright Jerome Benoit. 2024. All Rights Reserved. | |
2 | ||
3 | import { FixedPriorityQueue } from './fixed-priority-queue.js' | |
4 | ||
5 | /** | |
6 | * Default bucket size. | |
7 | */ | |
8 | export const defaultBucketSize = 2048 | |
9 | ||
10 | /** | |
11 | * Priority queue node. | |
12 | * @typeParam T - Type of priority queue node data. | |
13 | * @internal | |
14 | */ | |
15 | export interface PriorityQueueNode<T> extends FixedPriorityQueue<T> { | |
16 | next?: FixedPriorityQueue<T> | |
17 | } | |
18 | ||
19 | /** | |
20 | * Priority queue. | |
21 | * @typeParam T - Type of priority queue data. | |
22 | * @internal | |
23 | */ | |
24 | export class PriorityQueue<T> { | |
25 | private head!: PriorityQueueNode<T> | |
26 | private tail!: PriorityQueueNode<T> | |
27 | private readonly bucketSize: number | |
28 | /** The priority queue maximum size. */ | |
29 | public maxSize!: number | |
30 | ||
31 | /** | |
32 | * Constructs a priority queue. | |
33 | * @param bucketSize - Prioritized bucket size. @defaultValue defaultBucketSize | |
34 | * @param enablePriority - Whether to enable priority. @defaultValue false | |
35 | * @returns PriorityQueue. | |
36 | */ | |
37 | public constructor ( | |
38 | bucketSize: number = defaultBucketSize, | |
39 | enablePriority = false | |
40 | ) { | |
41 | if (!Number.isSafeInteger(bucketSize)) { | |
42 | throw new TypeError( | |
43 | `Invalid bucket size: '${bucketSize.toString()}' is not an integer` | |
44 | ) | |
45 | } | |
46 | if (bucketSize < 0) { | |
47 | throw new RangeError(`Invalid bucket size: ${bucketSize.toString()} < 0`) | |
48 | } | |
49 | this.bucketSize = bucketSize | |
50 | this.head = this.tail = new FixedPriorityQueue( | |
51 | this.bucketSize, | |
52 | enablePriority | |
53 | ) | |
54 | this.maxSize = 0 | |
55 | } | |
56 | ||
57 | /** | |
58 | * The priority queue size. | |
59 | * @returns The priority queue size. | |
60 | */ | |
61 | public get size (): number { | |
62 | let node: PriorityQueueNode<T> | undefined = this.tail | |
63 | let size = 0 | |
64 | while (node != null) { | |
65 | size += node.size | |
66 | node = node.next | |
67 | } | |
68 | return size | |
69 | } | |
70 | ||
71 | public get enablePriority (): boolean { | |
72 | return this.head.enablePriority | |
73 | } | |
74 | ||
75 | public set enablePriority (enablePriority: boolean) { | |
76 | if (this.head.enablePriority === enablePriority) { | |
77 | return | |
78 | } | |
79 | let node: PriorityQueueNode<T> | undefined = this.tail | |
80 | while (node != null) { | |
81 | node.enablePriority = enablePriority | |
82 | node = node.next | |
83 | } | |
84 | } | |
85 | ||
86 | /** | |
87 | * The number of filled prioritized buckets. | |
88 | * @returns The number of filled prioritized buckets. | |
89 | */ | |
90 | public get buckets (): number { | |
91 | return Math.trunc(this.size / this.bucketSize) | |
92 | } | |
93 | ||
94 | /** | |
95 | * Enqueue data into the priority queue. | |
96 | * @param data - Data to enqueue. | |
97 | * @param priority - Priority of the data. Lower values have higher priority. | |
98 | * @returns The new size of the priority queue. | |
99 | */ | |
100 | public enqueue (data: T, priority?: number): number { | |
101 | if (this.head.full()) { | |
102 | this.head = this.head.next = new FixedPriorityQueue( | |
103 | this.bucketSize, | |
104 | this.enablePriority | |
105 | ) | |
106 | } | |
107 | this.head.enqueue(data, priority) | |
108 | const size = this.size | |
109 | if (size > this.maxSize) { | |
110 | this.maxSize = size | |
111 | } | |
112 | return size | |
113 | } | |
114 | ||
115 | /** | |
116 | * Dequeue data from the priority queue. | |
117 | * @param bucket - The prioritized bucket to dequeue from. | |
118 | * @returns The dequeued data or `undefined` if the priority queue is empty. | |
119 | */ | |
120 | public dequeue (bucket?: number): T | undefined { | |
121 | let tail: PriorityQueueNode<T> | undefined = this.tail | |
122 | let tailChanged = false | |
123 | if (bucket != null && bucket > 0) { | |
124 | let currentBucket = 1 | |
125 | while (tail != null) { | |
126 | if (currentBucket === bucket) { | |
127 | break | |
128 | } | |
129 | ++currentBucket | |
130 | tail = tail.next | |
131 | } | |
132 | tailChanged = tail !== this.tail | |
133 | } | |
134 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | |
135 | const data = tail!.dequeue() | |
136 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | |
137 | if (tail!.empty()) { | |
138 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | |
139 | if (!tailChanged && tail!.next != null) { | |
140 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | |
141 | this.tail = tail!.next | |
142 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | |
143 | delete tail!.next | |
144 | } else if (tailChanged) { | |
145 | let node: PriorityQueueNode<T> | undefined = this.tail | |
146 | while (node != null) { | |
147 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | |
148 | if (node.next === tail && tail!.next != null) { | |
149 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | |
150 | node.next = tail!.next | |
151 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | |
152 | delete tail!.next | |
153 | break | |
154 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | |
155 | } else if (node.next === tail && tail!.next == null) { | |
156 | delete node.next | |
157 | this.head = node | |
158 | break | |
159 | } | |
160 | node = node.next | |
161 | } | |
162 | } | |
163 | } | |
164 | return data | |
165 | } | |
166 | ||
167 | /** | |
168 | * Clears the priority queue. | |
169 | */ | |
170 | public clear (): void { | |
171 | this.head = this.tail = new FixedPriorityQueue( | |
172 | this.bucketSize, | |
173 | this.enablePriority | |
174 | ) | |
175 | this.maxSize = 0 | |
176 | } | |
177 | ||
178 | /** | |
179 | * Returns an iterator for the priority queue. | |
180 | * @returns An iterator for the priority queue. | |
181 | * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols | |
182 | */ | |
183 | public [Symbol.iterator] (): Iterator<T> { | |
184 | let index = 0 | |
185 | let node = this.tail | |
186 | return { | |
187 | next: () => { | |
188 | const value = node.get(index) as T | |
189 | if (value == null) { | |
190 | return { | |
191 | value: undefined, | |
192 | done: true, | |
193 | } | |
194 | } | |
195 | ++index | |
196 | if (index === node.capacity && node.next != null) { | |
197 | node = node.next | |
198 | index = 0 | |
199 | } | |
200 | return { | |
201 | value, | |
202 | done: false, | |
203 | } | |
204 | }, | |
205 | } | |
206 | } | |
207 | } |