Skip to content

Commit d451b81

Browse files
luanpotterspydon
andauthored
feat!: Introduce new ReadOnlyOrderedSet super-interface (#59)
* feat!: Introduce new ReadOnlyOrderedSet super-interface (also makes all sets Queryable) * Fix docs * Fix readme line breaks * Bump dependencies * forEach -> for-loop * Try with pragma * Try without pragma * Final backing sets * Revert "Try without pragma" This reverts commit 43d1801. * Reapply "Try without pragma" This reverts commit 8963777. --------- Co-authored-by: Lukas Klingsbo <[email protected]>
1 parent d67710d commit d451b81

14 files changed

+257
-242
lines changed

README.md

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,17 +67,15 @@ Note that you could instead just create a `MappingOrderedSet` instead:
6767
// ...
6868
```
6969

70-
## Mapping vs Comparing vs Queryable
70+
## Mapping vs Comparing
7171

72-
There are three main implementations of the `OrderedSet` interface:
72+
There are two main implementations of the `OrderedSet` interface:
7373

7474
* `ComparingOrderedSet`: the simplest implementation, takes in a `Comparator` and does not cache
7575
priorities. It uses Dart's `SplayTreeSet` as a backing implementation.
7676
* `MappingOrderedSet`: a slightly more advanced implementation that takes in a mapper function
7777
(maps elements to their priorities) and caches them. It uses Dart's `SplayTreeMap` as a backing
7878
implementation.
79-
* `QueryableOrderedSet`: a simple wrapper over either `OrderedSet` that allows for O(1) type
80-
queries; if you find yourself doing `.whereType<T>()` a lot, you should consider using this.
8179

8280
In order to create an `OrderedSet`, however, you can just use the static methods on the interface
8381
itself:
@@ -90,10 +88,19 @@ itself:
9088
a `MappingOrderedSet` with identity mapping.
9189
* `OrderedSet.simple<E>()`: if `E extends Comparable<E>`, this is an even simpler way of creating
9290
a `MappingOrderedSet` with identity mapping.
93-
* `OrderedSet.queryable<E>(orderedSet)`: wraps the given `OrderedSet` into a `QueryableOrderedSet`.
91+
92+
## Querying
93+
94+
You can [register] a set of queries, i.e., predefined sub-types, whose results, i.e., subsets of
95+
this set, are then cached. Since the queries have to be type checks, and types are runtime
96+
constants, this can be vastly optimized.
97+
98+
You can then filter by type by using the [query] method (or using [whereType]; which is overridden).
99+
100+
Note that you can change [strictMode] to allow for querying for unregistered types; if you do so,
101+
the registration cost is payed on the first query.
94102

95103
## Contributing
96104

97105
All contributions are very welcome! Please feel free to create Issues, help us with PR's or comment
98106
your suggestions, feature requests, bugs, et cetera. Give us a star if you liked it!
99-

benchmark/main.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import 'types.dart';
88

99
void main() {
1010
OrderedSet<K> comparing<K>(Mapper<K> mapper) {
11-
return OrderedSet.comparing<K>(Comparing.on(mapper));
11+
return OrderedSet.comparing<K>(compare: Comparing.on(mapper));
1212
}
1313

1414
OrderedSet<K> priority<K>(Mapper<K> mapper) {

example/ordered_set_example.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ void main() {
99
items.add(1);
1010
print(items.toList()); // [1, 2]
1111

12-
final a = OrderedSet.comparing<Person>((a, b) => a.age - b.age);
12+
final a = OrderedSet.comparing<Person>(compare: (a, b) => a.age - b.age);
1313
a.add(Person(12, 'Klaus'));
1414
a.add(Person(1, 'Sunny'));
1515
a.add(Person(14, 'Violet'));
@@ -25,7 +25,7 @@ void main() {
2525
// use Comparing for simpler creation:
2626
// sort by age desc and then name asc
2727
final b = OrderedSet.comparing<Person>(
28-
Comparing.join([(p) => -p.age, (p) => p.name]),
28+
compare: Comparing.join([(p) => -p.age, (p) => p.name]),
2929
);
3030
b.addAll(a.toList());
3131
print(b.toList().map((e) => e.name));

lib/comparing_ordered_set.dart

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,31 @@ import 'dart:collection';
33
import 'package:ordered_set/mapping_ordered_set.dart';
44
import 'package:ordered_set/ordered_set.dart';
55
import 'package:ordered_set/ordered_set_iterator.dart';
6+
import 'package:ordered_set/queryable_ordered_set_impl.dart';
67

78
/// A simple implementation of [OrderedSet] that uses a [SplayTreeSet] as the
89
/// backing store.
910
///
1011
/// This does not store the elements priorities, so it is susceptible to race
1112
/// conditions if priorities are changed while iterating.
1213
/// For a safer implementation, use [MappingOrderedSet].
13-
class ComparingOrderedSet<E> extends OrderedSet<E> {
14+
class ComparingOrderedSet<E> extends OrderedSet<E>
15+
with QueryableOrderedSetImpl<E> {
1416
// If the default implementation of `Set` changes from `LinkedHashSet` to
1517
// something else that isn't ordered we'll have to change this to explicitly
1618
// be `LinkedHashSet` (or some other data structure that preserves order).
17-
late SplayTreeSet<Set<E>> _backingSet;
18-
late int _length;
19+
late final SplayTreeSet<Set<E>> _backingSet = SplayTreeSet<LinkedHashSet<E>>(
20+
_outerComparator,
21+
);
22+
final int Function(E e1, E e2) _comparator;
23+
int _length = 0;
1924

2025
bool _validReverseCache = true;
2126
Iterable<E> _reverseCache = const Iterable.empty();
2227

28+
@override
29+
final bool strictMode;
30+
2331
// Copied from SplayTreeSet, but those are private there
2432
static int _dynamicCompare(dynamic a, dynamic b) => Comparable.compare(
2533
a as Comparable,
@@ -37,22 +45,10 @@ class ComparingOrderedSet<E> extends OrderedSet<E> {
3745
///
3846
/// If the [compare] function is omitted, it defaults to [Comparable.compare],
3947
/// and the elements must be comparable.
40-
ComparingOrderedSet([int Function(E e1, E e2)? compare]) {
41-
final comparator = compare ?? _defaultCompare<E>();
42-
_backingSet = SplayTreeSet<LinkedHashSet<E>>((Set<E> l1, Set<E> l2) {
43-
if (l1.isEmpty) {
44-
if (l2.isEmpty) {
45-
return 0;
46-
}
47-
return -1;
48-
}
49-
if (l2.isEmpty) {
50-
return 1;
51-
}
52-
return comparator(l1.first, l2.first);
53-
});
54-
_length = 0;
55-
}
48+
ComparingOrderedSet({
49+
int Function(E e1, E e2)? compare,
50+
this.strictMode = true,
51+
}) : _comparator = compare ?? _defaultCompare<E>();
5652

5753
@override
5854
int get length => _length;
@@ -81,6 +77,7 @@ class ComparingOrderedSet<E> extends OrderedSet<E> {
8177
if (added) {
8278
_length++;
8379
_validReverseCache = false;
80+
onAdd(e);
8481
}
8582
return added;
8683
}
@@ -122,6 +119,7 @@ class ComparingOrderedSet<E> extends OrderedSet<E> {
122119
// If the removal resulted in an empty bucket, remove the bucket as well.
123120
_backingSet.remove(<E>{});
124121
_validReverseCache = false;
122+
onRemove(e);
125123
}
126124
return result;
127125
}
@@ -131,5 +129,19 @@ class ComparingOrderedSet<E> extends OrderedSet<E> {
131129
_validReverseCache = false;
132130
_backingSet.clear();
133131
_length = 0;
132+
onClear();
133+
}
134+
135+
int _outerComparator(Set<E> l1, Set<E> l2) {
136+
if (l1.isEmpty) {
137+
if (l2.isEmpty) {
138+
return 0;
139+
}
140+
return -1;
141+
}
142+
if (l2.isEmpty) {
143+
return 1;
144+
}
145+
return _comparator(l1.first, l2.first);
134146
}
135147
}

lib/mapping_ordered_set.dart

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,30 @@ import 'dart:collection';
33
import 'package:ordered_set/comparing_ordered_set.dart';
44
import 'package:ordered_set/ordered_set.dart';
55
import 'package:ordered_set/ordered_set_iterator.dart';
6+
import 'package:ordered_set/queryable_ordered_set_impl.dart';
67

78
/// A simple implementation of [OrderedSet] that uses a [SplayTreeMap] as the
89
/// backing store.
910
///
1011
/// This allows it to keep a cache of elements priorities, so they can be used
1112
/// changed without rebalancing.
1213
/// For an alternative implementation, use [ComparingOrderedSet].
13-
class MappingOrderedSet<K extends Comparable<K>, E> extends OrderedSet<E> {
14+
class MappingOrderedSet<K extends Comparable<K>, E> extends OrderedSet<E>
15+
with QueryableOrderedSetImpl<E> {
1416
final K Function(E a) _mappingFunction;
15-
late SplayTreeMap<K, Set<E>> _backingSet;
16-
late int _length;
17+
final SplayTreeMap<K, Set<E>> _backingSet;
18+
int _length = 0;
1719

1820
bool _validReverseCache = true;
1921
Iterable<E> _reverseCache = const Iterable.empty();
2022

21-
MappingOrderedSet(this._mappingFunction) {
22-
_backingSet = SplayTreeMap((K k1, K k2) {
23-
return k1.compareTo(k2);
24-
});
25-
_length = 0;
26-
}
23+
@override
24+
final bool strictMode;
25+
26+
MappingOrderedSet(
27+
this._mappingFunction, {
28+
this.strictMode = true,
29+
}) : _backingSet = SplayTreeMap((K k1, K k2) => k1.compareTo(k2));
2730

2831
@override
2932
int get length => _length;
@@ -49,6 +52,7 @@ class MappingOrderedSet<K extends Comparable<K>, E> extends OrderedSet<E> {
4952
if (added) {
5053
_length++;
5154
_validReverseCache = false;
55+
onAdd(e);
5256
}
5357
return added;
5458
}
@@ -94,6 +98,7 @@ class MappingOrderedSet<K extends Comparable<K>, E> extends OrderedSet<E> {
9498
_backingSet.remove(key);
9599
}
96100
_validReverseCache = false;
101+
onRemove(e);
97102
}
98103
return result;
99104
}
@@ -103,5 +108,6 @@ class MappingOrderedSet<K extends Comparable<K>, E> extends OrderedSet<E> {
103108
_validReverseCache = false;
104109
_backingSet.clear();
105110
_length = 0;
111+
onClear();
106112
}
107113
}

lib/ordered_set.dart

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,15 @@ import 'dart:collection';
22

33
import 'package:ordered_set/comparing_ordered_set.dart';
44
import 'package:ordered_set/mapping_ordered_set.dart';
5-
import 'package:ordered_set/queryable_ordered_set.dart';
5+
import 'package:ordered_set/read_only_ordered_set.dart';
66

77
/// A simple interface of an ordered set for Dart.
88
///
99
/// It accepts some way of comparing items for their priority. Unlike
1010
/// [SplayTreeSet], it allows for several different elements with the same
1111
/// priority to be added. It also implements [Iterable], so you can iterate it
1212
/// in O(n).
13-
abstract class OrderedSet<E> extends IterableMixin<E> {
14-
/// The tree's elements in reversed order; should be cached when possible.
15-
Iterable<E> reversed();
16-
13+
abstract class OrderedSet<E> extends ReadOnlyOrderedSet<E> {
1714
/// Adds the element [e] to this, and returns whether the element was
1815
/// added or not. If the element already exists in the collection, it isn't
1916
/// added.
@@ -67,12 +64,22 @@ abstract class OrderedSet<E> extends IterableMixin<E> {
6764
/// Remove all elements that match the [test] condition; returns the removed
6865
/// elements.
6966
Iterable<E> removeWhere(bool Function(E element) test) {
70-
return where(test).toList(growable: false)..forEach(remove);
67+
final elements = where(test).toList(growable: false);
68+
for (final element in elements) {
69+
remove(element);
70+
}
71+
return elements;
7172
}
7273

7374
/// Remove all [elements] and returns the removed elements.
7475
Iterable<E> removeAll(Iterable<E> elements) {
75-
return elements.where(remove).toList(growable: false);
76+
final removed = <E>[];
77+
for (final element in elements) {
78+
if (remove(element)) {
79+
removed.add(element);
80+
}
81+
}
82+
return removed;
7683
}
7784

7885
/// Removes the element at [index].
@@ -86,43 +93,40 @@ abstract class OrderedSet<E> extends IterableMixin<E> {
8693
///
8794
/// This implementation will not store component priorities, so it is
8895
/// susceptible to race conditions if priorities are changed while iterating.
89-
static ComparingOrderedSet<E> comparing<E>([
96+
static ComparingOrderedSet<E> comparing<E>({
9097
int Function(E a, E b)? compare,
91-
]) {
92-
return ComparingOrderedSet<E>(compare);
98+
bool strictMode = true,
99+
}) {
100+
return ComparingOrderedSet<E>(compare: compare, strictMode: strictMode);
93101
}
94102

95103
/// Creates an instance of [OrderedSet] using the [MappingOrderedSet]
96104
/// implementation and the provided [mappingFunction].
97105
static MappingOrderedSet<K, E> mapping<K extends Comparable<K>, E>(
98-
K Function(E a) mappingFunction,
99-
) {
100-
return MappingOrderedSet(mappingFunction);
106+
K Function(E a) mappingFunction, {
107+
bool strictMode = true,
108+
}) {
109+
return MappingOrderedSet(mappingFunction, strictMode: strictMode);
101110
}
102111

103112
/// Creates an instance of [OrderedSet] for items that are already
104113
/// [Comparable] using the [MappingOrderedSet] implementation.
105114
/// Use this for classes that implement [Comparable] of a different class.
106115
/// Equivalent to `mapping<K, E>((a) => a)`.
107116
static MappingOrderedSet<K, E>
108-
comparable<K extends Comparable<K>, E extends K>() {
109-
return mapping<K, E>((a) => a);
117+
comparable<K extends Comparable<K>, E extends K>({
118+
bool strictMode = true,
119+
}) {
120+
return mapping<K, E>((a) => a, strictMode: strictMode);
110121
}
111122

112123
/// Creates an instance of [OrderedSet] for items that are already
113124
/// [Comparable] of themselves, using the [MappingOrderedSet] implementation.
114125
/// Use this for classes that implement [Comparable] of themselves.
115126
/// Equivalent to `mapping<K, K>((a) => a)`.
116-
static MappingOrderedSet<E, E> simple<E extends Comparable<E>>() {
117-
return comparable<E, E>();
118-
}
119-
120-
/// Creates an instance of [OrderedSet] using the [QueryableOrderedSet]
121-
/// by wrapping the provided [backingSet].
122-
static QueryableOrderedSet<E> queryable<E>(
123-
OrderedSet<E> backingSet, {
127+
static MappingOrderedSet<E, E> simple<E extends Comparable<E>>({
124128
bool strictMode = true,
125129
}) {
126-
return QueryableOrderedSet<E>(backingSet, strictMode: strictMode);
130+
return comparable<E, E>(strictMode: strictMode);
127131
}
128132
}

0 commit comments

Comments
 (0)