UNPKG

@itrocks/sorted-array

Version:

Array subclasses that remain continuously sorted on insert() or push() calls, featuring optimized includes() and indexOf()

152 lines (151 loc) 4.42 kB
class ASortedArray extends Array { distinct = false; constructor(...items) { super(...items); Object.defineProperty(this, 'distinct', { configurable: true, enumerable: false, value: this.distinct, writable: true }); } push(...items) { for (const item of items) this.insert(item); return this.length; } } export class SortedArray extends ASortedArray { #leftOf(element) { let left = 0; let middle; let right = this.length; while (left < right) { middle = ((left + right) >>> 1); if (this[middle] < element) left = middle + 1; else right = middle; } return left; } includes(searchElement) { const left = this.#leftOf(searchElement); return this[left] === searchElement; } indexOf(searchElement) { const left = this.#leftOf(searchElement); return (this[left] === searchElement) ? left : -1; } insert(item) { const left = this.#leftOf(item); if (this.distinct && (this[left] === item)) { return; } this.splice(left, 0, item); return left; } isSorted() { const length = this.length; for (let index = 1; index < length; index++) { if (this[index] < this[index - 1]) return false; } return true; } } export class SortedArrayBy extends ASortedArray { compareBy; constructor(compareBy, ...items) { super(...items); this.compareBy = compareBy; } #leftOf(element) { const compareKey = this.compareBy; let left = 0; let middle; let right = this.length; while (left < right) { middle = ((left + right) >>> 1); if (this[middle][compareKey] < element[compareKey]) left = middle + 1; else right = middle; } return left; } includes(searchElement) { const left = this.#leftOf(searchElement); return this[left] === searchElement; } indexOf(searchElement) { const left = this.#leftOf(searchElement); return (this[left] === searchElement) ? left : -1; } insert(item) { const left = this.#leftOf(item); if (this.distinct && (this[left] === item)) { return; } this.splice(left, 0, item); return left; } isSorted() { const compareBy = this.compareBy; const length = this.length; for (let index = 1; index < length; index++) { if (this[index][compareBy] < this[index - 1][compareBy]) return false; } return true; } sort(compareFn) { return super.sort(compareFn ?? ((a, b) => { if (a[this.compareBy] > b[this.compareBy]) return 1; return (a[this.compareBy] < b[this.compareBy]) ? -1 : 0; })); } } export class SortedArrayCompareFn extends ASortedArray { compareFn; constructor(compareFn, ...items) { super(...items); this.compareFn = compareFn; } #leftOf(element) { let left = 0; let middle; let right = this.length; while (left < right) { middle = ((left + right) >>> 1); if (this.compareFn(this[middle], element) < 0) left = middle + 1; else right = middle; } return left; } includes(searchElement) { const left = this.#leftOf(searchElement); return !this.compareFn(this[left], searchElement); } indexOf(searchElement) { const left = this.#leftOf(searchElement); return this.compareFn(this[left], searchElement) ? -1 : left; } insert(insert) { const left = this.#leftOf(insert); if (this.distinct && (this[left] === insert)) { return; } this.splice(left, 0, insert); return left; } isSorted() { const length = this.length; for (let index = 1; index < length; index++) { if (this.compareFn(this[index], this[index - 1]) < 0) return false; } return true; } sort(compareFn) { return super.sort(compareFn ?? this.compareFn); } }