From edc4e505804e0743084f195a4c6a84583786602b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Fri, 24 Nov 2023 17:42:25 +0100 Subject: [PATCH] Tweak docs again.. --- README.md | 8 +- lib/src/node.dart | 22 ++--- lib/src/treap_base.dart | 178 ++++++++++++++++++++++++++-------------- pubspec.yaml | 6 ++ test/treap_test.dart | 27 +++--- 5 files changed, 151 insertions(+), 90 deletions(-) diff --git a/README.md b/README.md index 98c41ab..6c41dbc 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -A library implementing a persistent treap data structure for Dart developers. +A package implementing a persistent treap data structure. ## Usage @@ -27,7 +27,11 @@ main() { } ``` -For something more significant see the [todo](example/) flutter app +For something more substantial see the [todo](example/) flutter app. + +It illustrates how to use a persistent treap to handle undo/redo and animate a list view. + +If your browser supports WasmGC (such as Chrome 119+, or Firefox 120+), you can try out the app [here](https://byolimit.github.io) ## Status diff --git a/lib/src/node.dart b/lib/src/node.dart index c1f8bd5..8c877b6 100644 --- a/lib/src/node.dart +++ b/lib/src/node.dart @@ -77,7 +77,7 @@ extension NodeEx> on Node? { int get size => this?._size ?? 0; Split split(T pivot) { - final self = this; + final self = this; // for type promotion if (self == null) return emptySplit(); final order = pivot.compareTo(self.item); if (order < 0) { @@ -92,10 +92,10 @@ extension NodeEx> on Node? { } Node? join(Node? other) { - final self = this; + final self = this; // for type promotion if (self != null && other != null) { assert(self.max.compareTo(other.min) < 0); - // two - ensure heap order + // two nodes - ensure heap order if (self.priority > other.priority) { return self.withRight(self.right.join(other)); } @@ -109,7 +109,7 @@ extension NodeEx> on Node? { Node? erase(T dead) => split(dead).withMiddle(null).join(); Node? union(Node? other) { - final self = this; + final self = this; // for type promotion if (self == null) return other; // {} | B == B final s = other.split(self.item); return newSplit( @@ -120,7 +120,7 @@ extension NodeEx> on Node? { } Node? intersection(Node? other) { - final self = this; + final self = this; // for type promotion if (self == null || other == null) return null; // {} & B == A & {} == {} final s = other.split(self.item); return newSplit( @@ -131,7 +131,7 @@ extension NodeEx> on Node? { } Node? difference(Node? other) { - final self = this; + final self = this; // for type promotion if (self == null) return null; // {} - B == {} if (other == null) return self; // A - {} == A final s = other.split(self.item); @@ -143,7 +143,7 @@ extension NodeEx> on Node? { } Node? find(T item) { - final self = this; + final self = this; // for type promotion if (self == null) return null; final order = item.compareTo(self.item); if (order < 0) return self.left?.find(item); @@ -152,7 +152,7 @@ extension NodeEx> on Node? { } int rank(T item) { - final self = this; + final self = this; // for type promotion if (self == null) return 0; final order = item.compareTo(self.item); if (order < 0) return self.left.rank(item); @@ -161,9 +161,9 @@ extension NodeEx> on Node? { return l; // order == 0 } - /// throws if out of bounds + /// Throws a [RangeError] if [rank] is out of bounds Node select(int rank) { - final self = this; + final self = this; // for type promotion if (self == null || rank < 0 || rank >= size) { throw RangeError.range(rank, 0, size - 1, 'rank'); } @@ -174,7 +174,7 @@ extension NodeEx> on Node? { } Iterable get values { - final self = this; + final self = this; // for type promotion if (self == null) return []; return self.inOrder().map((n) => n.item); } diff --git a/lib/src/treap_base.dart b/lib/src/treap_base.dart index 944f1ab..22651da 100644 --- a/lib/src/treap_base.dart +++ b/lib/src/treap_base.dart @@ -4,114 +4,166 @@ import 'node.dart'; final _rnd = Random(42); -/// Treap class -/// A treap is a type of binary search tree data structure that maintains a dynamic -/// set of ordered keys and allows binary search tree operations in addition to operations -/// like add, find, and erase in O(log n) time. The name 'Treap' is a portmanteau -/// of tree and heap, as the tree maintains its shape using heap properties. +/// A [fully persistent](https://en.wikipedia.org/wiki/Persistent_data_structure) +/// (immutable) implementation of a [Treap](https://en.wikipedia.org/wiki/Treap). +/// +/// A treap is a type of binary search tree. Each node in the tree is assigned a +/// uniformly distributed priority, either randomly or by a strong hash. +/// +/// Nodes in the tree are kept heap-ordered wrt. their priority. This ensures that +/// the shape of the tree is a random variable with the same probability distribution +/// as a random binary tree. Hence the name treap, which is a portmanteau of tree +/// and heap. +/// +/// In particular (with high probability), given a treap with `N` keys, the height +/// is `O(log(N))`, so that [find], [add], [erase], etc. also takes `O(log(N))` time +/// to perform. +/// +/// This particular implementation is made [persistent](https://en.wikipedia.org/wiki/Persistent_data_structure), +/// by means of path copying. +/// +/// Both [add] and [erase] has a space complexity of `O(log(N))`, due to path copying, +/// but erased nodes can later be reclaimed by the garbage collector, if the old +/// treaps containing them becomes eligible for reaping. class Treap> { final Node? _root; - const Treap._(this._root); - const Treap() : this._(null); - - /// The build method takes an iterable of items and constructs a treap from them. - /// It does this by folding over the items, adding each one to the treap in turn. - /// This method is O(N log(N)) in complexity. An O(N) algorithm exists if the items - /// are sorted. However, this method is simpler and works even if the items are not - /// sorted. - factory Treap.build(Iterable items) => - items.fold(Treap(), (acc, i) => acc.add(i)); - - /// Adds an item to the treap. Creates a new node with the item and a random priority. - /// The new node is then added to the root of the treap. Returns a new treap with the - /// added node. + /// The [Comparator] used to determine element order. + /// + /// Defaults to [Comparable.compare]. + final Comparator comparator; + + const Treap._(this._root, this.comparator); + + /// The empty [Treap] for a given [comparator]. + const Treap([Comparator? comparator]) + : this._(null, comparator ?? Comparable.compare); + + /// Build a treap containing the [items]. + /// + /// Constructs the treap by folding [add] over the [items], adding each one + /// to the treap in turn. + /// + /// This method is `O(N log(N))` in complexity. An `O(N)` algorithm exists if the + /// items are sorted. However, this works in all cases. + factory Treap.of(Iterable items, [Comparator? comparator]) => + items.fold(Treap(comparator), (acc, i) => acc.add(i)); + + /// Adds an [item]. + /// + /// Creates a new node with the [item] and a random priority. Returns a new treap + /// with the added node. Treap add(T item) => - Treap._(_root.add(Node(item, _rnd.nextInt(1 << 32)))); + Treap._(_root.add(Node(item, _rnd.nextInt(1 << 32))), comparator); - /// Erases an item from the treap. Returns a new treap without the erased item. - Treap erase(T item) => Treap._(_root.erase(item)); + /// Adds a range of [items]. + /// + /// Returns a new treap with the added [items]. + Treap addRange(Iterable items) => union(Treap.of(items, comparator)); - /// Checks if the treap is empty. Returns true if the treap is empty, false otherwise. + /// Erases an [item] from the treap, if it exists. + /// + /// Returns a new treap without the erased [item]. + Treap erase(T item) => Treap._(_root.erase(item), comparator); + + /// Whether this treap is empty. bool get isEmpty => _root == null; - /// Returns the size of the treap. + /// The size of this treap. int get size => _root.size; - /// Finds an item in the treap. Returns the item if found, null otherwise. + /// Finds the [item] in this treap. + /// + /// Returns the [T] in the treap, if any, that orders together with [item] by [comparator]. + /// Otherwise returns `null`. T? find(T item) => _root.find(item)?.item; - /// Checks if an item exists in the treap. Returns true if the item exists, false - /// otherwise. + /// Whether an[item] exists in this treap. + /// + /// Returns `true` if `find` re bool has(T item) => find(item) != null; - /// Returns the rank of an item in the treap. + /// The rank of an [item]. + /// + /// For an [item] in this treap, the rank is the index of the item. For an item not + /// in this treap, the rank is the index it would be at, if it was added. int rank(T item) => _root.rank(item); - /// Selects an item in the treap by its rank. Returns the selected item. - T select(int rank) => _root.select(rank).item; + /// Selects an item in this treap by its [index]. + T select(int index) => _root.select(index).item; - /// Returns all the values in the treap. + /// The values in this treap ordered by the [comparator]. Iterable get values => _root.values; - /// Returns the first item in the treap, or null if the treap is empty. + /// The first item in this treap, or `null` if it is empty. T? get firstOrDefault => _root?.min; - /// Returns the last item in the treap, or null if the treap is empty. + /// The last item in this treap, or `null` if it is empty. T? get lastOrDefault => _root?.max; - /// Returns the first item in the treap. Throws an error if the treap is empty. + /// The first item in this treap. + /// + /// Throws a [StateError] if it is empty. T get first => firstOrDefault ?? (throw StateError('No element')); - /// Returns the last item in the treap. Throws an error if the treap is empty. + /// The last item in this treap. + /// + /// Throws a [StateError] if it is empty. T get last => lastOrDefault ?? (throw StateError('No element')); - /// Returns the previous item in the treap for a given item. + /// The item preceding a given [item] in this treap. + /// + /// Throws a [RangeError] if no such item exists. Note that [item] need not be + /// contained this treap. T prev(T item) => _root.select(rank(item) - 1).item; - /// Returns the next item in the treap for a given item. + /// Returns the next item in the treap for a given [item]. + /// + /// [item] need not be contained this treap. + /// Throws a [RangeError] if no such item exists. T next(T item) => _root.select(rank(item) + 1).item; - /// Returns a new treap with the first n items. If n is greater than the size of - /// the treap, returns the original treap. - Treap take(int n) => - n < size ? Treap._(_root.split(_root.select(n).item).low) : this; - - /// Skips the first n items and returns a new treap with the remaining items. - /// If n is greater than or equal to the size of the treap, returns an empty treap. + /// Returns a new treap with the first [n] items. + /// + /// Returns the original treap, if [n] is greater than the [size] of this treap. + Treap take(int n) => n < size + ? Treap._(_root.split(_root.select(n).item).low, comparator) + : this; + + /// Skips the first [n] items and returns a new treap with the remaining items. + /// + /// Returns an empty treap, if [n] is greater than or equal to the [size] of this + /// treap. Treap skip(int n) { - if (n >= size) return Treap(); // empty + if (n >= size) return Treap(comparator); // empty final split = _root.split(_root.select(n).item); - return Treap._(split.high.union(split.middle)); + return Treap._(split.high.union(split.middle), comparator); } - /// Returns a new treap that is the union of this treap and another treap. - Treap union(Treap other) => Treap._(_root.union(other._root)); + /// Returns a new treap that is the union of this treap and the [other] treap. + Treap union(Treap other) => + Treap._(_root.union(other._root), comparator); - /// Returns a new treap that is the intersection of this treap and another treap. + /// Returns a new treap that is the intersection of this treap and the [other] treap. Treap intersection(Treap other) => - Treap._(_root.intersection(other._root)); + Treap._(_root.intersection(other._root), comparator); - /// Returns a new treap that is the difference of this treap and another treap. - Treap difference(Treap other) => Treap._(_root.difference(other._root)); + /// Returns a new treap that is the difference of this treap minus the [other] treap. + Treap difference(Treap other) => + Treap._(_root.difference(other._root), comparator); - /// Operator overloading for adding an item to the treap. Returns a new treap with - /// the added item. + /// Operator overload for [add]ing an [item] to the treap. Treap operator +(T item) => add(item); - /// Operator overloading for the union of this treap and another treap. - /// Returns a new treap that is the union of the two treaps. + /// Operator overload for the [union] of two treaps. Treap operator |(Treap other) => union(other); - /// Operator overloading for the intersection of this treap and another treap. - /// Returns a new treap that is the intersection of the two treaps. + /// Operator overload for the [intersection] of two treaps. Treap operator &(Treap other) => intersection(other); - /// Operator overloading for the difference of this treap and another treap. - /// Returns a new treap that is the difference of the two treaps. + /// Operator overload for the [difference] of two treaps. Treap operator -(Treap other) => difference(other); - /// Operator overloading for selecting an item in the treap by its rank. - /// Returns the selected item. - T operator [](int i) => select(i); + /// Operator overload for [select]ing an item in the treap by its [index]. + T operator [](int index) => select(index); } diff --git a/pubspec.yaml b/pubspec.yaml index 30f3cbf..8110d82 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,6 +5,12 @@ description: >- A heap balanced randomized binary tree with efficient value semantics. All operations are O(log n) worst case. +topics: + - treap + - data-structures + - collections + - functional + version: 1.0.0 homepage: https://github.com/nielsenko/treap diff --git a/test/treap_test.dart b/test/treap_test.dart index e51b677..42f6d3f 100644 --- a/test/treap_test.dart +++ b/test/treap_test.dart @@ -18,12 +18,12 @@ void main() { ..add(3); expect(x.values, [1]); - final big = Treap.build([9, 8, 7, 6, 1, 2, 3, 4, 5]..shuffle()); + final big = Treap.of([9, 8, 7, 6, 1, 2, 3, 4, 5]..shuffle()); expect(big.values, [1, 2, 3, 4, 5, 6, 7, 8, 9]); expect(big.erase(1).erase(0).erase(5).values, [2, 3, 4, 6, 7, 8, 9]); - final w = Treap.build([1]); + final w = Treap.of([1]); expect(x, isNot(w)); // equal items does not imply equality }); @@ -38,7 +38,7 @@ void main() { test('find, has, rank, select', () { const max = 1000; final items = [for (int i = 0; i < max; ++i) i]..shuffle(); - final t = Treap.build(items); + final t = Treap.of(items); for (final i in items) { expect(t.find(i), isNotNull); expect(t.rank(t.find(i)!), i); @@ -57,7 +57,7 @@ void main() { }); test('rank, select', () { - final top = Treap.build([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]..shuffle()); + final top = Treap.of([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]..shuffle()); expect(top.values, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); expect( top.values.map((i) => top.rank(i)), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); @@ -75,12 +75,12 @@ void main() { group('iterator', () { test('values', () { - final t = Treap.build([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]..shuffle()); + final t = Treap.of([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]..shuffle()); expect(t.values, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); }); test('take, skip', () { - final t = Treap.build([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]..shuffle()); + final t = Treap.of([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]..shuffle()); for (var i = 0; i < t.size + 10; ++i) { expect(t.take(i).values, t.values.take(i)); expect(t.skip(i).values, t.values.skip(i)); @@ -102,13 +102,12 @@ void main() { expect(() => empty.last, throwsStateError); expect(empty.lastOrDefault, null); - final single = Treap.build([1]); + final single = Treap.of([1]); expect(single.first, single.last); expect(single.first, single.firstOrDefault); expect(single.first, single.lastOrDefault); - final many = - Treap.build([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]..shuffle()); + final many = Treap.of([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]..shuffle()); expect(many.first, many.values.first); expect(many.first, 0); expect(many.last, many.values.last); @@ -116,7 +115,7 @@ void main() { }); test('prev, next', () { - final t = Treap.build([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]..shuffle()); + final t = Treap.of([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]..shuffle()); final l = t.values.toList(); for (var i = 1; i < l.length - 1; ++i) { expect(t.prev(t.select(i)), l[i - 1]); @@ -128,8 +127,8 @@ void main() { }); group('set algebra', () { - final x = Treap.build(['foo', 'bar']); - final y = Treap.build(['bar', 'mitzvah']); + final x = Treap.of(['foo', 'bar']); + final y = Treap.of(['bar', 'mitzvah']); test('union', () { expect(x.union(y).values, ['bar', 'foo', 'mitzvah']); @@ -152,8 +151,8 @@ void main() { final x = {for (int i = 0; i < max; ++i) rnd.nextInt(max)}; final y = {for (int i = 0; i < max; ++i) rnd.nextInt(max)}; - final tx = Treap.build(x); - final ty = Treap.build(y); + final tx = Treap.of(x); + final ty = Treap.of(y); expect((tx | ty).values, x.union(y)); expect((tx & ty).values, x.intersection(y));