build(deps-dev): apply updates
[poolifier.git] / src / priority-queue.ts
index 55a97169f757beb0f53c51843d6a8a87f6392612..ed4d63f5fa9cb03b71f1e91b472f84d1978d2573 100644 (file)
@@ -1,9 +1,20 @@
+// Copyright Jerome Benoit. 2024. All Rights Reserved.
+
+import { FixedPriorityQueue } from './fixed-priority-queue.js'
+
+/**
+ * Default bucket size.
+ */
+export const defaultBucketSize = 2048
+
 /**
+ * Priority queue node.
+ *
+ * @typeParam T - Type of priority queue node data.
  * @internal
  */
-interface PriorityQueueNode<T> {
-  data: T
-  priority: number
+export interface PriorityQueueNode<T> extends FixedPriorityQueue<T> {
+  next?: FixedPriorityQueue<T>
 }
 
 /**
@@ -13,16 +24,48 @@ interface PriorityQueueNode<T> {
  * @internal
  */
 export class PriorityQueue<T> {
-  private nodeArray!: Array<PriorityQueueNode<T>>
-  /** The size of the priority queue. */
-  public size!: number
-  /** The maximum size of the priority queue. */
+  private head!: PriorityQueueNode<T>
+  private tail!: PriorityQueueNode<T>
+  private readonly bucketSize: number
   public maxSize!: number
 
-  public constructor () {
+  /**
+   * Constructs a priority queue.
+   *
+   * @param bucketSize - Prioritized bucket size. @defaultValue defaultBucketSize
+   * @returns PriorityQueue.
+   */
+  public constructor (bucketSize: number = defaultBucketSize) {
+    if (!Number.isSafeInteger(bucketSize)) {
+      throw new TypeError('bucketSize must be an integer')
+    }
+    if (bucketSize < 1) {
+      throw new RangeError('bucketSize must be greater than or equal to 1')
+    }
+    this.bucketSize = bucketSize
     this.clear()
   }
 
+  /**
+   * The size of the priority queue.
+   */
+  public get size (): number {
+    let node: PriorityQueueNode<T> | undefined = this.tail
+    let size = 0
+    while (node != null) {
+      size += node.size
+      node = node.next
+    }
+    return size
+  }
+
+  /**
+   * The number of filled prioritized buckets.
+   */
+  public get buckets (): number {
+    return Math.trunc(this.size / this.bucketSize)
+  }
+
   /**
    * Enqueue data into the priority queue.
    *
@@ -31,68 +74,94 @@ export class PriorityQueue<T> {
    * @returns The new size of the priority queue.
    */
   public enqueue (data: T, priority?: number): number {
-    priority = priority ?? 0
-    let inserted = false
-    for (const [index, node] of this.nodeArray.entries()) {
-      if (node.priority > priority) {
-        this.nodeArray.splice(index, 0, { data, priority })
-        inserted = true
-        break
-      }
+    if (this.head.full()) {
+      this.head = this.head.next = new FixedPriorityQueue(this.bucketSize)
     }
-    if (!inserted) {
-      this.nodeArray.push({ data, priority })
+    this.head.enqueue(data, priority)
+    const size = this.size
+    if (size > this.maxSize) {
+      this.maxSize = size
     }
-    return this.incrementSize()
+    return size
   }
 
   /**
    * Dequeue data from the priority queue.
    *
+   * @param bucket - The prioritized bucket to dequeue from.
    * @returns The dequeued data or `undefined` if the priority queue is empty.
    */
-  public dequeue (): T | undefined {
-    if (this.size > 0) {
-      --this.size
+  public dequeue (bucket?: number): T | undefined {
+    let tail: PriorityQueueNode<T> | undefined = this.tail
+    if (bucket != null) {
+      let currentBucket = 0
+      while (tail != null) {
+        ++currentBucket
+        if (currentBucket === bucket) {
+          break
+        }
+        tail = tail.next
+      }
     }
-    return this.nodeArray.shift()?.data
-  }
-
-  /**
-   * Peeks at the first data.
-   * @returns The first data or `undefined` if the priority queue is empty.
-   */
-  public peekFirst (): T | undefined {
-    return this.nodeArray[0]?.data
-  }
-
-  /**
-   * Peeks at the last data.
-   * @returns The last data or `undefined` if the priority queue is empty.
-   */
-  public peekLast (): T | undefined {
-    return this.nodeArray[this.nodeArray.length - 1]?.data
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    const data = tail!.dequeue()
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    if (tail!.empty() && tail!.next != null) {
+      if (tail === this.tail) {
+        this.tail = tail.next
+      } else {
+        let node: PriorityQueueNode<T> | undefined = this.tail
+        while (node != null) {
+          if (node.next === tail) {
+            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+            node.next = tail!.next
+            break
+          }
+          node = node.next
+        }
+      }
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      delete tail!.next
+    }
+    return data
   }
 
   /**
    * Clears the priority queue.
    */
   public clear (): void {
-    this.nodeArray = []
-    this.size = 0
+    this.head = this.tail = new FixedPriorityQueue(this.bucketSize)
     this.maxSize = 0
   }
 
   /**
-   * Increments the size of the deque.
+   * Returns an iterator for the priority queue.
    *
-   * @returns The new size of the deque.
+   * @returns An iterator for the priority queue.
+   * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols
    */
-  private incrementSize (): number {
-    ++this.size
-    if (this.size > this.maxSize) {
-      this.maxSize = this.size
+  public [Symbol.iterator] (): Iterator<T> {
+    let i = 0
+    let node = this.tail
+    return {
+      next: () => {
+        if (node.empty() || (i >= node.capacity && node.next == null)) {
+          return {
+            value: undefined,
+            done: true
+          }
+        }
+        const value = node.dequeue() as T
+        ++i
+        if (i === node.capacity && node.next != null) {
+          node = node.next
+          i = 0
+        }
+        return {
+          value,
+          done: false
+        }
+      }
     }
-    return this.size
   }
 }