Skip to content

Commit e6d6317

Browse files
committed
feat!: Introduce new ReadOnlyOrderedSet super-interface (also makes all sets Queryable)
1 parent d67710d commit e6d6317

11 files changed

+116
-119
lines changed

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: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@ 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> with QueryableOrderedSetImpl<E> {
1415
// If the default implementation of `Set` changes from `LinkedHashSet` to
1516
// something else that isn't ordered we'll have to change this to explicitly
1617
// be `LinkedHashSet` (or some other data structure that preserves order).
@@ -20,6 +21,9 @@ class ComparingOrderedSet<E> extends OrderedSet<E> {
2021
bool _validReverseCache = true;
2122
Iterable<E> _reverseCache = const Iterable.empty();
2223

24+
@override
25+
final bool strictMode;
26+
2327
// Copied from SplayTreeSet, but those are private there
2428
static int _dynamicCompare(dynamic a, dynamic b) => Comparable.compare(
2529
a as Comparable,
@@ -37,7 +41,10 @@ class ComparingOrderedSet<E> extends OrderedSet<E> {
3741
///
3842
/// If the [compare] function is omitted, it defaults to [Comparable.compare],
3943
/// and the elements must be comparable.
40-
ComparingOrderedSet([int Function(E e1, E e2)? compare]) {
44+
ComparingOrderedSet({
45+
int Function(E e1, E e2)? compare,
46+
this.strictMode = true,
47+
}) {
4148
final comparator = compare ?? _defaultCompare<E>();
4249
_backingSet = SplayTreeSet<LinkedHashSet<E>>((Set<E> l1, Set<E> l2) {
4350
if (l1.isEmpty) {
@@ -81,6 +88,7 @@ class ComparingOrderedSet<E> extends OrderedSet<E> {
8188
if (added) {
8289
_length++;
8390
_validReverseCache = false;
91+
onAdd(e);
8492
}
8593
return added;
8694
}
@@ -122,6 +130,7 @@ class ComparingOrderedSet<E> extends OrderedSet<E> {
122130
// If the removal resulted in an empty bucket, remove the bucket as well.
123131
_backingSet.remove(<E>{});
124132
_validReverseCache = false;
133+
onRemove(e);
125134
}
126135
return result;
127136
}
@@ -131,5 +140,6 @@ class ComparingOrderedSet<E> extends OrderedSet<E> {
131140
_validReverseCache = false;
132141
_backingSet.clear();
133142
_length = 0;
143+
onClear();
134144
}
135145
}

lib/mapping_ordered_set.dart

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,27 @@ 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;
1517
late SplayTreeMap<K, Set<E>> _backingSet;
1618
late int _length;
1719

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

21-
MappingOrderedSet(this._mappingFunction) {
23+
@override
24+
final bool strictMode;
25+
26+
MappingOrderedSet(this._mappingFunction, {this.strictMode = true}) {
2227
_backingSet = SplayTreeMap((K k1, K k2) {
2328
return k1.compareTo(k2);
2429
});
@@ -49,6 +54,7 @@ class MappingOrderedSet<K extends Comparable<K>, E> extends OrderedSet<E> {
4954
if (added) {
5055
_length++;
5156
_validReverseCache = false;
57+
onAdd(e);
5258
}
5359
return added;
5460
}
@@ -94,6 +100,7 @@ class MappingOrderedSet<K extends Comparable<K>, E> extends OrderedSet<E> {
94100
_backingSet.remove(key);
95101
}
96102
_validReverseCache = false;
103+
onRemove(e);
97104
}
98105
return result;
99106
}
@@ -103,5 +110,6 @@ class MappingOrderedSet<K extends Comparable<K>, E> extends OrderedSet<E> {
103110
_validReverseCache = false;
104111
_backingSet.clear();
105112
_length = 0;
113+
onClear();
106114
}
107115
}

lib/ordered_set.dart

Lines changed: 16 additions & 22 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.
@@ -86,43 +83,40 @@ abstract class OrderedSet<E> extends IterableMixin<E> {
8683
///
8784
/// This implementation will not store component priorities, so it is
8885
/// susceptible to race conditions if priorities are changed while iterating.
89-
static ComparingOrderedSet<E> comparing<E>([
86+
static ComparingOrderedSet<E> comparing<E>({
9087
int Function(E a, E b)? compare,
91-
]) {
92-
return ComparingOrderedSet<E>(compare);
88+
bool strictMode = true,
89+
}) {
90+
return ComparingOrderedSet<E>(compare: compare, strictMode: strictMode);
9391
}
9492

9593
/// Creates an instance of [OrderedSet] using the [MappingOrderedSet]
9694
/// implementation and the provided [mappingFunction].
9795
static MappingOrderedSet<K, E> mapping<K extends Comparable<K>, E>(
98-
K Function(E a) mappingFunction,
99-
) {
100-
return MappingOrderedSet(mappingFunction);
96+
K Function(E a) mappingFunction, {
97+
bool strictMode = true,
98+
}) {
99+
return MappingOrderedSet(mappingFunction, strictMode: strictMode);
101100
}
102101

103102
/// Creates an instance of [OrderedSet] for items that are already
104103
/// [Comparable] using the [MappingOrderedSet] implementation.
105104
/// Use this for classes that implement [Comparable] of a different class.
106105
/// Equivalent to `mapping<K, E>((a) => a)`.
107106
static MappingOrderedSet<K, E>
108-
comparable<K extends Comparable<K>, E extends K>() {
109-
return mapping<K, E>((a) => a);
107+
comparable<K extends Comparable<K>, E extends K>({
108+
bool strictMode = true,
109+
}) {
110+
return mapping<K, E>((a) => a, strictMode: strictMode);
110111
}
111112

112113
/// Creates an instance of [OrderedSet] for items that are already
113114
/// [Comparable] of themselves, using the [MappingOrderedSet] implementation.
114115
/// Use this for classes that implement [Comparable] of themselves.
115116
/// 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, {
117+
static MappingOrderedSet<E, E> simple<E extends Comparable<E>>({
124118
bool strictMode = true,
125119
}) {
126-
return QueryableOrderedSet<E>(backingSet, strictMode: strictMode);
120+
return comparable<E, E>(strictMode: strictMode);
127121
}
128122
}
Lines changed: 14 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import 'package:ordered_set/ordered_set.dart';
22

3-
/// This is an implementation of [OrderedSet] that allows you to more
4-
/// efficiently [query] the list.
3+
/// This is a mixin to provide the query caching capabilities to both
4+
/// [OrderedSet] implementations.
55
///
66
/// You can [register] a set of queries, i.e., predefined sub-types, whose
77
/// results, i.e., subsets of this set, are then cached. Since the queries
@@ -21,26 +21,10 @@ import 'package:ordered_set/ordered_set.dart';
2121
///
2222
/// Note that you can change [strictMode] to allow for querying for unregistered
2323
/// types; if you do so, the registration cost is payed on the first query.
24-
class QueryableOrderedSet<E> extends OrderedSet<E> {
25-
/// Controls whether running an unregistered query throws an error or
26-
/// performs just-in-time filtering.
27-
final bool strictMode;
24+
mixin QueryableOrderedSetImpl<E> on OrderedSet<E> {
2825
final Map<Type, _CacheEntry<E, E>> _cache = {};
29-
final OrderedSet<E> _backingSet;
3026

31-
QueryableOrderedSet(
32-
this._backingSet, {
33-
this.strictMode = true,
34-
});
35-
36-
/// Adds a new cache for a subtype [C] of [E], allowing you to call [query].
37-
/// If the cache already exists this operation is a no-op.
38-
///
39-
/// If the set is not empty, the current elements will be re-sorted.
40-
///
41-
/// It is recommended to [register] all desired types at the beginning of
42-
/// your application to avoid recomputing the existing elements upon
43-
/// registration.
27+
@override
4428
void register<C extends E>() {
4529
if (isRegistered<C>()) {
4630
return;
@@ -50,17 +34,7 @@ class QueryableOrderedSet<E> extends OrderedSet<E> {
5034
);
5135
}
5236

53-
/// Allow you to find a subset of this set with all the elements `e` for
54-
/// which the condition `e is C` is true. This is equivalent to
55-
///
56-
/// ```dart
57-
/// orderedSet.whereType<C>()
58-
/// ```
59-
///
60-
/// except that it is O(0).
61-
///
62-
/// Note: you *must* call [register] for every type [C] you desire to use
63-
/// before calling this, or set [strictMode] to false.
37+
@override
6438
Iterable<C> query<C extends E>() {
6539
final result = _cache[C];
6640
if (result == null) {
@@ -92,51 +66,23 @@ class QueryableOrderedSet<E> extends OrderedSet<E> {
9266
return super.whereType<C>();
9367
}
9468

95-
/// Whether type [C] is registered as a cache
96-
bool isRegistered<C>() => _cache.containsKey(C);
97-
98-
@override
99-
int get length => _backingSet.length;
100-
101-
@override
102-
Iterator<E> get iterator => _backingSet.iterator;
103-
104-
@override
105-
Iterable<E> reversed() => _backingSet.reversed();
106-
107-
@override
108-
void rebalanceAll() {
109-
_backingSet.rebalanceAll();
110-
}
111-
11269
@override
113-
void rebalanceWhere(bool Function(E element) test) {
114-
_backingSet.rebalanceWhere(test);
115-
}
70+
bool isRegistered<C extends E>() => _cache.containsKey(C);
11671

117-
@override
118-
bool add(E t) {
119-
if (_backingSet.add(t)) {
120-
_cache.forEach((key, value) {
121-
if (value.check(t)) {
122-
value.data.add(t);
123-
}
124-
});
125-
return true;
126-
}
127-
return false;
72+
void onAdd(E t) {
73+
_cache.forEach((key, value) {
74+
if (value.check(t)) {
75+
value.data.add(t);
76+
}
77+
});
12878
}
12979

130-
@override
131-
bool remove(E e) {
80+
void onRemove(E e) {
13281
_cache.values.forEach((v) => v.data.remove(e));
133-
return _backingSet.remove(e);
13482
}
13583

136-
@override
137-
void clear() {
84+
void onClear() {
13885
_cache.values.forEach((v) => v.data.clear());
139-
_backingSet.clear();
14086
}
14187

14288
List<C> _filter<C extends E>() => whereType<C>().toList();

lib/read_only_ordered_set.dart

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import 'dart:collection';
2+
3+
abstract class ReadOnlyOrderedSet<E> extends IterableMixin<E> {
4+
/// The tree's elements in reversed order; should be cached when possible.
5+
Iterable<E> reversed();
6+
7+
/// Controls whether running an unregistered query throws an error or
8+
/// performs just-in-time filtering.
9+
bool get strictMode;
10+
11+
/// Whether type [C] is registered as a cache
12+
bool isRegistered<C extends E>();
13+
14+
/// Adds a new cache for a subtype [C] of [E], allowing you to call [query].
15+
/// If the cache already exists this operation is a no-op.
16+
///
17+
/// If the set is not empty, the current elements will be re-sorted.
18+
///
19+
/// It is recommended to [register] all desired types at the beginning of
20+
/// your application to avoid recomputing the existing elements upon
21+
/// registration.
22+
void register<C extends E>();
23+
24+
/// Allow you to find a subset of this set with all the elements `e` for
25+
/// which the condition `e is C` is true. This is equivalent to
26+
///
27+
/// ```dart
28+
/// orderedSet.whereType<C>()
29+
/// ```
30+
///
31+
/// except that it is O(0).
32+
///
33+
/// Note: you *must* call [register] for every type [C] you desire to use
34+
/// before calling this, or set [strictMode] to false.
35+
Iterable<C> query<C extends E>();
36+
}

0 commit comments

Comments
 (0)