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