| // 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(); | |
| } | |
| } |