Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
19 changes: 13 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>()` 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:
Expand All @@ -90,10 +88,19 @@ itself:
a `MappingOrderedSet` with identity mapping.
* `OrderedSet.simple<E>()`: if `E extends Comparable<E>`, this is an even simpler way of creating
a `MappingOrderedSet` with identity mapping.
* `OrderedSet.queryable<E>(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

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!

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
50 changes: 31 additions & 19 deletions lib/comparing_ordered_set.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,31 @@ 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).
late SplayTreeSet<Set<E>> _backingSet;
late int _length;
late final SplayTreeSet<Set<E>> _backingSet = SplayTreeSet<LinkedHashSet<E>>(
_outerComparator,
);
final int Function(E e1, E e2) _comparator;
int _length = 0;

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,22 +45,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]) {
final comparator = compare ?? _defaultCompare<E>();
_backingSet = SplayTreeSet<LinkedHashSet<E>>((Set<E> l1, Set<E> l2) {
if (l1.isEmpty) {
if (l2.isEmpty) {
return 0;
}
return -1;
}
if (l2.isEmpty) {
return 1;
}
return comparator(l1.first, l2.first);
});
_length = 0;
}
ComparingOrderedSet({
int Function(E e1, E e2)? compare,
this.strictMode = true,
}) : _comparator = compare ?? _defaultCompare<E>();

@override
int get length => _length;
Expand Down Expand Up @@ -81,6 +77,7 @@ class ComparingOrderedSet<E> extends OrderedSet<E> {
if (added) {
_length++;
_validReverseCache = false;
onAdd(e);
}
return added;
}
Expand Down Expand Up @@ -122,6 +119,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 +129,19 @@ class ComparingOrderedSet<E> extends OrderedSet<E> {
_validReverseCache = false;
_backingSet.clear();
_length = 0;
onClear();
}

int _outerComparator(Set<E> l1, Set<E> l2) {
if (l1.isEmpty) {
if (l2.isEmpty) {
return 0;
}
return -1;
}
if (l2.isEmpty) {
return 1;
}
return _comparator(l1.first, l2.first);
}
}
24 changes: 15 additions & 9 deletions lib/mapping_ordered_set.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,30 @@ 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;
final SplayTreeMap<K, Set<E>> _backingSet;
int _length = 0;

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

MappingOrderedSet(this._mappingFunction) {
_backingSet = SplayTreeMap((K k1, K k2) {
return k1.compareTo(k2);
});
_length = 0;
}
@override
final bool strictMode;

MappingOrderedSet(
this._mappingFunction, {
this.strictMode = true,
}) : _backingSet = SplayTreeMap((K k1, K k2) => k1.compareTo(k2));

@override
int get length => _length;
Expand All @@ -49,6 +52,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 +98,7 @@ class MappingOrderedSet<K extends Comparable<K>, E> extends OrderedSet<E> {
_backingSet.remove(key);
}
_validReverseCache = false;
onRemove(e);
}
return result;
}
Expand All @@ -103,5 +108,6 @@ class MappingOrderedSet<K extends Comparable<K>, E> extends OrderedSet<E> {
_validReverseCache = false;
_backingSet.clear();
_length = 0;
onClear();
}
}
52 changes: 28 additions & 24 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 @@ -67,12 +64,22 @@ abstract class OrderedSet<E> extends IterableMixin<E> {
/// Remove all elements that match the [test] condition; returns the removed
/// elements.
Iterable<E> 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<E> removeAll(Iterable<E> elements) {
return elements.where(remove).toList(growable: false);
final removed = <E>[];
for (final element in elements) {
if (remove(element)) {
removed.add(element);
}
}
return removed;
}

/// Removes the element at [index].
Expand All @@ -86,43 +93,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);
}
}
Loading