treap 0.4.0
treap: ^0.4.0 copied to clipboard
A persistent treap for Dart. A heap balanced randomized binary tree with efficient value semantics.
Treap #
A package implementing a persistent (immutable) order statistic tree by means of the treap data structure (a kind of cartesian tree). Apart from the regular binary tree operations, it allows for:
select(i)– find thei-th smallest element.rank(x)– find the rank of elementx, i.e. the number of elements smaller thanx.
both in O(log(N)) time.
The package also contains an implementation based on the related implicit treap (providing dynamic array-like functionality). Which has better computational complexity O(Log(N)) on:
insert(i, x)- insert elementxat indexiremove(i)- remove element at indexi.
than a regular dynamic array.
The implementation of both are made fully persistent by means of path copying. That is, any operation that would otherwise mutate a treap (or implicit treap), instead produces a new treap, and does so using only O(log(N)) extra space. This allow copies to be O(1) in time and space.
The package contains implementations of the standard Dart Set<T> and List<T> interfaces, built on top of this foundation.
Core Treap Structures #
Before diving into the Set and List implementations, it's useful to understand the core persistent structures:
Ordered Treap (TreapBase) #
This is the fundamental ordered treap implementation, providing:
- Basic Operations:
add,remove,find(lookup by value),has(contains) - Order Statistics:
rank(get index of value),select(get value at index) - Accessors:
first,last,firstOrDefault,lastOrDefault,prev,next - Properties:
size,isEmpty,values(ordered iterable) - Range Operations:
take,skip - Set Algebra:
union,intersection,difference - Immutability:
copy
Implicit Treap (ImplicitTreapBase) #
This structure uses treaps to implement a persistent list/array with efficient index-based operations:
- Basic Operations:
add(append),insert(at index),remove(at index),append(concatenate another implicit treap) - Accessors:
[](index operator),values(iterable) - Properties:
size - Range Operations:
take,skip - Immutability:
copy
These base structures are used internally by TreapSet and TreapList respectively.
TreapSet #
TreapSet<T> is a persistent, ordered set implementation based on the treap data structure. It implements Dart's Set<T> interface, providing familiar operations like:
- Adding/Removing:
add,addAll,remove,removeAll,retainAll,clear - Querying:
contains,lookup,length,isEmpty - Iteration:
iterator,forEach - Set Operations:
union,intersection,difference - Order-Based Operations:
first,last,elementAt,take,skip,takeWhile,skipWhile
Its performance characteristics differ from standard collections:
Performance Characteristics #
- Ordered Elements: Like
SplayTreeSet<T>, elements are kept in order. - Efficient Range/Order Operations:
take,skip, andelementAtare all O(log N), scaling better than standard sets. - Constant-Time Copying:
toSet()(creating a copy) is O(1) due to persistence. - Mutation Speed: Mutating operations (
add,remove) are generally slower (roughly 2x) thanSplayTreeSetdue to path copying overhead and the native implementation of standard collections.
This makes TreapSet particularly well-suited for scenarios where:
- Ordered iteration is required.
- Frequent
take,skip, orelementAtoperations are performed. - Immutable snapshots of the set are needed (e.g., for history or concurrency).
Benchmarks #
A benchmark suite comparing set operations is available in benchmark/set_main.dart:
# Run with Dart
dart run --no-enable-asserts benchmark/set_main.dart
# Or compile to native executable for better performance
dart compile exe benchmark/set_main.dart
./benchmark/set_main.exe
TreapSet Usage Example #
import 'package:treap/treap.dart';
void main() {
// Create sets
final set1 = TreapSet<int>();
final set2 = set1.add(1).add(2).add(3); // Immutable operations
// Original set remains unchanged
print(set1.isEmpty); // true
print(set2.length); // 3
// Efficient set operations
final set3 = TreapSet<int>.of([2, 3, 4]);
final union = set2.union(set3); // [1, 2, 3, 4]
final intersection = set2.intersection(set3); // [2, 3]
final difference = set2.difference(set3); // [1]
// Order statistics
print(set2.elementAt(1)); // 2 (O(log N) operation)
}
TreapList #
TreapList<T> is a persistent list implementation based on the implicit treap data structure. It implements Dart's List<T> interface, providing familiar operations like:
- Adding/Removing:
add,addAll,insert,insertAll,remove,removeAt,removeLast,removeWhere,retainWhere,clear - Accessing Elements:
[](index operator),first,last,elementAt - Querying:
length,isEmpty,isNotEmpty - Iteration:
iterator,forEach - Range Operations:
sublist,getRange,take,skip,takeWhile,skipWhile
Its performance characteristics differ significantly from the standard List:
Performance Characteristics #
- Logarithmic Access: Element access operations (
[], first, last) are O(log N), unlike standard List's O(1) - Efficient Insertions/Deletions:
insertandremoveare O(log N), significantly better than standard List's O(N) - Efficient Range Operations:
sublist,getRange,take, andskipare all O(log N)
This makes TreapList particularly well-suited for scenarios where:
- Elements are frequently inserted or removed at arbitrary positions
- The list is frequently sliced into sublists
- Immutability is required
Benchmarks #
A benchmark suite comparing list operations is available in benchmark/list_main.dart:
# Run with Dart
dart run --no-enable-asserts benchmark/list_main.dart
# Or compile to native executable for better performance
dart compile exe benchmark/list_main.dart
./benchmark/list_main.exe
TreapList Usage Example #
import 'package:treap/treap.dart';
void main() {
// Create a list
final list = TreapList<String>();
list.add("apple");
list.add("banana");
list.add("cherry");
// Efficient operations at any position
list.insert(1, "blueberry"); // O(log N) operation
final removed = list.removeAt(2); // O(log N) operation
// Efficient sublist operations
final sublist = list.sublist(1, 3); // O(log N) operation
print(list[0]); // "apple" - O(log N) access
}
Specialized Variants for Cross-Isolate Sharing #
This package includes specialized implementations optimized for cross-isolate communication using Dart's @pragma('vm:deeply-immutable') annotation:
Specialized Collections #
| Type | Purpose | Node Implementation |
|---|---|---|
TreapIntSet |
Set of integers | IntNode |
TreapStringSet |
Set of strings | StringNode |
TreapDoubleSet |
Set of doubles | DoubleNode |
TreapIntList |
List of integers | IntNode |
TreapStringList |
List of strings | StringNode |
TreapDoubleList |
List of doubles | DoubleNode |
Benefits of Specialized Variants #
- Efficient Sharing: The primary benefit is passing these collections to worker isolates without copying, crucial for large datasets or frequent isolate communication.
- Performance: Avoids serialization/deserialization overhead, speeding up multi-threaded applications.
- Safety: Deep immutability ensures safe concurrent access from multiple isolates.
While returning data from an isolate can also benefit, simple return values are often handled efficiently by mechanisms like Isolate.run, which automatically merges the heaps upon completion. The main advantage of these specialized types lies in efficiently providing input data to the isolate.
Creating Custom Deeply-Immutable Types #
For your own deeply-immutable types, you can reference the DeeplyImmutableNode class in lib/src/deeply_immutable_node.dart.
Note: Due to VM limitations,
@pragma('vm:deeply-immutable')can only be used onfinalorsealedclasses. You'll need to copy this implementation to your own project. Consider supporting this issue for future improvements.
Cross-Isolate Usage Example #
This example demonstrates sending a specialized TreapIntList (which is deeply immutable) to another isolate. The list is accessed directly in the new isolate without any copying.
import 'dart:isolate';
import 'package:treap/treap.dart';
// Function to be executed in the new isolate.
// It receives the TreapIntList directly.
void processReadOnlyInIsolate(TreapIntList dataList) {
// Access the list directly in the new isolate.
// No copying occurred when passing the list.
print('Isolate received list with length: ${dataList.length}');
if (dataList.isNotEmpty) {
print('Isolate accessing first element: ${dataList.first}');
}
// No need to send data back for this example.
}
void main() async {
// Create a specialized list in the main isolate.
final mainList = TreapIntList.of([10, 20, 30, 40, 50]);
print('Main isolate created list: ${mainList.join(', ')}');
// Spawn the isolate, passing the TreapIntList directly.
// Because TreapIntList is deeply immutable, it's passed by reference (no copy).
try {
await Isolate.spawn(processReadOnlyInIsolate, mainList);
print('Isolate spawned successfully.');
} catch (e) {
print('Error spawning isolate: $e');
}
// Give the isolate some time to run and print its output.
await Future.delayed(Duration(seconds: 1));
print('Main isolate finished.');
}
Example Application #
The package includes a Todo Application Example (Flutter) that demonstrates:
- Using persistent treaps for efficient undo/redo functionality
- Animating list view changes with persistent data structures
- Maintaining application state history without memory overhead
You can try the application in your browser if it supports WebAssembly Garbage Collection (WasmGC):
- Chrome 119+
- Firefox 120+
- Edge 119+