Skip to content

Commit

Permalink
perf: improve speed of FilteredMapView.forEach
Browse files Browse the repository at this point in the history
  • Loading branch information
rbellens committed Feb 28, 2024
1 parent 14dad5b commit 20c5f0c
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 40 deletions.
46 changes: 31 additions & 15 deletions lib/src/filteredmap.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,14 @@ class _FilteredMap<K extends Comparable, V> extends _SortedMap<K, V>
final Filter<K, V> filter;

_FilteredMap._(Filter<K, V> filter,
TreeSet<_MapEntryWithIndex<K, V>>? sortedEntries, TreeMap<K, V>? map)
AvlTreeSet<_MapEntryWithIndex<K, V>>? sortedEntries, TreeMap<K, V>? map)
: filter = filter,
super._(filter.ordering, sortedEntries, map);

@override
FilteredMap<K, V> clone() => _FilteredMap<K, V>._(
filter,
TreeSet(comparator: _SortedMap._compare)..addAll(_sortedEntries),
AvlTreeSet(comparator: _SortedMap._compare)..addAll(_sortedEntries),
TreeMap.from(_map));

@override
Expand All @@ -69,13 +69,15 @@ class _FilteredMap<K extends Comparable, V> extends _SortedMap<K, V>
/// A filtered view on a [SortedMap] or [FilteredMap].
class FilteredMapView<K extends Comparable, V> extends MapBase<K, V>
with SortedMap<K, V>, FilteredMap<K, V>, UnmodifiableSortedMap<K, V> {
final SortedMap<K, V> _baseMap;
final _SortedMap<K, V> _baseMap;

@override
final Filter<K, V> filter;



/// Creates a FilteredMapView from a SortedMap.
FilteredMapView(this._baseMap,
FilteredMapView._(this._baseMap,
{required Pair start,
required Pair end,
int? limit,
Expand Down Expand Up @@ -119,21 +121,26 @@ class FilteredMapView<K extends Comparable, V> extends MapBase<K, V>
_entryForKey(_baseMap.lastKeyBefore(key, orElse: orElse))!.key;

@override
Iterable<K> get keys => _baseMap.subkeys(
start: filter.validInterval.start,
end: filter.validInterval.end,
limit: filter.limit,
reversed: filter.reversed);
Iterable<K> get keys => entries.map((e) => e.key);

@override
Iterable<V> get values => keys.map((k) => _baseMap[k]!);
Iterable<V> get values => entries.map((e) => e.value);

MapEntry<Object?, Iterable<MapEntry<K, V>>>? _entriesCache;
@override
Iterable<MapEntry<K, V>> get entries => _baseMap.subentries(
start: filter.validInterval.start,
end: filter.validInterval.end,
limit: filter.limit,
reversed: filter.reversed);
Iterable<MapEntry<K, V>> get entries {
if (_entriesCache == null ||
_entriesCache!.key != _baseMap._sortedEntries) {
_entriesCache = MapEntry(
_baseMap._sortedEntries,
_baseMap.subentries(
start: filter.validInterval.start,
end: filter.validInterval.end,
limit: filter.limit,
reversed: filter.reversed));
}
return _entriesCache!.value;
}

/// Returns true when the [completeInterval] is within the complete interval
/// of the base map.
Expand All @@ -160,4 +167,13 @@ class FilteredMapView<K extends Comparable, V> extends MapBase<K, V>
bool reversed = false}) {
throw UnimplementedError();
}

@override
FilteredMapView<K, V> filteredMapView(
{required Pair start,
required Pair end,
int? limit,
bool reversed = false}) {
throw UnimplementedError();
}
}
40 changes: 23 additions & 17 deletions lib/src/sortedmap.dart
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,10 @@ abstract class SortedMap<K extends Comparable, V> implements Map<K, V> {

/// Creates a filtered view of this map.
FilteredMapView<K, V> filteredMapView(
{required Pair start,
required Pair end,
int? limit,
bool reversed = false}) =>
FilteredMapView(this,
start: start, end: end, limit: limit, reversed: reversed);
{required Pair start,
required Pair end,
int? limit,
bool reversed = false});

/// Creates a filtered map based on this map.
FilteredMap<K, V> filteredMap(
Expand All @@ -130,6 +128,13 @@ abstract class SortedMap<K extends Comparable, V> implements Map<K, V> {

_MapEntryWithIndex<K, V>? _entryForKey(K? key) =>
containsKey(key) ? ordering.mapEntry(key!, this[key] as V) : null;

@override
void forEach(void Function(K key, V value) action) {
for (var e in entries) {
action(e.key, e.value);
}
}
}

class _MapEntryWithIndex<K extends Comparable, V> implements MapEntry<K, V> {
Expand Down Expand Up @@ -170,15 +175,15 @@ class _SortedMap<K extends Comparable, V> extends MapBase<K, V>
@override
final Ordering ordering;

final TreeSet<_MapEntryWithIndex<K, V>> _sortedEntries;
final AvlTreeSet<_MapEntryWithIndex<K, V>> _sortedEntries;
final TreeMap<K, V> _map;

static int _compare(_MapEntryWithIndex a, _MapEntryWithIndex b) =>
Comparable.compare(a.index, b.index);

_SortedMap._(this.ordering, TreeSet<_MapEntryWithIndex<K, V>>? sortedPairs,
_SortedMap._(this.ordering, AvlTreeSet<_MapEntryWithIndex<K, V>>? sortedPairs,
TreeMap<K, V>? map)
: _sortedEntries = sortedPairs ?? TreeSet(comparator: _compare),
: _sortedEntries = sortedPairs ?? AvlTreeSet(comparator: _compare),
_map = map ?? TreeMap();

@override
Expand All @@ -190,12 +195,20 @@ class _SortedMap<K extends Comparable, V> extends MapBase<K, V>
@override
SortedMap<K, V> clone() => _SortedMap<K, V>._(
ordering,
TreeSet(comparator: _compare)..addAll(_sortedEntries),
AvlTreeSet(comparator: _compare)..addAll(_sortedEntries),
TreeMap<K, V>.from(_map));

@override
V? operator [](Object? key) => _map[key as K];

@override
FilteredMapView<K, V> filteredMapView(
{required Pair start,
required Pair end,
int? limit,
bool reversed = false}) =>
FilteredMapView._(this,
start: start, end: end, limit: limit, reversed: reversed);
@override
void addAll(Map<K, V> other) {
if (other is _SortedMap<K, V> &&
Expand All @@ -215,13 +228,6 @@ class _SortedMap<K extends Comparable, V> extends MapBase<K, V>
_addEntry(key, value);
}

@override
void forEach(void Function(K key, V value) action) {
for (var e in entries) {
action(e.key, e.value);
}
}

@override
bool get isEmpty => _map.isEmpty;

Expand Down
4 changes: 4 additions & 0 deletions lib/src/treeset.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import 'dart:collection';
import 'dart:math';

mixin HasCacheState {
Object get cacheState;
}

abstract class TreeSet<V> implements Set<V> {
/// Create a [TreeSet] with an ordering defined by [comparator] or the
/// default `(a, b) => a.compareTo(b)`.
Expand Down
26 changes: 18 additions & 8 deletions test/benchmarks_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,15 @@ Future<void> main() async {
map = SortedMap(const Ordering.byValue()).filteredMap(
start: Pair.min(), end: Pair.max(), limit: n, reversed: true);
map.addAll(data);
view = map.filteredMapView(
start: Pair.min(), end: Pair.max(), limit: n ~/ 2, reversed: true);
});
benchmark('of FilteredMap', () async {
map.completeInterval;
});

setUpEach(() {
view = map.filteredMapView(
start: Pair.min(), end: Pair.max(), limit: n ~/ 2, reversed: true);
});
benchmark('of FilteredMapView', () async {
view.completeInterval;
});
Expand All @@ -46,12 +49,14 @@ Future<void> main() async {
map = SortedMap(const Ordering.byValue()).filteredMap(
start: Pair.min(), end: Pair.max(), limit: n, reversed: true);
map.addAll(data);
view = map.filteredMapView(
start: Pair.min(), end: Pair.max(), limit: n ~/ 2, reversed: true);
});
benchmark('of FilteredMap', () async {
map.length;
});
setUpEach(() {
view = map.filteredMapView(
start: Pair.min(), end: Pair.max(), limit: n ~/ 2, reversed: true);
});
benchmark('of FilteredMapView', () async {
view.length;
});
Expand All @@ -72,12 +77,14 @@ Future<void> main() async {
map = SortedMap(const Ordering.byValue()).filteredMap(
start: Pair.min(), end: Pair.max(), limit: n, reversed: true);
map.addAll(data);
view = map.filteredMapView(
start: Pair.min(), end: Pair.max(), limit: n ~/ 2, reversed: true);
});
benchmark('of FilteredMap', () async {
map.forEach((key, value) {});
});
setUpEach(() {
view = map.filteredMapView(
start: Pair.min(), end: Pair.max(), limit: n ~/ 2, reversed: true);
});
benchmark('of FilteredMapView', () async {
view.forEach((key, value) {});
});
Expand All @@ -93,10 +100,13 @@ Future<void> main() async {

group('addAll', () {
var map = SortedMap.from(data, const Ordering.byValue());
var view = map.filteredMapView(
start: Pair.min(), end: Pair.max(), limit: n ~/ 2, reversed: true);
var values = data.values.toList();

late FilteredMapView view;
setUpEach(() {
view = map.filteredMapView(
start: Pair.min(), end: Pair.max(), limit: n ~/ 2, reversed: true);
});
benchmark('to empty SortedMap', () async {
SortedMap(const Ordering.byValue()).addAll(data);
});
Expand Down

0 comments on commit 20c5f0c

Please sign in to comment.