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