Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion benchmark/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import 'types.dart';

void main() {
OrderedSet<K> comparing<K>(Mapper<K> mapper) {
return OrderedSet.comparing<K>(Comparing.on(mapper));
return OrderedSet.comparing<K>(compare: Comparing.on(mapper));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dart doesn't allow named and optional parameters on the same function sadly:
https://dart.dev/language/functions

honestly I am considering just making the compare function mandatory, and then removing the "default compare" nastyness

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's quite nice to be able to just plug in Comparables without having to specify the compare function though, because that is supported today right?
Not sure if we should think of other users than Flame for this package, because maybe there are none. 😅

}

OrderedSet<K> priority<K>(Mapper<K> mapper) {
Expand Down
4 changes: 2 additions & 2 deletions example/ordered_set_example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ void main() {
items.add(1);
print(items.toList()); // [1, 2]

final a = OrderedSet.comparing<Person>((a, b) => a.age - b.age);
final a = OrderedSet.comparing<Person>(compare: (a, b) => a.age - b.age);
a.add(Person(12, 'Klaus'));
a.add(Person(1, 'Sunny'));
a.add(Person(14, 'Violet'));
Expand All @@ -25,7 +25,7 @@ void main() {
// use Comparing for simpler creation:
// sort by age desc and then name asc
final b = OrderedSet.comparing<Person>(
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));
Expand Down
15 changes: 13 additions & 2 deletions lib/comparing_ordered_set.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ 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.
///
/// 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<E> extends OrderedSet<E> {
class ComparingOrderedSet<E> extends OrderedSet<E>
with QueryableOrderedSetImpl<E> {
// 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).
Expand All @@ -20,6 +22,9 @@ class ComparingOrderedSet<E> extends OrderedSet<E> {
bool _validReverseCache = true;
Iterable<E> _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,
Expand All @@ -37,7 +42,10 @@ class ComparingOrderedSet<E> extends OrderedSet<E> {
///
/// 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<E>();
_backingSet = SplayTreeSet<LinkedHashSet<E>>((Set<E> l1, Set<E> l2) {
if (l1.isEmpty) {
Expand Down Expand Up @@ -81,6 +89,7 @@ class ComparingOrderedSet<E> extends OrderedSet<E> {
if (added) {
_length++;
_validReverseCache = false;
onAdd(e);
}
return added;
}
Expand Down Expand Up @@ -122,6 +131,7 @@ class ComparingOrderedSet<E> extends OrderedSet<E> {
// If the removal resulted in an empty bucket, remove the bucket as well.
_backingSet.remove(<E>{});
_validReverseCache = false;
onRemove(e);
}
return result;
}
Expand All @@ -131,5 +141,6 @@ class ComparingOrderedSet<E> extends OrderedSet<E> {
_validReverseCache = false;
_backingSet.clear();
_length = 0;
onClear();
}
}
12 changes: 10 additions & 2 deletions lib/mapping_ordered_set.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,27 @@ 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.
///
/// 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<K extends Comparable<K>, E> extends OrderedSet<E> {
class MappingOrderedSet<K extends Comparable<K>, E> extends OrderedSet<E>
with QueryableOrderedSetImpl<E> {
final K Function(E a) _mappingFunction;
late SplayTreeMap<K, Set<E>> _backingSet;
late int _length;

bool _validReverseCache = true;
Iterable<E> _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);
});
Expand Down Expand Up @@ -49,6 +54,7 @@ class MappingOrderedSet<K extends Comparable<K>, E> extends OrderedSet<E> {
if (added) {
_length++;
_validReverseCache = false;
onAdd(e);
}
return added;
}
Expand Down Expand Up @@ -94,6 +100,7 @@ class MappingOrderedSet<K extends Comparable<K>, E> extends OrderedSet<E> {
_backingSet.remove(key);
}
_validReverseCache = false;
onRemove(e);
}
return result;
}
Expand All @@ -103,5 +110,6 @@ class MappingOrderedSet<K extends Comparable<K>, E> extends OrderedSet<E> {
_validReverseCache = false;
_backingSet.clear();
_length = 0;
onClear();
}
}
38 changes: 16 additions & 22 deletions lib/ordered_set.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,15 @@ 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.
///
/// It accepts some way of comparing items for their priority. Unlike
/// [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<E> extends IterableMixin<E> {
/// The tree's elements in reversed order; should be cached when possible.
Iterable<E> reversed();

abstract class OrderedSet<E> extends ReadOnlyOrderedSet<E> {
/// 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.
Expand Down Expand Up @@ -86,43 +83,40 @@ abstract class OrderedSet<E> extends IterableMixin<E> {
///
/// This implementation will not store component priorities, so it is
/// susceptible to race conditions if priorities are changed while iterating.
static ComparingOrderedSet<E> comparing<E>([
static ComparingOrderedSet<E> comparing<E>({
int Function(E a, E b)? compare,
]) {
return ComparingOrderedSet<E>(compare);
bool strictMode = true,
}) {
return ComparingOrderedSet<E>(compare: compare, strictMode: strictMode);
}

/// Creates an instance of [OrderedSet] using the [MappingOrderedSet]
/// implementation and the provided [mappingFunction].
static MappingOrderedSet<K, E> mapping<K extends Comparable<K>, 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
/// [Comparable] using the [MappingOrderedSet] implementation.
/// Use this for classes that implement [Comparable] of a different class.
/// Equivalent to `mapping<K, E>((a) => a)`.
static MappingOrderedSet<K, E>
comparable<K extends Comparable<K>, E extends K>() {
return mapping<K, E>((a) => a);
comparable<K extends Comparable<K>, E extends K>({
bool strictMode = true,
}) {
return mapping<K, E>((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<K, K>((a) => a)`.
static MappingOrderedSet<E, E> simple<E extends Comparable<E>>() {
return comparable<E, E>();
}

/// Creates an instance of [OrderedSet] using the [QueryableOrderedSet]
/// by wrapping the provided [backingSet].
static QueryableOrderedSet<E> queryable<E>(
OrderedSet<E> backingSet, {
static MappingOrderedSet<E, E> simple<E extends Comparable<E>>({
bool strictMode = true,
}) {
return QueryableOrderedSet<E>(backingSet, strictMode: strictMode);
return comparable<E, E>(strictMode: strictMode);
}
}
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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<E> extends OrderedSet<E> {
/// Controls whether running an unregistered query throws an error or
/// performs just-in-time filtering.
final bool strictMode;
mixin QueryableOrderedSetImpl<E> on OrderedSet<E> {
final Map<Type, _CacheEntry<E, E>> _cache = {};
final OrderedSet<E> _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<C extends E>() {
if (isRegistered<C>()) {
return;
Expand All @@ -50,17 +34,7 @@ class QueryableOrderedSet<E> extends OrderedSet<E> {
);
}

/// 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<C>()
/// ```
///
/// 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<C> query<C extends E>() {
final result = _cache[C];
if (result == null) {
Expand Down Expand Up @@ -92,51 +66,23 @@ class QueryableOrderedSet<E> extends OrderedSet<E> {
return super.whereType<C>();
}

/// Whether type [C] is registered as a cache
bool isRegistered<C>() => _cache.containsKey(C);

@override
int get length => _backingSet.length;

@override
Iterator<E> get iterator => _backingSet.iterator;

@override
Iterable<E> reversed() => _backingSet.reversed();

@override
void rebalanceAll() {
_backingSet.rebalanceAll();
}

@override
void rebalanceWhere(bool Function(E element) test) {
_backingSet.rebalanceWhere(test);
}
bool isRegistered<C extends E>() => _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<C> _filter<C extends E>() => whereType<C>().toList();
Expand Down
36 changes: 36 additions & 0 deletions lib/read_only_ordered_set.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import 'dart:collection';

abstract class ReadOnlyOrderedSet<E> extends IterableMixin<E> {
/// The tree's elements in reversed order; should be cached when possible.
Iterable<E> 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<C extends E>();

/// 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<C extends E>();

/// 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<C>()
/// ```
///
/// 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<C> query<C extends E>();
}
Loading