@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
JavaScript
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);
}
}