Skip to content

Commit eae83dd

Browse files
committed
perf(IntervalSet): leverage binary searching
1 parent 8392678 commit eae83dd

1 file changed

Lines changed: 50 additions & 17 deletions

File tree

lib/IntervalSet.ts

Lines changed: 50 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,42 +15,75 @@ export default class IntervalSet {
1515
return this.#intervals[Symbol.iterator]();
1616
}
1717

18-
has(interval: Interval) {
19-
return this.#intervals.some((existing) => existing.contains(interval));
18+
#findFirstWhere(predicate: (interval: Interval) => boolean) {
19+
let left = 0, right = this.#intervals.length;
20+
while (left < right) {
21+
const middle = Math.floor((left + right) / 2);
22+
if (predicate(this.#intervals[middle])) {
23+
right = middle;
24+
} else {
25+
left = middle + 1;
26+
}
27+
}
28+
return left;
2029
}
2130

22-
#compareIntervalsTo(other: Interval) {
23-
const { [-1]: lower = [], [0]: contiguous = [], [1]: upper = [] } = Object
24-
.groupBy(this.#intervals, (interval) => Math.sign(other.gapTo(interval)));
25-
return { lower, contiguous, upper };
31+
has(interval: Interval) {
32+
let left = 0, right = this.#intervals.length - 1;
33+
while (left <= right) {
34+
const middle = Math.floor((left + right) / 2);
35+
const existing = this.#intervals[middle];
36+
if (existing.upper < interval.lower) {
37+
left = middle + 1;
38+
} else if (existing.lower > interval.upper) {
39+
right = middle - 1;
40+
} else if (existing.contains(interval)) {
41+
return true;
42+
} else if (existing.upper < interval.upper) {
43+
left = middle + 1;
44+
} else {
45+
right = middle - 1;
46+
}
47+
}
48+
return false;
2649
}
2750

2851
add(interval: Interval) {
29-
const { lower, contiguous, upper } = this.#compareIntervalsTo(interval);
30-
if (contiguous.length === 0) {
31-
this.#intervals = [...lower, interval, ...upper];
52+
const start = this.#findFirstWhere((i) => i.upper >= interval.lower - 1);
53+
let end = start;
54+
while (
55+
end < this.#intervals.length &&
56+
this.#intervals[end].lower <= interval.upper + 1
57+
) end++;
58+
if (start === end) {
59+
this.#intervals.splice(start, 0, interval);
3260
return;
3361
}
3462
const mergedInterval = new Interval(
35-
Math.min(interval.lower, contiguous[0].lower),
36-
Math.max(interval.upper, contiguous[contiguous.length - 1].upper),
63+
Math.min(interval.lower, this.#intervals[start].lower),
64+
Math.max(interval.upper, this.#intervals[end - 1].upper),
3765
);
38-
this.#intervals = [...lower, mergedInterval, ...upper];
66+
this.#intervals.splice(start, end - start, mergedInterval);
3967
}
4068

4169
delete(interval: Interval) {
42-
const { lower, contiguous, upper } = this.#compareIntervalsTo(interval);
43-
if (contiguous.length === 0) return;
70+
const start = this.#findFirstWhere((i) => i.upper >= interval.lower);
71+
let end = start;
72+
while (
73+
end < this.#intervals.length &&
74+
this.#intervals[end].lower <= interval.upper
75+
) end++;
76+
if (start === end) return;
4477
const newIntervals: Interval[] = [];
45-
const first = contiguous[0];
78+
const first = this.#intervals[start];
4679
if (first.lower < interval.lower) {
4780
newIntervals.push(new Interval(first.lower, interval.lower - 1));
4881
}
49-
const last = contiguous[contiguous.length - 1];
82+
const last = this.#intervals[end - 1];
5083
if (last.upper > interval.upper) {
5184
newIntervals.push(new Interval(interval.upper + 1, last.upper));
5285
}
53-
this.#intervals = [...lower, ...newIntervals, ...upper];
86+
this.#intervals.splice(start, end - start, ...newIntervals);
5487
}
5588

5689
difference(other: IntervalSet) {

0 commit comments

Comments
 (0)