253 lines
6.6 KiB
TypeScript
253 lines
6.6 KiB
TypeScript
const MIN_INITIAL_CAPACITY = 8
|
|
|
|
// System.arraycopy from java
|
|
function arraycopy<T>(
|
|
src: T[],
|
|
srcPos: number,
|
|
dest: T[],
|
|
destPos: number,
|
|
length: number
|
|
) {
|
|
for (let i = 0; i < length; i++) {
|
|
dest[destPos + i] = src[srcPos + i]
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Deque implementation.
|
|
* `undefined` values are not allowed
|
|
*
|
|
* Based on Java implementation
|
|
*/
|
|
export class Deque<T> {
|
|
// another implementation variant would be to use
|
|
// blocks of fixed size instead of a single array
|
|
// to avoid copying stuff around
|
|
protected _elements: (T | undefined)[]
|
|
protected _head = 0
|
|
protected _tail = 0
|
|
protected _capacity: number
|
|
|
|
constructor(
|
|
minCapacity = MIN_INITIAL_CAPACITY,
|
|
readonly maxLength = Infinity
|
|
) {
|
|
let capacity = minCapacity
|
|
if (capacity >= MIN_INITIAL_CAPACITY) {
|
|
// Find the best power of two to hold elements.
|
|
// >= because array can't be full
|
|
capacity |= capacity >>> 1
|
|
capacity |= capacity >>> 2
|
|
capacity |= capacity >>> 4
|
|
capacity |= capacity >>> 8
|
|
capacity |= capacity >>> 16
|
|
capacity += 1
|
|
|
|
if (capacity < 0) {
|
|
// too many
|
|
capacity >>>= 1
|
|
}
|
|
}
|
|
|
|
this._elements = new Array(capacity)
|
|
this._capacity = capacity
|
|
}
|
|
|
|
protected _resize() {
|
|
const p = this._head
|
|
const n = this._capacity
|
|
const r = n - p // number of elements to the right of the head
|
|
|
|
const newCapacity = n << 1
|
|
if (newCapacity < 0) throw new Error('Deque is too big')
|
|
|
|
const arr = new Array(newCapacity)
|
|
|
|
// copy items to the new array
|
|
// copy head till the end of arr
|
|
arraycopy(this._elements, p, arr, 0, r)
|
|
// copy from start to tail
|
|
arraycopy(this._elements, 0, arr, r, p)
|
|
|
|
this._elements = arr
|
|
this._head = 0
|
|
this._tail = n
|
|
this._capacity = newCapacity
|
|
}
|
|
|
|
pushBack(item: T): void {
|
|
if (item === undefined) throw new Error('item can not be undefined')
|
|
|
|
this._elements[this._tail] = item
|
|
|
|
if (
|
|
(this._tail = (this._tail + 1) & (this._capacity - 1)) ===
|
|
this._head
|
|
) {
|
|
this._resize()
|
|
}
|
|
|
|
if (this.length > this.maxLength) {
|
|
this.popFront()
|
|
}
|
|
}
|
|
|
|
pushFront(item: T): void {
|
|
if (item === undefined) throw new Error('item can not be undefined')
|
|
|
|
this._elements[(this._head = (this._head - 1) & (this._capacity - 1))] =
|
|
item
|
|
|
|
if (this._head === this._tail) {
|
|
this._resize()
|
|
}
|
|
|
|
if (this.length > this.maxLength) {
|
|
this.popBack()
|
|
}
|
|
}
|
|
|
|
popFront(): T | undefined {
|
|
const h = this._head
|
|
const res = this._elements[h]
|
|
if (res === undefined) return undefined
|
|
|
|
this._elements[h] = undefined
|
|
this._head = (h + 1) & (this._capacity - 1)
|
|
|
|
return res
|
|
}
|
|
|
|
popBack(): T | undefined {
|
|
const t = (this._tail - 1) & (this._capacity - 1)
|
|
const res = this._elements[t]
|
|
if (res === undefined) return undefined
|
|
|
|
this._elements[t] = undefined
|
|
this._tail = t
|
|
|
|
return res
|
|
}
|
|
|
|
peekFront(): T | undefined {
|
|
return this._elements[this._head]
|
|
}
|
|
|
|
peekBack(): T | undefined {
|
|
return this._elements[(this._tail - 1) & (this._capacity - 1)]
|
|
}
|
|
|
|
get length(): number {
|
|
return (this._tail - this._head) & (this._capacity - 1)
|
|
}
|
|
|
|
toArray(): T[] {
|
|
const sz = this.length
|
|
const arr = new Array(sz)
|
|
|
|
if (this._head < this._tail) {
|
|
// copy as-is, head to tail
|
|
arraycopy(this._elements, this._head, arr, 0, sz)
|
|
} else {
|
|
const headPortion = this._capacity - this._head
|
|
|
|
// copy from head to end
|
|
arraycopy(this._elements, this._head, arr, 0, headPortion)
|
|
// copy from start to tail
|
|
arraycopy(this._elements, 0, arr, headPortion, this._tail)
|
|
}
|
|
|
|
return arr as T[]
|
|
}
|
|
|
|
protected _delete(i: number): void {
|
|
const els = this._elements
|
|
const mask = this._capacity - 1
|
|
const h = this._head
|
|
const t = this._tail
|
|
const front = (i - h) & mask
|
|
const back = (t - i) & mask
|
|
|
|
if (front < back) {
|
|
if (h <= i) {
|
|
arraycopy(els, h, els, h + 1, front)
|
|
} else {
|
|
// wrap
|
|
arraycopy(els, 0, els, 1, i)
|
|
els[0] = els[mask]
|
|
arraycopy(els, h, els, h + 1, mask - h)
|
|
}
|
|
els[h] = undefined
|
|
this._head = (h + 1) & mask
|
|
} else {
|
|
if (i < t) {
|
|
// copy null tail as well
|
|
arraycopy(els, i + 1, els, i, back)
|
|
this._tail = t - 1
|
|
} else {
|
|
// wrap
|
|
arraycopy(els, i + 1, els, i, mask - i)
|
|
els[mask] = els[0]
|
|
arraycopy(els, 1, els, 0, t)
|
|
this._tail = (t - 1) & mask
|
|
}
|
|
}
|
|
}
|
|
|
|
remove(item: T): void {
|
|
const mask = this._capacity - 1
|
|
let i = this._head
|
|
let val: T | undefined
|
|
while ((val = this._elements[i]) !== undefined) {
|
|
if (item === val) {
|
|
this._delete(i)
|
|
return
|
|
}
|
|
i = (i + 1) & mask
|
|
}
|
|
}
|
|
|
|
removeBy(pred: (it: T) => boolean): void {
|
|
const mask = this._capacity - 1
|
|
let i = this._head
|
|
let val: T | undefined
|
|
while ((val = this._elements[i]) !== undefined) {
|
|
if (pred(val)) {
|
|
this._delete(i)
|
|
return
|
|
}
|
|
i = (i + 1) & mask
|
|
}
|
|
}
|
|
|
|
at(idx: number): T | undefined {
|
|
return this._elements[(this._head + idx) & (this._capacity - 1)]
|
|
}
|
|
|
|
*iter(): IterableIterator<T> {
|
|
const sz = this.length
|
|
|
|
if (this._head < this._tail) {
|
|
// head to tail
|
|
for (let i = 0; i < sz; i++) {
|
|
yield this._elements[this._head + i]!
|
|
}
|
|
} else {
|
|
const headPortion = this._capacity - this._head
|
|
|
|
// head to end
|
|
for (let i = 0; i < headPortion; i++) {
|
|
yield this._elements[this._head + i]!
|
|
}
|
|
// start to tail
|
|
for (let i = 0; i < this._tail; i++) {
|
|
yield this._elements[i]!
|
|
}
|
|
}
|
|
}
|
|
|
|
clear(): void {
|
|
this._elements = new Array(this._capacity)
|
|
this._head = this._tail = 0
|
|
}
|
|
}
|