From c210922a560ec76524dbf00ac85ee9ad0f4f7960 Mon Sep 17 00:00:00 2001 From: Luan Nico Date: Sun, 4 May 2025 12:15:55 -0400 Subject: [PATCH 01/10] feat!: Introduce new ReadOnlyOrderedSet super-interface (also makes all sets Queryable) --- benchmark/main.dart | 2 +- example/ordered_set_example.dart | 4 +- lib/comparing_ordered_set.dart | 15 +++- lib/mapping_ordered_set.dart | 12 ++- lib/ordered_set.dart | 38 ++++----- ...t.dart => queryable_ordered_set_impl.dart} | 82 ++++--------------- lib/read_only_ordered_set.dart | 36 ++++++++ test/comparing_ordered_set_test.dart | 24 ++++-- test/comparing_test.dart | 7 +- ...queryable_comparable_ordered_set_test.dart | 9 +- test/queryable_priority_ordered_set_test.dart | 7 +- 11 files changed, 117 insertions(+), 119 deletions(-) rename lib/{queryable_ordered_set.dart => queryable_ordered_set_impl.dart} (55%) create mode 100644 lib/read_only_ordered_set.dart diff --git a/benchmark/main.dart b/benchmark/main.dart index 4c86258..88f4d22 100644 --- a/benchmark/main.dart +++ b/benchmark/main.dart @@ -8,7 +8,7 @@ import 'types.dart'; void main() { OrderedSet comparing(Mapper mapper) { - return OrderedSet.comparing(Comparing.on(mapper)); + return OrderedSet.comparing(compare: Comparing.on(mapper)); } OrderedSet priority(Mapper mapper) { diff --git a/example/ordered_set_example.dart b/example/ordered_set_example.dart index 429c794..30f605d 100644 --- a/example/ordered_set_example.dart +++ b/example/ordered_set_example.dart @@ -9,7 +9,7 @@ void main() { items.add(1); print(items.toList()); // [1, 2] - final a = OrderedSet.comparing((a, b) => a.age - b.age); + final a = OrderedSet.comparing(compare: (a, b) => a.age - b.age); a.add(Person(12, 'Klaus')); a.add(Person(1, 'Sunny')); a.add(Person(14, 'Violet')); @@ -25,7 +25,7 @@ void main() { // use Comparing for simpler creation: // sort by age desc and then name asc final b = OrderedSet.comparing( - Comparing.join([(p) => -p.age, (p) => p.name]), + compare: Comparing.join([(p) => -p.age, (p) => p.name]), ); b.addAll(a.toList()); print(b.toList().map((e) => e.name)); diff --git a/lib/comparing_ordered_set.dart b/lib/comparing_ordered_set.dart index da1b6e1..88ceca1 100644 --- a/lib/comparing_ordered_set.dart +++ b/lib/comparing_ordered_set.dart @@ -3,6 +3,7 @@ import 'dart:collection'; import 'package:ordered_set/mapping_ordered_set.dart'; import 'package:ordered_set/ordered_set.dart'; import 'package:ordered_set/ordered_set_iterator.dart'; +import 'package:ordered_set/queryable_ordered_set_impl.dart'; /// A simple implementation of [OrderedSet] that uses a [SplayTreeSet] as the /// backing store. @@ -10,7 +11,8 @@ import 'package:ordered_set/ordered_set_iterator.dart'; /// This does not store the elements priorities, so it is susceptible to race /// conditions if priorities are changed while iterating. /// For a safer implementation, use [MappingOrderedSet]. -class ComparingOrderedSet extends OrderedSet { +class ComparingOrderedSet extends OrderedSet + with QueryableOrderedSetImpl { // If the default implementation of `Set` changes from `LinkedHashSet` to // something else that isn't ordered we'll have to change this to explicitly // be `LinkedHashSet` (or some other data structure that preserves order). @@ -20,6 +22,9 @@ class ComparingOrderedSet extends OrderedSet { bool _validReverseCache = true; Iterable _reverseCache = const Iterable.empty(); + @override + final bool strictMode; + // Copied from SplayTreeSet, but those are private there static int _dynamicCompare(dynamic a, dynamic b) => Comparable.compare( a as Comparable, @@ -37,7 +42,10 @@ class ComparingOrderedSet extends OrderedSet { /// /// If the [compare] function is omitted, it defaults to [Comparable.compare], /// and the elements must be comparable. - ComparingOrderedSet([int Function(E e1, E e2)? compare]) { + ComparingOrderedSet({ + int Function(E e1, E e2)? compare, + this.strictMode = true, + }) { final comparator = compare ?? _defaultCompare(); _backingSet = SplayTreeSet>((Set l1, Set l2) { if (l1.isEmpty) { @@ -81,6 +89,7 @@ class ComparingOrderedSet extends OrderedSet { if (added) { _length++; _validReverseCache = false; + onAdd(e); } return added; } @@ -122,6 +131,7 @@ class ComparingOrderedSet extends OrderedSet { // If the removal resulted in an empty bucket, remove the bucket as well. _backingSet.remove({}); _validReverseCache = false; + onRemove(e); } return result; } @@ -131,5 +141,6 @@ class ComparingOrderedSet extends OrderedSet { _validReverseCache = false; _backingSet.clear(); _length = 0; + onClear(); } } diff --git a/lib/mapping_ordered_set.dart b/lib/mapping_ordered_set.dart index dabd1e7..36649d3 100644 --- a/lib/mapping_ordered_set.dart +++ b/lib/mapping_ordered_set.dart @@ -3,6 +3,7 @@ import 'dart:collection'; import 'package:ordered_set/comparing_ordered_set.dart'; import 'package:ordered_set/ordered_set.dart'; import 'package:ordered_set/ordered_set_iterator.dart'; +import 'package:ordered_set/queryable_ordered_set_impl.dart'; /// A simple implementation of [OrderedSet] that uses a [SplayTreeMap] as the /// backing store. @@ -10,7 +11,8 @@ import 'package:ordered_set/ordered_set_iterator.dart'; /// This allows it to keep a cache of elements priorities, so they can be used /// changed without rebalancing. /// For an alternative implementation, use [ComparingOrderedSet]. -class MappingOrderedSet, E> extends OrderedSet { +class MappingOrderedSet, E> extends OrderedSet + with QueryableOrderedSetImpl { final K Function(E a) _mappingFunction; late SplayTreeMap> _backingSet; late int _length; @@ -18,7 +20,10 @@ class MappingOrderedSet, E> extends OrderedSet { bool _validReverseCache = true; Iterable _reverseCache = const Iterable.empty(); - MappingOrderedSet(this._mappingFunction) { + @override + final bool strictMode; + + MappingOrderedSet(this._mappingFunction, {this.strictMode = true}) { _backingSet = SplayTreeMap((K k1, K k2) { return k1.compareTo(k2); }); @@ -49,6 +54,7 @@ class MappingOrderedSet, E> extends OrderedSet { if (added) { _length++; _validReverseCache = false; + onAdd(e); } return added; } @@ -94,6 +100,7 @@ class MappingOrderedSet, E> extends OrderedSet { _backingSet.remove(key); } _validReverseCache = false; + onRemove(e); } return result; } @@ -103,5 +110,6 @@ class MappingOrderedSet, E> extends OrderedSet { _validReverseCache = false; _backingSet.clear(); _length = 0; + onClear(); } } diff --git a/lib/ordered_set.dart b/lib/ordered_set.dart index fd89456..0131094 100644 --- a/lib/ordered_set.dart +++ b/lib/ordered_set.dart @@ -2,7 +2,7 @@ import 'dart:collection'; import 'package:ordered_set/comparing_ordered_set.dart'; import 'package:ordered_set/mapping_ordered_set.dart'; -import 'package:ordered_set/queryable_ordered_set.dart'; +import 'package:ordered_set/read_only_ordered_set.dart'; /// A simple interface of an ordered set for Dart. /// @@ -10,10 +10,7 @@ import 'package:ordered_set/queryable_ordered_set.dart'; /// [SplayTreeSet], it allows for several different elements with the same /// priority to be added. It also implements [Iterable], so you can iterate it /// in O(n). -abstract class OrderedSet extends IterableMixin { - /// The tree's elements in reversed order; should be cached when possible. - Iterable reversed(); - +abstract class OrderedSet extends ReadOnlyOrderedSet { /// Adds the element [e] to this, and returns whether the element was /// added or not. If the element already exists in the collection, it isn't /// added. @@ -86,18 +83,20 @@ abstract class OrderedSet extends IterableMixin { /// /// This implementation will not store component priorities, so it is /// susceptible to race conditions if priorities are changed while iterating. - static ComparingOrderedSet comparing([ + static ComparingOrderedSet comparing({ int Function(E a, E b)? compare, - ]) { - return ComparingOrderedSet(compare); + bool strictMode = true, + }) { + return ComparingOrderedSet(compare: compare, strictMode: strictMode); } /// Creates an instance of [OrderedSet] using the [MappingOrderedSet] /// implementation and the provided [mappingFunction]. static MappingOrderedSet mapping, E>( - K Function(E a) mappingFunction, - ) { - return MappingOrderedSet(mappingFunction); + K Function(E a) mappingFunction, { + bool strictMode = true, + }) { + return MappingOrderedSet(mappingFunction, strictMode: strictMode); } /// Creates an instance of [OrderedSet] for items that are already @@ -105,24 +104,19 @@ abstract class OrderedSet extends IterableMixin { /// Use this for classes that implement [Comparable] of a different class. /// Equivalent to `mapping((a) => a)`. static MappingOrderedSet - comparable, E extends K>() { - return mapping((a) => a); + comparable, E extends K>({ + bool strictMode = true, + }) { + return mapping((a) => a, strictMode: strictMode); } /// Creates an instance of [OrderedSet] for items that are already /// [Comparable] of themselves, using the [MappingOrderedSet] implementation. /// Use this for classes that implement [Comparable] of themselves. /// Equivalent to `mapping((a) => a)`. - static MappingOrderedSet simple>() { - return comparable(); - } - - /// Creates an instance of [OrderedSet] using the [QueryableOrderedSet] - /// by wrapping the provided [backingSet]. - static QueryableOrderedSet queryable( - OrderedSet backingSet, { + static MappingOrderedSet simple>({ bool strictMode = true, }) { - return QueryableOrderedSet(backingSet, strictMode: strictMode); + return comparable(strictMode: strictMode); } } diff --git a/lib/queryable_ordered_set.dart b/lib/queryable_ordered_set_impl.dart similarity index 55% rename from lib/queryable_ordered_set.dart rename to lib/queryable_ordered_set_impl.dart index 1bf5a72..5e72513 100644 --- a/lib/queryable_ordered_set.dart +++ b/lib/queryable_ordered_set_impl.dart @@ -1,7 +1,7 @@ import 'package:ordered_set/ordered_set.dart'; -/// This is an implementation of [OrderedSet] that allows you to more -/// efficiently [query] the list. +/// This is a mixin to provide the query caching capabilities to both +/// [OrderedSet] implementations. /// /// You can [register] a set of queries, i.e., predefined sub-types, whose /// results, i.e., subsets of this set, are then cached. Since the queries @@ -21,26 +21,10 @@ import 'package:ordered_set/ordered_set.dart'; /// /// Note that you can change [strictMode] to allow for querying for unregistered /// types; if you do so, the registration cost is payed on the first query. -class QueryableOrderedSet extends OrderedSet { - /// Controls whether running an unregistered query throws an error or - /// performs just-in-time filtering. - final bool strictMode; +mixin QueryableOrderedSetImpl on OrderedSet { final Map> _cache = {}; - final OrderedSet _backingSet; - QueryableOrderedSet( - this._backingSet, { - this.strictMode = true, - }); - - /// Adds a new cache for a subtype [C] of [E], allowing you to call [query]. - /// If the cache already exists this operation is a no-op. - /// - /// If the set is not empty, the current elements will be re-sorted. - /// - /// It is recommended to [register] all desired types at the beginning of - /// your application to avoid recomputing the existing elements upon - /// registration. + @override void register() { if (isRegistered()) { return; @@ -50,17 +34,7 @@ class QueryableOrderedSet extends OrderedSet { ); } - /// Allow you to find a subset of this set with all the elements `e` for - /// which the condition `e is C` is true. This is equivalent to - /// - /// ```dart - /// orderedSet.whereType() - /// ``` - /// - /// except that it is O(0). - /// - /// Note: you *must* call [register] for every type [C] you desire to use - /// before calling this, or set [strictMode] to false. + @override Iterable query() { final result = _cache[C]; if (result == null) { @@ -92,51 +66,23 @@ class QueryableOrderedSet extends OrderedSet { return super.whereType(); } - /// Whether type [C] is registered as a cache - bool isRegistered() => _cache.containsKey(C); - - @override - int get length => _backingSet.length; - - @override - Iterator get iterator => _backingSet.iterator; - - @override - Iterable reversed() => _backingSet.reversed(); - - @override - void rebalanceAll() { - _backingSet.rebalanceAll(); - } - @override - void rebalanceWhere(bool Function(E element) test) { - _backingSet.rebalanceWhere(test); - } + bool isRegistered() => _cache.containsKey(C); - @override - bool add(E t) { - if (_backingSet.add(t)) { - _cache.forEach((key, value) { - if (value.check(t)) { - value.data.add(t); - } - }); - return true; - } - return false; + void onAdd(E t) { + _cache.forEach((key, value) { + if (value.check(t)) { + value.data.add(t); + } + }); } - @override - bool remove(E e) { + void onRemove(E e) { _cache.values.forEach((v) => v.data.remove(e)); - return _backingSet.remove(e); } - @override - void clear() { + void onClear() { _cache.values.forEach((v) => v.data.clear()); - _backingSet.clear(); } List _filter() => whereType().toList(); diff --git a/lib/read_only_ordered_set.dart b/lib/read_only_ordered_set.dart new file mode 100644 index 0000000..37ca5c8 --- /dev/null +++ b/lib/read_only_ordered_set.dart @@ -0,0 +1,36 @@ +import 'dart:collection'; + +abstract class ReadOnlyOrderedSet extends IterableMixin { + /// The tree's elements in reversed order; should be cached when possible. + Iterable reversed(); + + /// Controls whether running an unregistered query throws an error or + /// performs just-in-time filtering. + bool get strictMode; + + /// Whether type [C] is registered as a cache + bool isRegistered(); + + /// Adds a new cache for a subtype [C] of [E], allowing you to call [query]. + /// If the cache already exists this operation is a no-op. + /// + /// If the set is not empty, the current elements will be re-sorted. + /// + /// It is recommended to [register] all desired types at the beginning of + /// your application to avoid recomputing the existing elements upon + /// registration. + void register(); + + /// Allow you to find a subset of this set with all the elements `e` for + /// which the condition `e is C` is true. This is equivalent to + /// + /// ```dart + /// orderedSet.whereType() + /// ``` + /// + /// except that it is O(0). + /// + /// Note: you *must* call [register] for every type [C] you desire to use + /// before calling this, or set [strictMode] to false. + Iterable query(); +} diff --git a/test/comparing_ordered_set_test.dart b/test/comparing_ordered_set_test.dart index c7438cc..11e48b6 100644 --- a/test/comparing_ordered_set_test.dart +++ b/test/comparing_ordered_set_test.dart @@ -118,12 +118,14 @@ void main() { }); test('with repeated priority elements', () { - final a = OrderedSet.comparing((a, b) => (a % 2) - (b % 2)); + final a = OrderedSet.comparing( + compare: (a, b) => (a % 2) - (b % 2), + ); expect(a.addAll([7, 4, 3, 1, 2, 6, 5]), 7); expect(a.length, 7); expect(a.toList().join(), '4267315'); - final b = OrderedSet.comparing((a, b) => 0); + final b = OrderedSet.comparing(compare: (a, b) => 0); expect(b.addAll([7, 4, 3, 1, 2, 6, 5]), 7); expect(a.length, 7); expect(b.toList().join(), '7431265'); @@ -174,7 +176,9 @@ void main() { }); test('keeps track of length when removing', () { - final a = OrderedSet.comparing((a, b) => 0); // no priority + final a = OrderedSet.comparing( + compare: (a, b) => 0, // no priority + ); expect(a.addAll([1, 2, 3, 4]), 4); expect(a.length, 4); @@ -223,7 +227,7 @@ void main() { test('test with custom comparator', () { final a = OrderedSet.comparing( - (a, b) => a.name.compareTo(b.name), + compare: (a, b) => a.name.compareTo(b.name), ); expect(a.add(ComparableObject(1, 'Sunny')), isTrue); expect(a.add(ComparableObject(12, 'Klaus')), isTrue); @@ -235,7 +239,9 @@ void main() { test( 'test items with repeated comparables, maintain insertion order', () { - final a = OrderedSet.comparing((a, b) => (a % 2) - (b % 2)); + final a = OrderedSet.comparing( + compare: (a, b) => (a % 2) - (b % 2), + ); for (var i = 0; i < 10; i++) { expect(a.add(i), isTrue); } @@ -275,7 +281,7 @@ void main() { test('with custom comparator, repeated items and removal', () { final a = OrderedSet.comparing( - (a, b) => -a.priority.compareTo(b.priority), + compare: (a, b) => -a.priority.compareTo(b.priority), ); final a1 = ComparableObject(2, '1'); final a2 = ComparableObject(2, '2'); @@ -321,7 +327,7 @@ void main() { test('removeAll', () { final orderedSet = OrderedSet.comparing( - Comparing.on((e) => e.priority), + compare: Comparing.on((e) => e.priority), ); final a = ComparableObject(0, 'a'); @@ -361,7 +367,7 @@ void main() { group('rebalancing', () { test('rebalanceWhere and rebalanceAll', () { final orderedSet = OrderedSet.comparing( - Comparing.on((e) => e.priority), + compare: Comparing.on((e) => e.priority), ); final a = ComparableObject(0, 'a'); @@ -388,7 +394,7 @@ void main() { group('reversed', () { test('reversed properly invalidates cache', () { final orderedSet = OrderedSet.comparing( - Comparing.on((e) => e.priority), + compare: Comparing.on((e) => e.priority), ); final a = ComparableObject(0, 'a'); diff --git a/test/comparing_test.dart b/test/comparing_test.dart index ec6527d..5216ee7 100644 --- a/test/comparing_test.dart +++ b/test/comparing_test.dart @@ -1,5 +1,6 @@ import 'package:ordered_set/comparing.dart'; import 'package:ordered_set/comparing_ordered_set.dart'; +import 'package:ordered_set/ordered_set.dart'; import 'package:test/test.dart'; import 'comparable_object.dart'; @@ -31,7 +32,7 @@ void main() { }); test('using complex object', () { final set = ComparingOrderedSet( - Comparing.on((o) => o.name), + compare: Comparing.on((o) => o.name), ); set.add(ComparableObject(0, 'd')); set.add(ComparableObject(1, 'b')); @@ -55,7 +56,7 @@ void main() { final c = Comparing.reverse( Comparing.on((t) => t.name), ); - final set = ComparingOrderedSet(c); + final set = OrderedSet.comparing(compare: c); set.add(ComparableObject(0, 'd')); set.add(ComparableObject(1, 'b')); set.add(ComparableObject(2, 'a')); @@ -70,7 +71,7 @@ void main() { (ComparableObject t) => t.priority, (ComparableObject t) => t.name, ]); - final set = ComparingOrderedSet(c); + final set = OrderedSet.comparing(compare: c); set.add(ComparableObject(1, 'b')); set.add(ComparableObject(0, 'b')); set.add(ComparableObject(3, 'a')); diff --git a/test/queryable_comparable_ordered_set_test.dart b/test/queryable_comparable_ordered_set_test.dart index ad979d3..c0ff5ad 100644 --- a/test/queryable_comparable_ordered_set_test.dart +++ b/test/queryable_comparable_ordered_set_test.dart @@ -1,6 +1,5 @@ import 'package:ordered_set/comparing.dart'; import 'package:ordered_set/ordered_set.dart'; -import 'package:ordered_set/queryable_ordered_set.dart'; import 'package:test/test.dart'; abstract class Animal { @@ -291,13 +290,11 @@ void main() { }); } -QueryableOrderedSet _create({ +OrderedSet _create({ bool strictMode = true, }) { - return OrderedSet.queryable( - OrderedSet.comparing( - Comparing.on((e) => e.name), - ), + return OrderedSet.comparing( + compare: Comparing.on((e) => e.name), strictMode: strictMode, ); } diff --git a/test/queryable_priority_ordered_set_test.dart b/test/queryable_priority_ordered_set_test.dart index cab9c1b..12f1918 100644 --- a/test/queryable_priority_ordered_set_test.dart +++ b/test/queryable_priority_ordered_set_test.dart @@ -1,5 +1,4 @@ import 'package:ordered_set/ordered_set.dart'; -import 'package:ordered_set/queryable_ordered_set.dart'; import 'package:test/test.dart'; abstract class Animal { @@ -290,11 +289,11 @@ void main() { }); } -QueryableOrderedSet _create({ +OrderedSet _create({ bool strictMode = true, }) { - return OrderedSet.queryable( - OrderedSet.mapping((e) => e.name), + return OrderedSet.mapping( + (e) => e.name, strictMode: strictMode, ); } From f7eb6cd69178fb8e47232022d6c430b4a6de1dba Mon Sep 17 00:00:00 2001 From: Luan Nico Date: Sun, 4 May 2025 16:45:35 -0400 Subject: [PATCH 02/10] Fix docs --- README.md | 20 +++++++++++++++----- lib/queryable_ordered_set_impl.dart | 12 ++---------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 034766b..6f116ba 100644 --- a/README.md +++ b/README.md @@ -67,17 +67,15 @@ Note that you could instead just create a `MappingOrderedSet` instead: // ... ``` -## Mapping vs Comparing vs Queryable +## Mapping vs Comparing -There are three main implementations of the `OrderedSet` interface: +There are two main implementations of the `OrderedSet` interface: * `ComparingOrderedSet`: the simplest implementation, takes in a `Comparator` and does not cache priorities. It uses Dart's `SplayTreeSet` as a backing implementation. * `MappingOrderedSet`: a slightly more advanced implementation that takes in a mapper function (maps elements to their priorities) and caches them. It uses Dart's `SplayTreeMap` as a backing implementation. -* `QueryableOrderedSet`: a simple wrapper over either `OrderedSet` that allows for O(1) type - queries; if you find yourself doing `.whereType()` a lot, you should consider using this. In order to create an `OrderedSet`, however, you can just use the static methods on the interface itself: @@ -90,7 +88,19 @@ itself: a `MappingOrderedSet` with identity mapping. * `OrderedSet.simple()`: if `E extends Comparable`, this is an even simpler way of creating a `MappingOrderedSet` with identity mapping. -* `OrderedSet.queryable(orderedSet)`: wraps the given `OrderedSet` into a `QueryableOrderedSet`. + +## Querying + +You can [register] a set of queries, i.e., predefined sub-types, whose results, +i.e., subsets of this set, are then cached. +Since the queries have to be type checks, and types are runtime constants, this +can be vastly optimized. + +You can then filter by type by using the [query] method (or using [whereType]; +which is overridden). + +Note that you can change [strictMode] to allow for querying for unregistered +types; if you do so, the registration cost is payed on the first query. ## Contributing diff --git a/lib/queryable_ordered_set_impl.dart b/lib/queryable_ordered_set_impl.dart index 5e72513..41bcdee 100644 --- a/lib/queryable_ordered_set_impl.dart +++ b/lib/queryable_ordered_set_impl.dart @@ -8,16 +8,8 @@ import 'package:ordered_set/ordered_set.dart'; /// have to be type checks, and types are runtime constants, this can be /// vastly optimized. /// -/// If you find yourself doing a lot of: -/// -/// ```dart -/// orderedSet.whereType() -/// ``` -/// -/// On your code, and are concerned you are iterating a very long O(n) list to -/// find a handful of elements, specially if this is done every tick, you -/// can use this class, that pays a small O(number of registers) cost on [add], -/// but lets you find (specific) subsets at O(0). +/// You can then filter by type by using the [query] method (or using +/// [whereType]; which is overridden). /// /// Note that you can change [strictMode] to allow for querying for unregistered /// types; if you do so, the registration cost is payed on the first query. From 7fa19d8d3bd25ecf9ebe794094cfd52998ccb377 Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Wed, 21 May 2025 15:24:25 +0200 Subject: [PATCH 03/10] Fix readme line breaks --- README.md | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 6f116ba..1af3e7a 100644 --- a/README.md +++ b/README.md @@ -91,19 +91,16 @@ itself: ## Querying -You can [register] a set of queries, i.e., predefined sub-types, whose results, -i.e., subsets of this set, are then cached. -Since the queries have to be type checks, and types are runtime constants, this -can be vastly optimized. +You can [register] a set of queries, i.e., predefined sub-types, whose results, i.e., subsets of +this set, are then cached. Since the queries have to be type checks, and types are runtime +constants, this can be vastly optimized. -You can then filter by type by using the [query] method (or using [whereType]; -which is overridden). +You can then filter by type by using the [query] method (or using [whereType]; which is overridden). -Note that you can change [strictMode] to allow for querying for unregistered -types; if you do so, the registration cost is payed on the first query. +Note that you can change [strictMode] to allow for querying for unregistered types; if you do so, +the registration cost is payed on the first query. ## Contributing All contributions are very welcome! Please feel free to create Issues, help us with PR's or comment your suggestions, feature requests, bugs, et cetera. Give us a star if you liked it! - From 0a287fef27ea07e065112f2ca306505aa0bd06f5 Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Wed, 21 May 2025 16:11:51 +0200 Subject: [PATCH 04/10] Bump dependencies --- pubspec.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index e186761..5eb8d0b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,11 +12,11 @@ topics: - ordered-set environment: - sdk: '>=3.4.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dev_dependencies: - benchmark_harness: ^2.2.2 - coverage: ^1.8.0 - dartdoc: ^8.0.9+1 - flame_lint: ^1.2.0 - test: ^1.25.7 + benchmark_harness: ^2.3.1 + coverage: ^1.13.1 + dartdoc: ^8.3.3 + flame_lint: ^1.3.0 + test: ^1.26.2 From b970236c605c5113b7c409369df1d9afc7accbf6 Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Wed, 21 May 2025 16:11:58 +0200 Subject: [PATCH 05/10] forEach -> for-loop --- lib/ordered_set.dart | 14 ++++++++++++-- lib/queryable_ordered_set_impl.dart | 21 +++++++++++++++------ 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/lib/ordered_set.dart b/lib/ordered_set.dart index 0131094..44401f6 100644 --- a/lib/ordered_set.dart +++ b/lib/ordered_set.dart @@ -64,12 +64,22 @@ abstract class OrderedSet extends ReadOnlyOrderedSet { /// Remove all elements that match the [test] condition; returns the removed /// elements. Iterable removeWhere(bool Function(E element) test) { - return where(test).toList(growable: false)..forEach(remove); + final elements = where(test).toList(growable: false); + for (final element in elements) { + remove(element); + } + return elements; } /// Remove all [elements] and returns the removed elements. Iterable removeAll(Iterable elements) { - return elements.where(remove).toList(growable: false); + final removed = []; + for (final element in elements) { + if (remove(element)) { + removed.add(element); + } + } + return removed; } /// Removes the element at [index]. diff --git a/lib/queryable_ordered_set_impl.dart b/lib/queryable_ordered_set_impl.dart index 41bcdee..abda49a 100644 --- a/lib/queryable_ordered_set_impl.dart +++ b/lib/queryable_ordered_set_impl.dart @@ -22,7 +22,7 @@ mixin QueryableOrderedSetImpl on OrderedSet { return; } _cache[C] = _CacheEntry( - data: _filter(), + _filter(), ); } @@ -62,19 +62,28 @@ mixin QueryableOrderedSetImpl on OrderedSet { bool isRegistered() => _cache.containsKey(C); void onAdd(E t) { - _cache.forEach((key, value) { + for (final entry in _cache.entries) { + final value = entry.value; if (value.check(t)) { value.data.add(t); } - }); + } } void onRemove(E e) { - _cache.values.forEach((v) => v.data.remove(e)); + for (final entry in _cache.entries) { + final value = entry.value; + if (value.check(e)) { + value.data.remove(e); + } + } } void onClear() { - _cache.values.forEach((v) => v.data.clear()); + for (final entry in _cache.entries) { + final value = entry.value; + value.data.clear(); + } } List _filter() => whereType().toList(); @@ -83,7 +92,7 @@ mixin QueryableOrderedSetImpl on OrderedSet { class _CacheEntry { final List data; - _CacheEntry({required this.data}); + _CacheEntry(this.data); bool check(T t) { return t is C; From 7ef8423321a04d0cc499ce8d8cef440e1db5413b Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Wed, 21 May 2025 16:21:28 +0200 Subject: [PATCH 06/10] Try with pragma --- lib/comparing_ordered_set.dart | 4 ++++ lib/mapping_ordered_set.dart | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/lib/comparing_ordered_set.dart b/lib/comparing_ordered_set.dart index 88ceca1..b7c0af7 100644 --- a/lib/comparing_ordered_set.dart +++ b/lib/comparing_ordered_set.dart @@ -78,6 +78,8 @@ class ComparingOrderedSet extends OrderedSet return _reverseCache; } + @pragma('vm:prefer-inline') + @pragma('wasm:prefer-inline') @override bool add(E e) { final elementSet = {e}; @@ -107,6 +109,8 @@ class ComparingOrderedSet extends OrderedSet addAll(elements); } + @pragma('vm:prefer-inline') + @pragma('wasm:prefer-inline') @override bool remove(E e) { var bucket = _backingSet.lookup({e}); diff --git a/lib/mapping_ordered_set.dart b/lib/mapping_ordered_set.dart index 36649d3..7d12a4f 100644 --- a/lib/mapping_ordered_set.dart +++ b/lib/mapping_ordered_set.dart @@ -46,6 +46,8 @@ class MappingOrderedSet, E> extends OrderedSet return _reverseCache; } + @pragma('vm:prefer-inline') + @pragma('wasm:prefer-inline') @override bool add(E e) { final elementPriority = _mappingFunction(e); @@ -72,6 +74,8 @@ class MappingOrderedSet, E> extends OrderedSet addAll(elements); } + @pragma('vm:prefer-inline') + @pragma('wasm:prefer-inline') @override bool remove(E e) { K? key = _mappingFunction(e); From 43d18013e8a3104ab0f9559b1441251263d5c027 Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Wed, 21 May 2025 16:31:11 +0200 Subject: [PATCH 07/10] Try without pragma --- lib/comparing_ordered_set.dart | 4 ---- lib/mapping_ordered_set.dart | 4 ---- 2 files changed, 8 deletions(-) diff --git a/lib/comparing_ordered_set.dart b/lib/comparing_ordered_set.dart index b7c0af7..88ceca1 100644 --- a/lib/comparing_ordered_set.dart +++ b/lib/comparing_ordered_set.dart @@ -78,8 +78,6 @@ class ComparingOrderedSet extends OrderedSet return _reverseCache; } - @pragma('vm:prefer-inline') - @pragma('wasm:prefer-inline') @override bool add(E e) { final elementSet = {e}; @@ -109,8 +107,6 @@ class ComparingOrderedSet extends OrderedSet addAll(elements); } - @pragma('vm:prefer-inline') - @pragma('wasm:prefer-inline') @override bool remove(E e) { var bucket = _backingSet.lookup({e}); diff --git a/lib/mapping_ordered_set.dart b/lib/mapping_ordered_set.dart index 7d12a4f..36649d3 100644 --- a/lib/mapping_ordered_set.dart +++ b/lib/mapping_ordered_set.dart @@ -46,8 +46,6 @@ class MappingOrderedSet, E> extends OrderedSet return _reverseCache; } - @pragma('vm:prefer-inline') - @pragma('wasm:prefer-inline') @override bool add(E e) { final elementPriority = _mappingFunction(e); @@ -74,8 +72,6 @@ class MappingOrderedSet, E> extends OrderedSet addAll(elements); } - @pragma('vm:prefer-inline') - @pragma('wasm:prefer-inline') @override bool remove(E e) { K? key = _mappingFunction(e); From 974448bc4b02193afc3fb7b54b37240ccd97bf71 Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Wed, 21 May 2025 16:52:47 +0200 Subject: [PATCH 08/10] Final backing sets --- lib/comparing_ordered_set.dart | 37 +++++++++++++++++----------------- lib/mapping_ordered_set.dart | 14 ++++++------- 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/lib/comparing_ordered_set.dart b/lib/comparing_ordered_set.dart index 88ceca1..809be7d 100644 --- a/lib/comparing_ordered_set.dart +++ b/lib/comparing_ordered_set.dart @@ -16,8 +16,11 @@ class ComparingOrderedSet extends OrderedSet // If the default implementation of `Set` changes from `LinkedHashSet` to // something else that isn't ordered we'll have to change this to explicitly // be `LinkedHashSet` (or some other data structure that preserves order). - late SplayTreeSet> _backingSet; - late int _length; + late final SplayTreeSet> _backingSet = SplayTreeSet>( + _outerComparator, + ); + final int Function(E e1, E e2) _comparator; + int _length = 0; bool _validReverseCache = true; Iterable _reverseCache = const Iterable.empty(); @@ -45,22 +48,7 @@ class ComparingOrderedSet extends OrderedSet ComparingOrderedSet({ int Function(E e1, E e2)? compare, this.strictMode = true, - }) { - final comparator = compare ?? _defaultCompare(); - _backingSet = SplayTreeSet>((Set l1, Set l2) { - if (l1.isEmpty) { - if (l2.isEmpty) { - return 0; - } - return -1; - } - if (l2.isEmpty) { - return 1; - } - return comparator(l1.first, l2.first); - }); - _length = 0; - } + }) : _comparator = compare ?? _defaultCompare(); @override int get length => _length; @@ -143,4 +131,17 @@ class ComparingOrderedSet extends OrderedSet _length = 0; onClear(); } + + int _outerComparator(Set l1, Set l2) { + if (l1.isEmpty) { + if (l2.isEmpty) { + return 0; + } + return -1; + } + if (l2.isEmpty) { + return 1; + } + return _comparator(l1.first, l2.first); + } } diff --git a/lib/mapping_ordered_set.dart b/lib/mapping_ordered_set.dart index 36649d3..59441af 100644 --- a/lib/mapping_ordered_set.dart +++ b/lib/mapping_ordered_set.dart @@ -14,8 +14,8 @@ import 'package:ordered_set/queryable_ordered_set_impl.dart'; class MappingOrderedSet, E> extends OrderedSet with QueryableOrderedSetImpl { final K Function(E a) _mappingFunction; - late SplayTreeMap> _backingSet; - late int _length; + final SplayTreeMap> _backingSet; + int _length = 0; bool _validReverseCache = true; Iterable _reverseCache = const Iterable.empty(); @@ -23,12 +23,10 @@ class MappingOrderedSet, E> extends OrderedSet @override final bool strictMode; - MappingOrderedSet(this._mappingFunction, {this.strictMode = true}) { - _backingSet = SplayTreeMap((K k1, K k2) { - return k1.compareTo(k2); - }); - _length = 0; - } + MappingOrderedSet( + this._mappingFunction, { + this.strictMode = true, + }) : _backingSet = SplayTreeMap((K k1, K k2) => k1.compareTo(k2)); @override int get length => _length; From 8963777dba4442c651759fc804215dc5d90b8781 Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Wed, 21 May 2025 16:56:16 +0200 Subject: [PATCH 09/10] Revert "Try without pragma" This reverts commit 43d18013e8a3104ab0f9559b1441251263d5c027. --- lib/comparing_ordered_set.dart | 4 ++++ lib/mapping_ordered_set.dart | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/lib/comparing_ordered_set.dart b/lib/comparing_ordered_set.dart index 809be7d..4f72555 100644 --- a/lib/comparing_ordered_set.dart +++ b/lib/comparing_ordered_set.dart @@ -66,6 +66,8 @@ class ComparingOrderedSet extends OrderedSet return _reverseCache; } + @pragma('vm:prefer-inline') + @pragma('wasm:prefer-inline') @override bool add(E e) { final elementSet = {e}; @@ -95,6 +97,8 @@ class ComparingOrderedSet extends OrderedSet addAll(elements); } + @pragma('vm:prefer-inline') + @pragma('wasm:prefer-inline') @override bool remove(E e) { var bucket = _backingSet.lookup({e}); diff --git a/lib/mapping_ordered_set.dart b/lib/mapping_ordered_set.dart index 59441af..42a6ca8 100644 --- a/lib/mapping_ordered_set.dart +++ b/lib/mapping_ordered_set.dart @@ -44,6 +44,8 @@ class MappingOrderedSet, E> extends OrderedSet return _reverseCache; } + @pragma('vm:prefer-inline') + @pragma('wasm:prefer-inline') @override bool add(E e) { final elementPriority = _mappingFunction(e); @@ -70,6 +72,8 @@ class MappingOrderedSet, E> extends OrderedSet addAll(elements); } + @pragma('vm:prefer-inline') + @pragma('wasm:prefer-inline') @override bool remove(E e) { K? key = _mappingFunction(e); From 8134f00c99daeb953d63a8ec53ebcf8d7309e9af Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Wed, 21 May 2025 17:02:24 +0200 Subject: [PATCH 10/10] Reapply "Try without pragma" This reverts commit 8963777dba4442c651759fc804215dc5d90b8781. --- lib/comparing_ordered_set.dart | 4 ---- lib/mapping_ordered_set.dart | 4 ---- 2 files changed, 8 deletions(-) diff --git a/lib/comparing_ordered_set.dart b/lib/comparing_ordered_set.dart index 4f72555..809be7d 100644 --- a/lib/comparing_ordered_set.dart +++ b/lib/comparing_ordered_set.dart @@ -66,8 +66,6 @@ class ComparingOrderedSet extends OrderedSet return _reverseCache; } - @pragma('vm:prefer-inline') - @pragma('wasm:prefer-inline') @override bool add(E e) { final elementSet = {e}; @@ -97,8 +95,6 @@ class ComparingOrderedSet extends OrderedSet addAll(elements); } - @pragma('vm:prefer-inline') - @pragma('wasm:prefer-inline') @override bool remove(E e) { var bucket = _backingSet.lookup({e}); diff --git a/lib/mapping_ordered_set.dart b/lib/mapping_ordered_set.dart index 42a6ca8..59441af 100644 --- a/lib/mapping_ordered_set.dart +++ b/lib/mapping_ordered_set.dart @@ -44,8 +44,6 @@ class MappingOrderedSet, E> extends OrderedSet return _reverseCache; } - @pragma('vm:prefer-inline') - @pragma('wasm:prefer-inline') @override bool add(E e) { final elementPriority = _mappingFunction(e); @@ -72,8 +70,6 @@ class MappingOrderedSet, E> extends OrderedSet addAll(elements); } - @pragma('vm:prefer-inline') - @pragma('wasm:prefer-inline') @override bool remove(E e) { K? key = _mappingFunction(e);