blob: f3c0ea5851c510701c3c1b0ecff4e8b9210aadbf [file] [log] [blame]
part of lists;
/// Sparse list based on the grouped range lists.
// TODO: sublist
// TODO: toList
class SparseList<E> extends Object with ListMixin<E> {
final E defaultValue;
bool Function(E, E) _equals;
bool _fixedLength = false;
bool _frozen = false;
final List<GroupedRangeList<E>> _groups = <GroupedRangeList<E>>[];
int _length;
SparseList(
{this.defaultValue, bool Function(E e1, E e2) equals, int length}) {
if (length == null) {
length = 0;
} else {
if (length < 0) {
throw ArgumentError('length: $length');
}
_fixedLength = true;
}
_length = length;
if (equals != null) {
_equals = equals;
} else {
_equals = (E e1, E e2) => e1 == e2;
}
}
/// Returns the 'end' value from the last group (if available); otherwise null.
int get end {
final length = _groups.length;
if (_groups.isEmpty) {
return null;
}
return _groups[length - 1].end;
}
/// Returns true if the list is frozen; otherwise false.
bool get frozen {
return _frozen;
}
/// Returns the number of groups.
int get groupCount => _groups.length;
/// Returns a read-only list of the groups.
List<GroupedRangeList<E>> get groups =>
UnmodifiableListView<GroupedRangeList<E>>(_groups);
/// Returns the length of list.
@override
int get length => _length;
/// Sets the length of list.
@override
set length(int length) {
if (length == null) {
throw ArgumentError('length: $length');
}
if (_fixedLength) {
throw StateError('Unable to set the length of a fixed list.');
}
if (frozen) {
_errorModificationNotAllowed();
}
if (length < 0) {
throw RangeError(length);
}
if (_length == length) {
return;
}
if (_length < length) {
_length = length;
return;
}
if (length == 0) {
_groups.length = 0;
_length = 0;
return;
}
_resetValues(RangeList(length, _length - 1));
_length = length;
}
/// Returns the 'start' value from the first group (if available); otherwise
/// null.
int get start {
if (_groups.isEmpty) {
return null;
}
return _groups[0].start;
}
@override
E operator [](int index) {
if (index == null) {
throw ArgumentError('index: $index');
}
if (index < 0 || index >= _length) {
throw RangeError(index);
}
var right = _groups.length;
if (right == 0) {
return defaultValue;
}
if (right == 1) {
final group = _groups[0];
if (index <= group.end && index >= group.start) {
return group.key;
} else {
return defaultValue;
}
}
var left = 0;
final key = index;
int middle;
final value = defaultValue;
while (left < right) {
middle = (left + right) >> 1;
final group = _groups[middle];
if (group.end < key) {
left = middle + 1;
} else {
if (key >= group.start) {
return group.key;
}
right = middle;
}
}
return value;
}
@override
void operator []=(int index, E value) {
if (index == null) {
throw ArgumentError('index: $index');
}
if (frozen) {
_errorModificationNotAllowed();
}
if (index < 0 || index >= _length) {
throw RangeError(index);
}
if (_equals(value, defaultValue)) {
_resetValues(RangeList(index, index));
} else {
setGroup(GroupedRangeList(index, index, value));
}
}
/// Sets the values ​​in accordance with the specified [group] and increases
/// (if required) the length up to (range.end + 1).
void addGroup(GroupedRangeList<E> group) {
if (group == null) {
throw ArgumentError('group: $group');
}
if (_fixedLength) {
throw StateError('Unable to add the group into fixed list.');
}
if (frozen) {
_errorModificationNotAllowed();
}
if (group.start < 0) {
throw RangeError(group.start);
}
_setGroup(group);
if (_length < group.end + 1) {
_length = group.end + 1;
}
}
/// Makes the list unmodifiable.
void freeze() {
_frozen = true;
}
/// Returns all groups that intersects with the specified [range] which being
/// expanded (with default value) or shrunk to specified [range].
List<GroupedRangeList<E>> getAlignedGroups(RangeList range) {
if (range == null) {
throw ArgumentError('range: $range');
}
final groups = getGroups(range).toList();
final length = groups.length;
if (length == 0) {
return [GroupedRangeList<E>(range.start, range.end, defaultValue)];
}
final first = groups.first;
if (range.start > first.start) {
groups[0] = first.intersection(range);
} else if (range.start < first.start) {
final insertion =
GroupedRangeList<E>(range.start, first.start - 1, defaultValue);
groups.insert(0, insertion);
}
final last = groups.last;
if (range.end > last.end) {
final addition =
GroupedRangeList<E>(last.end + 1, range.end, defaultValue);
groups.add(addition);
} else if (range.end < last.end) {
groups[groups.length - 1] = last.intersection(range);
}
return groups;
}
/// Returns all space as groups (including synthetic groups with default
/// values from not filled space) that intersects with the specified [range]
/// which being expanded (with default value) or shrunk to specified [range].
List<GroupedRangeList<E>> getAllSpace(RangeList range) {
if (range == null) {
throw ArgumentError('range: $range');
}
final groups = <GroupedRangeList<E>>[];
GroupedRangeList<E> previous;
for (final group in getAlignedGroups(range)) {
if (previous != null) {
final start = previous.end + 1;
final delta = group.start - start;
if (delta > 0) {
// Adds the synthetic group
groups.add(GroupedRangeList<E>(start, group.start - 1, defaultValue));
}
}
groups.add(group);
previous = group;
}
return groups;
}
/// Returns all groups that intersects with the specified [range].
Iterable<GroupedRangeList<E>> getGroups([RangeList range]) {
if (range == null) {
return _groups.getRange(0, _groups.length);
}
final length = _groups.length;
final left = _findNearestIndex(0, length, range.start);
var firstIndex = -1;
var lastIndex = -1;
for (var i = left; i < length; i++) {
if (_groups[i].intersect(range)) {
if (firstIndex == -1) {
firstIndex = i;
lastIndex = i;
} else {
lastIndex = i;
}
} else if (firstIndex != -1) {
break;
}
}
if (firstIndex == -1) {
return <GroupedRangeList<E>>[];
}
return _groups.getRange(firstIndex, lastIndex + 1);
}
/// Returns the indexes of the elements with a value.
Iterable<int> getIndexes() {
Iterable<int> generator() sync* {
for (final group in groups) {
final end = group.end;
for (var i = group.start; i <= end; i++) {
yield i;
}
}
}
return generator();
}
/// Removes the values in the specified range and decreases (if possible) the
/// length down to (range.start).
void removeValues(RangeList range) {
if (range == null) {
throw ArgumentError('range: $range');
}
if (_fixedLength) {
throw StateError('Unable to remove the values from a fixed list.');
}
if (frozen) {
_errorModificationNotAllowed();
}
if (range.start < 0) {
throw RangeError(range.start);
}
_resetValues(range);
if (_groups.isEmpty) {
if (_length > range.start) {
_length = range.start;
}
} else {
var length = _groups.last.end + 1;
if (length > range.start && length < range.end) {
length = range.start;
}
_length = length;
}
}
/// Resets the values in the specified [range] to the default values.
void resetValues(RangeList range) {
if (range == null) {
throw ArgumentError('range: $range');
}
if (frozen) {
_errorModificationNotAllowed();
}
if (range.start < 0 || range.start >= _length) {
throw RangeError(range.start);
}
if (range.end >= _length) {
throw RangeError(range.end);
}
_resetValues(range);
}
/// Sets the values ​​in accordance with the specified [group].
void setGroup(GroupedRangeList<E> group) {
if (group == null) {
throw ArgumentError('group: $group');
}
if (frozen) {
_errorModificationNotAllowed();
}
if (group.start < 0 || group.start >= _length) {
throw RangeError(group.start);
}
if (group.end >= _length) {
throw RangeError(group.end);
}
_setGroup(group);
}
/// Sets the length of list equal to the last not empty index + 1; otherwise
/// sets the length to 0.
void trim() {
if (_fixedLength) {
throw StateError('Unable to trim a fixed list.');
}
if (frozen) {
_errorModificationNotAllowed();
}
final groupCount = _groups.length;
if (groupCount == 0) {
_length = 0;
} else {
_length = _groups[groupCount - 1].end + 1;
}
}
void _errorModificationNotAllowed() {
throw StateError('Unable to modify the frozen list.');
}
int _findNearestIndex(int left, int right, int key) {
if (right == 0) {
return 0;
}
int middle;
while (left < right) {
middle = (left + right) >> 1;
if (_groups[middle].end < key) {
left = middle + 1;
} else {
right = middle;
}
}
if (left > 0) {
left--;
}
return left;
}
void _resetValues(RangeList range) {
final rangeStart = range.start;
final rangeEnd = range.end;
final length = _groups.length;
final left = _findNearestIndex(0, length, range.start);
var count = 0;
final newGroups = <GroupedRangeList<E>>[];
for (var i = left; i < length; i++) {
final current = _groups[i];
final start = current.start;
if (start > rangeEnd) {
break;
}
final intersect = current.intersect(range);
if (intersect) {
final end = current.end;
final key = current.key;
if (start < rangeStart) {
final newGroup = GroupedRangeList<E>(start, rangeStart - 1, key);
newGroups.add(newGroup);
}
if (end > rangeEnd) {
final newGroup = GroupedRangeList<E>(rangeEnd + 1, end, key);
newGroups.add(newGroup);
}
} else {
newGroups.add(current);
}
count++;
}
_groups.removeRange(left, left + count);
_groups.insertAll(left, newGroups);
}
void _setGroup(GroupedRangeList<E> group) {
final groupKey = group.key;
final length = _groups.length;
if (length == 0) {
_groups.add(group);
return;
}
final groupStart = group.start;
final lastEnd = _groups[length - 1].end;
int left;
if (groupStart == lastEnd + 1) {
left = length - 1;
} else if (groupStart > lastEnd) {
_groups.add(group);
return;
} else {
left = _findNearestIndex(0, length, group.start);
}
final groupEnd = group.end;
var count = 0;
final newGroups = <GroupedRangeList<E>>[];
for (var i = left; i < length; i++) {
final current = _groups[i];
final start = current.start;
if (start > groupEnd + 1) {
break;
}
final end = current.end;
final key = current.key;
final intersect = current.intersect(group);
if (intersect) {
if (start < groupStart) {
if (_equals(key, groupKey)) {
group = GroupedRangeList<E>(start, groupEnd, groupKey);
} else {
final newGroup = GroupedRangeList<E>(start, groupStart - 1, key);
newGroups.add(newGroup);
}
}
if (end > groupEnd) {
if (_equals(key, groupKey)) {
group = GroupedRangeList<E>(groupStart, end, key);
} else {
final newGroup = GroupedRangeList<E>(groupEnd + 1, end, key);
newGroups.add(newGroup);
}
}
} else {
if (_equals(key, groupKey)) {
if (groupStart == end + 1) {
group = GroupedRangeList<E>(start, groupEnd, key);
} else if (start == groupEnd + 1) {
group = GroupedRangeList<E>(groupStart, end, key);
} else {
newGroups.add(current);
}
} else {
newGroups.add(current);
}
}
count++;
}
newGroups.add(group);
newGroups.sort((a, b) => a.start.compareTo(b.start));
_groups.removeRange(left, left + count);
_groups.insertAll(left, newGroups);
}
}