blob: 68c65087e2728ac6725e9c0575d1edeacad01aa5 [file] [log] [blame]
// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:typed_data';
import 'dart:math' show max;
/// Sorted list of disjoint [start, end) intervals
/// (start of each interval is inclusive, end is exclusive).
///
/// Supports prepending intervals in the descending order.
class IntervalList {
static const initialCapacity = 4;
// Sorted list of [start, end) pairs.
Int32List _list;
int _first;
int _length = 0;
factory IntervalList() => IntervalList._(initialCapacity);
IntervalList._(int capacity) : _list = Int32List(capacity), _first = capacity;
bool get isEmpty => _length == 0;
int get length => _length;
int startAt(int index) => _list[_first + (index << 1)];
int endAt(int index) => _list[_first + (index << 1) + 1];
/// Overwrite starting point of the given interval.
void setStartAt(int index, int value) {
assert(value >= 0);
assert(value < endAt(index));
assert(index == 0 || endAt(index - 1) < value);
_setStartAt(index, value);
}
void _setStartAt(int index, int value) {
assert(value >= 0);
_list[_first + (index << 1)] = value;
}
void _setEndAt(int index, int value) {
assert(value >= 0);
_list[_first + (index << 1) + 1] = value;
}
int get start => startAt(0);
int get end => endAt(length - 1);
/// Add interval [start, end) to this list.
///
/// Intervals should be added in the descending order by the end position.
/// Intersecting intervals are not allowed except nested intervals with
/// the same starting position (which are ignored).
void addInterval(int start, int end) {
assert(start >= 0);
assert(end >= 0);
assert(start < end);
if (!isEmpty) {
// Intervals should be added in descending order.
assert(end < endAt(0));
if (start == startAt(0)) {
// Ignore nested interval.
return;
}
// Intersecting intervals are not allowed.
assert(end <= startAt(0));
if (end == startAt(0)) {
// Merge two adjacent intervals.
_setStartAt(0, start);
return;
}
// Add a new disjoint interval.
if (_first == 0) {
_expand(_list.length << 1);
}
}
++_length;
_first -= 2;
_setStartAt(0, start);
_setEndAt(0, end);
}
void _expand(int capacity) {
final newList = Int32List(capacity);
newList.setRange(
newList.length - (_length << 1),
newList.length,
_list,
_first,
);
_list = newList;
_first = newList.length - (_length << 1);
}
bool intersects(IntervalList other) {
if (end <= other.start || other.end <= start) {
return false;
}
return firstIntersection(0, other, 0) >= 0;
}
// Position of the first intersection between intervals index...length-1
// of this list and intervals otherIndex...other.length-1 of [other].
int firstIntersection(int index, IntervalList other, int otherIndex) {
var i = index;
var j = otherIndex;
final len1 = length;
final len2 = other.length;
while (i < len1 && j < len2) {
if (endAt(i) <= other.startAt(j)) {
++i;
} else if (other.endAt(j) <= startAt(i)) {
++j;
} else {
return max(startAt(i), other.startAt(j));
}
}
return -1;
}
/// Append disjoint list of intervals.
void merge(IntervalList other) {
final int otherLen = other.length;
if (_first < (otherLen << 1)) {
_expand((length + otherLen) << 1);
}
// Grow this list by otherLen.
_first = _first - (otherLen << 1);
assert(_first >= 0);
_length = _length + otherLen;
var dst = 0;
void append(int start, int end) {
assert(dst == 0 || endAt(dst - 1) <= start);
if (dst != 0 && endAt(dst - 1) == start) {
_setEndAt(dst - 1, end);
} else {
_setStartAt(dst, start);
_setEndAt(dst, end);
++dst;
}
}
var i = otherLen;
var j = 0;
while (i < length && j < other.length) {
final start1 = startAt(i);
final start2 = other.startAt(j);
if (start1 < start2) {
append(start1, endAt(i));
++i;
} else {
append(start2, other.endAt(j));
++j;
}
}
// Copy tails.
while (i < length) {
append(startAt(i), endAt(i));
++i;
}
while (j < other.length) {
append(other.startAt(j), other.endAt(j));
++j;
}
_length = dst;
}
/// Move out intervals after [pos] into a separate list.
///
/// If [pos] belongs to an interval [start, end), then
/// [start, pos) would belong to this list and [pos, end) is moved out.
IntervalList splitAt(int pos) {
var i = 0;
for (; i < length; ++i) {
if (endAt(i) > pos) {
break;
}
}
if (i == length) {
return IntervalList();
}
int tailLen = length - i;
final tail = IntervalList._(tailLen << 1);
tail._first = 0;
tail._length = tailLen;
tail._list.setRange(0, tailLen << 1, _list, _first + (i << 1));
if (startAt(i) < pos) {
this._length = i + 1;
_setEndAt(i, pos);
tail._setStartAt(0, pos);
} else {
this._length = i;
}
return tail;
}
@override
String toString() {
final buf = StringBuffer();
for (var i = 0; i < length; ++i) {
if (i != 0) buf.write(', ');
buf.write('[${startAt(i)}, ${endAt(i)})');
}
return buf.toString();
}
}