Skip to content

Commit

Permalink
Add a Fenwick tree.
Browse files Browse the repository at this point in the history
  • Loading branch information
renggli committed Jan 7, 2024
1 parent babd5b3 commit da86d60
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 0 deletions.
1 change: 1 addition & 0 deletions lib/collection.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ library collection;
export 'src/collection/bimap.dart'
show BiMap, BiMapOnIterableExtension, BiMapOnMapExtension;
export 'src/collection/bitlist.dart' show BitList, BitListExtension;
export 'src/collection/fenwick.dart' show FenwickTree;
export 'src/collection/iterable/chunked.dart' show ChunkedIterableExtension;
export 'src/collection/iterable/combinations.dart'
show CombinationsIterableExtension;
Expand Down
102 changes: 102 additions & 0 deletions lib/src/collection/fenwick.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/// A fenwick tree (or binary indexed tree) is a data structure that can
/// efficiently update and calculate sums in an array of values.
///
/// See https://en.wikipedia.org/wiki/Fenwick_tree.
class FenwickTree with Iterable<int> {
/// Constructs a Fenwick tree with the given [length].
FenwickTree(int length) : this._(List.filled(length, 0, growable: false));

/// Constructs a Fenwick tree from the values of [iterable] in _O(n)_.
factory FenwickTree.of(Iterable<int> iterable) {
if (iterable is FenwickTree) {
return FenwickTree._(iterable._tree.toList(growable: false));
}
final list = iterable.toList(growable: false);
for (var i = 1; i < list.length; i++) {
final j = i + (i & -i);
if (j < list.length) list[j] += list[i];
}
return FenwickTree._(list);
}

/// Internal constructor of a Fenwick tree.
FenwickTree._(this._tree);

final List<int> _tree;

@override
int get length => _tree.length;

@override
bool get isEmpty => _tree.isEmpty;

@override
Iterator<int> get iterator => _FenwickTreeIterator(this);

/// Converts the tree to a list in _O(n)_.
@override
List<int> toList({bool growable = true}) {
final result = _tree.toList(growable: growable);
for (var i = _tree.length - 1; i > 0; i--) {
final j = i + (i & -i);
if (j < _tree.length) result[j] -= result[i];
}
return result;
}

/// Returns the n-th element of this tree in _O(log n)_.
int operator [](int index) => range(index, index + 1);

/// Updates the n-th element of this tree in _O(log n)_.
void operator []=(int index, int value) => update(index, value - this[index]);

/// Computes the sum of all values up to [index] (exclusive) in _O(log n)_.
int prefix(int index) {
RangeError.checkValueInInterval(index, 0, _tree.length, 'index');
if (index == 0) return 0;
var value = _tree[0];
for (--index; index > 0; index -= index & -index) {
value += _tree[index];
}
return value;
}

/// Computes the sum of all values between [start] and [end] (exclusive)
/// in _O(log n)_.
int range(int start, int end) => prefix(end) - prefix(start);

/// Increments the element in the tree at [index] by [value] in _O(log n)_.
void update(int index, int value) {
if (index == 0) {
_tree[0] += value;
return;
}
while (index < _tree.length) {
_tree[index] += value;
index += index & -index;
}
}
}

final class _FenwickTreeIterator implements Iterator<int> {
_FenwickTreeIterator(this._tree);

final FenwickTree _tree;

int _index = 0;
int _previous = 0;

@override
late int current;

@override
bool moveNext() {
if (_index < _tree.length) {
final next = _tree.prefix(++_index);
current = next - _previous;
_previous = next;
return true;
}
return false;
}
}
77 changes: 77 additions & 0 deletions test/collection_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,83 @@ void main() {
});
}
});
group('fenwick', () {
for (final (:name, :list) in [
(name: 'empty', list: <int>[]),
(name: 'single', list: [42]),
(name: 'full', list: [-6, 11, 3, -20, 16, -5, -10, -19, -7, 8, 7, 4]),
]) {
group(name, () {
final tree = FenwickTree.of(list);
test('length', () {
expect(tree.isEmpty, list.isEmpty);
expect(tree.isNotEmpty, list.isNotEmpty);
expect(tree.length, list.length);
});
test('iterator', () {
expect(List.of(tree), list);
});
test('toList', () {
expect(tree.toList(), list);
});
test('read', () {
for (var i = 0; i < list.length; i++) {
expect(tree[i], list[i]);
}
expect(() => tree[-1], throwsRangeError);
expect(() => tree[list.length], throwsRangeError);
});
test('write', () {
final copy = FenwickTree.of(tree);
for (var i = 0; i < list.length; i++) {
copy[i] = i;
}
expect(tree, list);
expect(copy, 0.to(list.length));
expect(() => tree[-1] = 0, throwsRangeError);
expect(() => tree[list.length] = 0, throwsRangeError);
});
if (list.isNotEmpty) {
test('prefix', () {
for (var i = 0; i <= list.length; i++) {
expect(
tree.prefix(i), list.getRange(0, i).fold(0, (a, b) => a + b),
reason: '$i');
}
});
test('range', () {
for (var i = 0; i <= list.length; i++) {
for (var j = i; j <= list.length; j++) {
expect(tree.range(i, j),
list.getRange(i, j).fold(0, (a, b) => a + b),
reason: '$i..$j');
}
}
});
test('update', () {
final copy = FenwickTree.of(tree);
for (var i = 0; i < list.length; i++) {
copy.update(i, 1);
}
expect(copy, list.map((each) => each + 1));
});
}
});
}
test('stress', () {
final random = Random(572315);
for (var i = 0; i < 10; i++) {
final list = List.generate(
random.nextInt(10000), (index) => random.nextInt(1000));
final tree = FenwickTree.of(list);
expect(tree.toList(), list);
expect(List.of(tree), list);
for (var i = 0; i < list.length; i++) {
expect(tree[i], list[i]);
}
}
});
});
group('iterable', () {
group('chunked', () {
test('empty', () {
Expand Down

0 comments on commit da86d60

Please sign in to comment.