index.ts 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. /**
  2. * Zero-dependency circular deque for queues that retain entries across asynchronous work.
  3. * @module @deepseek-ai/dsh-deque
  4. */
  5. const MIN_CAPACITY = 16
  6. /**
  7. * A circular deque with amortized constant-time insertion and removal.
  8. * Removed entries are cleared immediately, and sparse storage shrinks after
  9. * the live entry count reaches one quarter of its capacity.
  10. */
  11. export class Deque<T> {
  12. private buffer = new Array<T | undefined>(MIN_CAPACITY)
  13. private head = 0
  14. private count = 0
  15. /** Number of entries available to remove. */
  16. get size(): number {
  17. return this.count
  18. }
  19. /**
  20. * Append one entry after the current tail.
  21. * @param value - entry to append.
  22. */
  23. pushBack(value: T): void {
  24. this.ensureCapacity()
  25. const tail = this.head + this.count
  26. this.buffer[tail < this.buffer.length ? tail : tail - this.buffer.length] = value
  27. this.count += 1
  28. }
  29. /**
  30. * Insert one entry before the current head.
  31. * @param value - entry to prepend.
  32. */
  33. pushFront(value: T): void {
  34. this.ensureCapacity()
  35. this.head = this.head === 0 ? this.buffer.length - 1 : this.head - 1
  36. this.buffer[this.head] = value
  37. this.count += 1
  38. }
  39. /**
  40. * Remove the current head entry and clear its retained reference.
  41. * Callers whose element type includes `undefined` use {@link size} to
  42. * distinguish an empty deque from an `undefined` entry.
  43. * @returns the removed entry, or `undefined` when the deque is empty.
  44. */
  45. popFront(): T | undefined {
  46. if (this.count === 0) return undefined
  47. const value = this.buffer[this.head] as T
  48. this.buffer[this.head] = undefined
  49. this.head += 1
  50. if (this.head === this.buffer.length) this.head = 0
  51. this.count -= 1
  52. this.compact()
  53. return value
  54. }
  55. /** Drop every entry and release the current backing storage. */
  56. clear(): void {
  57. this.buffer = new Array<T | undefined>(MIN_CAPACITY)
  58. this.head = 0
  59. this.count = 0
  60. }
  61. private ensureCapacity(): void {
  62. if (this.count < this.buffer.length) return
  63. this.resize(this.buffer.length * 2)
  64. }
  65. private compact(): void {
  66. if (this.count === 0) {
  67. this.head = 0
  68. return
  69. }
  70. if (this.buffer.length > MIN_CAPACITY && this.count <= this.buffer.length / 4) {
  71. this.resize(Math.max(MIN_CAPACITY, this.buffer.length / 2))
  72. }
  73. }
  74. private resize(capacity: number): void {
  75. const next = new Array<T | undefined>(capacity)
  76. let source = this.head
  77. for (let index = 0; index < this.count; index += 1) {
  78. next[index] = this.buffer[source]
  79. source += 1
  80. if (source === this.buffer.length) source = 0
  81. }
  82. this.buffer = next
  83. this.head = 0
  84. }
  85. }