0xweb
Version:
Contract package manager and other web3 tools
214 lines (189 loc) • 5.65 kB
text/typescript
import alot from 'alot';
/**
* Defines a RANGE: [FROM_NUMBER, TO_NUMBER | Infinity)
*
* - Walking/Iterating/Visiting through the range marks the visited number as ADD.
*
* - Possible to ADD the number manually, for example for persistence/restore properties
*
* - Check if NUMBER was already visited/iterated
*
*/
export class PackedRanges {
protected ranges: [number, number][] = [];
from: number = 0
to: number
constructor(opts?: {
from?: number
to?: number
ranges?: [ number, number ][]
}) {
if (opts != null) {
this.to = opts.to;
this.from = opts.from ?? opts.ranges?.[0]?.[0] ?? this.from;
this.ranges = opts.ranges ?? [];
}
}
total () {
let min = this.from;
let max = this.to ?? this.getMax();
if (min == null || max == null) {
return Infinity;
}
return max - min;
}
totalAdded () {
let count = 0;
for (let i = 0; i < this.ranges.length; i++) {
let [min, max] = this.ranges[i];
count += max - min + 1;
}
return count;
}
totalLeft () {
return this.total() - this.totalAdded();
}
next(): number {
let min = this.from;
if (this.ranges.length === 0) {
let x = min;
this.ranges.push([x, x]);
return x;
}
let [r0Min, r0Max] = this.ranges[0];
if (min < r0Min) {
this.add(min);
return min;
}
while (this.ranges.length > 1) {
let [r1Min, r1Max] = this.ranges[1];
let next = r0Max + 1;
if (next < r1Min) {
this.ranges[0][1] = next;
return next;
}
if (next === r1Min) {
this.ranges[0][1] = r1Max;
this.ranges.splice(1, 1);
continue;
}
throw new Error(`Unsorted ranges r0Max: ${r0Max}, r1Min: ${r1Min}`);
}
r0Max = this.ranges[0][1] + 1;
if (this.to != null && r0Max >= this.to) {
return null;
}
this.ranges[0][1] = r0Max;
return r0Max;
}
getMax() {
if (this.ranges.length === 0) {
return null;
}
let r = this.ranges[this.ranges.length - 1];
return r[1];
}
includes(nr: number) {
for (let i = 0; i < this.ranges.length; i++) {
let [a, b] = this.ranges[i];
if (a <= nr && nr <= b) {
return true;
}
}
return false;
}
add(nr: number) {
let modified = false;
for (let i = 0; i < this.ranges.length; i++) {
let [a, b] = this.ranges[i];
if (a <= nr && nr <= b) {
return false;
}
if (a - 1 === nr) {
this.ranges[i][0] = nr;
modified = true;
break;
}
if (b + 1 === nr) {
this.ranges[i][1] = nr;
modified = true;
break;
}
}
if (modified === false) {
// insert
let arr = [nr, nr] as [number, number];
let inserted = false;
for (let i = 0; i < this.ranges.length; i++) {
let [a] = this.ranges[i];
if (nr < a) {
this.ranges.splice(i, 0, arr);
inserted = true;
break;
}
}
if (inserted === false) {
this.ranges.push(arr);
}
}
return true;
}
remove (nr: number) {
for (let i = 0; i < this.ranges.length; i++) {
let [min, max] = this.ranges[i];
if (min <= nr && nr <= max) {
if (min === max) {
this.ranges.splice(i, 1);
return true;
}
if (min === nr) {
this.ranges[i][0] = min + 1;
return true;
}
if (max === nr) {
this.ranges[i][1] = max - 1;
return true;
}
let arr1 = [min, nr - 1] as [ number, number];
let arr2 = [nr + 1, max] as [ number, number];
this.ranges.splice(i, 1, arr1, arr2);
return true;
}
}
return false;
}
compact() {
for (let i = 0; i < this.ranges.length; i++) {
let rangeA = this.ranges[i];
for (let j = i + 1; j < this.ranges.length; j++) {
let rangeB = this.ranges[j];
if (Ranges.overlaps(rangeA, rangeB)) {
let [x1, y1] = rangeA;
let [x2, y2] = rangeB;
this.ranges[i] = [Math.min(x1, x2), Math.max(y1, y2)];
this.ranges.splice(j, 1);
j--;
continue;
}
}
}
this.ranges = alot(this.ranges)
.sortBy(([x]) => x)
.toArray();
}
set(ranges: [number, number][]) {
this.ranges = ranges;
this.compact();
}
serialize() {
return JSON.stringify(this.ranges);
}
}
namespace Ranges {
export function overlaps (r1: [number, number], r2: [number, number]) {
let [x1, y1] = r1;
let [x2, y2] = r2;
let d = Math.min(y1, y2) - Math.max(x1, x2);
return d > -1;
}
}