From bd6524bc6aea16dd50117e6e47cdcd5848b74011 Mon Sep 17 00:00:00 2001 From: Jozef Brandys Date: Tue, 19 Aug 2014 12:10:41 +0200 Subject: [PATCH 001/214] Use sublist instead of creating new list for performance. --- .gitignore | 1 + lib/src/map_impl.dart | 3 +-- pubspec.lock | 43 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 pubspec.lock diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f9ced93 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +packages diff --git a/lib/src/map_impl.dart b/lib/src/map_impl.dart index b5e6556..da96f6d 100644 --- a/lib/src/map_impl.dart +++ b/lib/src/map_impl.dart @@ -447,8 +447,7 @@ class _SubMap extends _APersistentMap { int index = _popcount(_bitmap & (mask - 1)); if ((_bitmap & mask) != 0) { - List<_APersistentMap> newarray = - new List<_APersistentMap>.from(_array, growable: false); + List<_APersistentMap> newarray = _array.sublist(0); _APersistentMap m = _array[index]; _APersistentMap newM = m._insertWith(keyValues, size, combine, hash, depth + 1); diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..e4139c3 --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,43 @@ +# Generated by pub +# See http://pub.dartlang.org/doc/glossary.html#lockfile +packages: + args: + description: args + source: hosted + version: "0.9.0" + collection: + description: collection + source: hosted + version: "0.9.4" + enumerators: + description: enumerators + source: hosted + version: "0.5.3" + http: + description: http + source: hosted + version: "0.9.2+3" + path: + description: path + source: hosted + version: "1.3.0" + propcheck: + description: propcheck + source: hosted + version: "0.4.0" + rational: + description: rational + source: hosted + version: "0.1.5" + stack_trace: + description: stack_trace + source: hosted + version: "0.9.3+2" + unittest: + description: unittest + source: hosted + version: "0.9.3" + unmodifiable_collection: + description: unmodifiable_collection + source: hosted + version: "0.9.2+1" From fbcd57018657116b45f3e126a15957cfcd28f915 Mon Sep 17 00:00:00 2001 From: Jozef Brandys Date: Tue, 19 Aug 2014 14:42:10 +0200 Subject: [PATCH 002/214] Use ensureOwner where possible. --- lib/src/map.dart | 109 ++++++++---- lib/src/map_impl.dart | 369 +++++++++++++++++++++------------------- test/src/map_model.dart | 2 +- 3 files changed, 273 insertions(+), 207 deletions(-) diff --git a/lib/src/map.dart b/lib/src/map.dart index 7f7e33a..befa2e9 100644 --- a/lib/src/map.dart +++ b/lib/src/map.dart @@ -14,35 +14,40 @@ part of persistent; * In all the examples below `{k1: v1, k2: v2, ...}` is a shorthand for * `PersistentMap.fromMap({k1: v1, k2: v2, ...})`. */ -abstract class PersistentMap implements Iterable> { +class PersistentMap + extends IterableBase> + implements Iterable> { + NodeBase _root; /** Creates an empty [PersistentMap] using its default implementation. */ - factory PersistentMap() => new _EmptyMap(); + PersistentMap() { + this._root = new _EmptyMap(null); + } /** * Creates an immutable copy of [map] using the default implementation of * [PersistentMap]. */ - factory PersistentMap.fromMap(Map map) { - PersistentMap result = new _EmptyMap(); + PersistentMap.fromMap(Map map) { + _root = new _EmptyMap(null); map.forEach((K key, V value) { - result = result.insert(key, value); + _root = _root.insert(null, key, value); }); - return result; } /** * Creates a [PersistentMap] from an [Iterable] of [Pair]s using the default * implementation of [PersistentMap]. */ - factory PersistentMap.fromPairs(Iterable> pairs) { - PersistentMap result = new _EmptyMap(); + PersistentMap.fromPairs(Iterable> pairs) { + _root = new _EmptyMap(); pairs.forEach((pair) { - result = result.insert(pair.fst, pair.snd); + _root = _root.insert(null, pair.fst, pair.snd); }); - return result; } + PersistentMap._new(NodeBase this._root); + /** * Returns a new map identical to `this` except that it binds [key] to * [value]. @@ -57,7 +62,9 @@ abstract class PersistentMap implements Iterable> { * {'a': 1, 'b': 2}.insert('b', 3, (x,y) => x - y) == {'a': 3, 'b', -1} */ PersistentMap - insert(K key, V value, [V combine(V oldvalue, V newvalue)]); + insert(K key, V value, [V combine(V oldvalue, V newvalue)]) { + return new PersistentMap._new(_root.insert(null, key, value, combine)); + } /** * Returns a new map identical to `this` except that it doesn't bind [key] @@ -66,7 +73,9 @@ abstract class PersistentMap implements Iterable> { * {'a': 1, 'b': 2}.delete('b') == {'a': 1} * {'a': 1}.delete('b') == {'a': 1} */ - PersistentMap delete(K key); + PersistentMap delete(K key) { + return new PersistentMap._new(_root.delete(null, key)); + } /** * Looks up the value possibly bound to [key] in `this`. Returns @@ -75,18 +84,22 @@ abstract class PersistentMap implements Iterable> { * {'a': 1}.lookup('b') == Option.none() * {'a': 1, 'b': 2}.lookup('b') == Option.some(2) */ - Option lookup(K key); + Option lookup(K key) { + return _root.lookup(key); + } /** * Returns the value for the given [key] or [:null:] if [key] * is not in the map. */ - V operator [](K key); + V operator [](K key) { + return _root.lookup(key).asNullable; + } /** * Evaluates `f(key, value)` for each (`key`, `value`) pair in `this`. */ - void forEachKeyValue(f(K key, V value)); + void forEachKeyValue(f(K key, V value)) => _root.forEachKeyValue(f); /** * Returns a new map identical to `this` except that the value it possibly @@ -95,7 +108,9 @@ abstract class PersistentMap implements Iterable> { * {'a': 1, 'b': 2}.adjust('b', (x) => x + 1) == {'a', 1, 'b', 3} * {'a': 1}.adjust('b', (x) => x + 1) == {'a', 1} */ - PersistentMap adjust(K key, V update(V value)); + PersistentMap adjust(K key, V update(V value)) { + return new PersistentMap._new(_root.adjust(null, key, update)); + } /** * Returns a new map identical to `this` where each value has been updated by @@ -104,7 +119,9 @@ abstract class PersistentMap implements Iterable> { * {'a': 1, 'b': 2}.mapValues((x) => x + 1) == {'a', 2, 'b', 3} * {}.mapValues((x) => x + 1) == {} */ - PersistentMap mapValues(f(V value)); + PersistentMap mapValues(f(V value)) { + return new PersistentMap._new(_root.mapValues(null, f)); + } /** * Returns a new map whose (key, value) pairs are the union of those of `this` @@ -124,7 +141,8 @@ abstract class PersistentMap implements Iterable> { * if it is commutative. */ PersistentMap - union(PersistentMap other, [V combine(V left, V right)]); + union(PersistentMap other, [V combine(V left, V right)]) => + new PersistentMap._new(_root.union(null, other._root, combine)); /** * Returns a new map whose (key, value) pairs are the intersection of those of @@ -144,33 +162,46 @@ abstract class PersistentMap implements Iterable> { * provided and if it is commutative. */ PersistentMap - intersection(PersistentMap other, [V combine(V left, V right)]); + intersection(PersistentMap other, [V combine(V left, V right)]) => + new PersistentMap._new(_root.intersection(null, other._root, combine)); /// Returns a mutable copy of `this`. - Map toMap(); + Map toMap() { + return _root.toMap(); + } /// The keys of `this`. - Iterable get keys; + Iterable get keys => _root.keys; /// The values of `this`. - Iterable get values; + Iterable get values => _root.values; /// Randomly picks an entry of `this`. - Pair pickRandomEntry([Random random]); + Pair pickRandomEntry([Random random]) => _root.pickRandomEntry(random); /// A strict (non-lazy) version of [map]. - PersistentMap strictMap(Pair f(Pair pair)); + PersistentMap strictMap(Pair f(Pair pair)) => + new PersistentMap.fromPairs(this.map(f)); /// A strict (non-lazy) version of [where]. - PersistentMap strictWhere(bool f(Pair pair)); + PersistentMap strictWhere(bool f(Pair pair)) => + new PersistentMap.fromPairs(this.where(f)); + + Iterator get iterator => _root.iterator; + + int get length => _root.length; } /** * A base class for implementations of [PersistentMap]. */ -abstract class PersistentMapBase - extends IterableBase> - implements PersistentMap { +abstract class NodeBase + extends IterableBase> { + + int _length; + get length => _length; + + NodeBase(this._length); Map toMap() { Map result = new Map(); @@ -205,9 +236,23 @@ abstract class PersistentMapBase V operator [](K key) => this.lookup(key).asNullable; - PersistentMap strictMap(Pair f(Pair pair)) => - new PersistentMap.fromPairs(this.map(f)); + NodeBase + insert(Owner owner, K key, V value, [V combine(V oldvalue, V newvalue)]); + + NodeBase delete(Owner owner, K key); + + Option lookup(K key); + + void forEachKeyValue(f(K key, V value)); + + NodeBase adjust(Owner owner, K key, V update(V value)); + + NodeBase mapValues(Owner owner, f(V value)); + + NodeBase + union(Owner owner, NodeBase other, [V combine(V left, V right)]); + + NodeBase + intersection(Owner owner, NodeBase other, [V combine(V left, V right)]); - PersistentMap strictWhere(bool f(Pair pair)) => - new PersistentMap.fromPairs(this.where(f)); } diff --git a/lib/src/map_impl.dart b/lib/src/map_impl.dart index da96f6d..2b7dbfd 100644 --- a/lib/src/map_impl.dart +++ b/lib/src/map_impl.dart @@ -12,44 +12,46 @@ final _random = new Random(); */ class _Stop implements Exception {} +class Owner {} + /** * Superclass for _EmptyMap, _Leaf and _SubMap. */ -abstract class _APersistentMap extends PersistentMapBase { - final int length; +abstract class _ANodeBase extends NodeBase { + Owner _owner; - _APersistentMap(this.length, this.isEmpty, this._isLeaf); + _ANodeBase(this._owner, length, this.isEmpty, this._isLeaf) : super(length); final bool isEmpty; final bool _isLeaf; Option _lookup(K key, int hash, int depth); - PersistentMap _insertWith(LinkedList> keyValues, int size, + NodeBase _insertWith(Owner owner, LinkedList> keyValues, int size, V combine(V x, V y), int hash, int depth); - PersistentMap _intersectWith(LinkedList> keyValues, int size, + NodeBase _intersectWith(Owner owner, LinkedList> keyValues, int size, V combine(V x, V y), int hash, int depth); - PersistentMap _delete(K key, int hash, int depth); - PersistentMap _adjust(K key, V update(V), int hash, int depth); - - _APersistentMap - _unionWith(_APersistentMap m, V combine(V x, V y), int depth); - _APersistentMap - _unionWithEmptyMap(_EmptyMap m, V combine(V x, V y), int depth); - _APersistentMap - _unionWithLeaf(_Leaf m, V combine(V x, V y), int depth); - _APersistentMap - _unionWithSubMap(_SubMap m, V combine(V x, V y), int depth); - - _APersistentMap - _intersectionWith(_APersistentMap m, V combine(V x, V y), + NodeBase _delete(Owner owner, K key, int hash, int depth); + NodeBase _adjust(Owner owner, K key, V update(V), int hash, int depth); + + _ANodeBase + _unionWith(Owner owner, _ANodeBase m, V combine(V x, V y), int depth); + _ANodeBase + _unionWithEmptyMap(Owner owner, _EmptyMap m, V combine(V x, V y), int depth); + _ANodeBase + _unionWithLeaf(Owner owner, _Leaf m, V combine(V x, V y), int depth); + _ANodeBase + _unionWithSubMap(Owner owner, _SubMap m, V combine(V x, V y), int depth); + + _ANodeBase + _intersectionWith(Owner owner, _ANodeBase m, V combine(V x, V y), int depth); - _APersistentMap - _intersectionWithEmptyMap(_EmptyMap m, V combine(V x, V y), + _ANodeBase + _intersectionWithEmptyMap(Owner owner, _EmptyMap m, V combine(V x, V y), int depth); - _APersistentMap - _intersectionWithLeaf(_Leaf m, V combine(V x, V y), int depth); - _APersistentMap - _intersectionWithSubMap(_SubMap m, V combine(V x, V y), int depth); + _ANodeBase + _intersectionWithLeaf(Owner owner, _Leaf m, V combine(V x, V y), int depth); + _ANodeBase + _intersectionWithSubMap(Owner owner, _SubMap m, V combine(V x, V y), int depth); Pair _elementAt(int index); @@ -59,24 +61,24 @@ abstract class _APersistentMap extends PersistentMapBase { Option lookup(K key) => _lookup(key, key.hashCode & 0x3fffffff, 0); - PersistentMap insert(K key, V value, [V combine(V x, V y)]) => - _insertWith(_onePair(key, value), + NodeBase insert(Owner owner, K key, V value, [V combine(V x, V y)]) => + _insertWith(owner, _onePair(key, value), 1, (combine != null) ? combine : (V x, V y) => y, key.hashCode & 0x3fffffff, 0); - PersistentMap delete(K key) => - _delete(key, key.hashCode & 0x3fffffff, 0); + NodeBase delete(Owner owner, K key) => + _delete(owner ,key, key.hashCode & 0x3fffffff, 0); - PersistentMap adjust(K key, V update(V)) => - _adjust(key, update, key.hashCode & 0x3fffffff, 0); + NodeBase adjust(Owner owner, K key, V update(V)) => + _adjust(owner, key, update, key.hashCode & 0x3fffffff, 0); - PersistentMap union(PersistentMap other, [V combine(V x, V y)]) => - this._unionWith(other, (combine != null) ? combine : (V x, V y) => y, 0); + NodeBase union(Owner owner, NodeBase other, [V combine(V x, V y)]) => + this._unionWith(owner, other, (combine != null) ? combine : (V x, V y) => y, 0); - PersistentMap - intersection(PersistentMap other, [V combine(V left, V right)]) => - this._intersectionWith(other, + NodeBase + intersection(Owner owner, NodeBase other, [V combine(V left, V right)]) => + this._intersectionWith(owner, other, (combine != null) ? combine : (V x, V y) => y, 0); Pair elementAt(int index) { @@ -93,63 +95,63 @@ class _EmptyMapIterator implements Iterator> { bool moveNext() => false; } -class _EmptyMap extends _APersistentMap { - _EmptyMap() : super(0, true, false); +class _EmptyMap extends _ANodeBase { + _EmptyMap(Owner owner) : super(owner, 0, true, false); Option _lookup(K key, int hash, int depth) => new Option.none(); - PersistentMap _insertWith( + NodeBase _insertWith(Owner owner, LinkedList> keyValues, int size, V combine(V x, V y), int hash, int depth) { assert(size == keyValues.length); - return new _Leaf(hash, keyValues, size); + return new _Leaf.abc(owner, hash, keyValues, size); } - PersistentMap _intersectWith(LinkedList> keyValues, int size, + NodeBase _intersectWith(Owner owner, LinkedList> keyValues, int size, V combine(V x, V y), int hash, int depth) { assert(size == keyValues.length); return this; } - PersistentMap _delete(K key, int hash, int depth) => this; + NodeBase _delete(Owner owner, K key, int hash, int depth) => this; - PersistentMap _adjust(K key, V update(V), int hash, int depth) => this; + NodeBase _adjust(Owner owner, K key, V update(V), int hash, int depth) => this; - PersistentMap - _unionWith(PersistentMap m, V combine(V x, V y), int depth) => m; + NodeBase + _unionWith(Owner owner, NodeBase m, V combine(V x, V y), int depth) => m; - PersistentMap - _unionWithEmptyMap(_EmptyMap m, V combine(V x, V y), int depth) { + NodeBase + _unionWithEmptyMap(Owner owner, _EmptyMap m, V combine(V x, V y), int depth) { throw "should never be called"; } - PersistentMap - _unionWithLeaf(_Leaf m, V combine(V x, V y), int depth) => m; + NodeBase + _unionWithLeaf(Owner owner, _Leaf m, V combine(V x, V y), int depth) => m; - PersistentMap - _unionWithSubMap(_SubMap m, V combine(V x, V y), int depth) => m; + NodeBase + _unionWithSubMap(Owner owner, _SubMap m, V combine(V x, V y), int depth) => m; - PersistentMap - _intersectionWith(_APersistentMap m, V combine(V x, V y), + NodeBase + _intersectionWith(Owner owner, _ANodeBase m, V combine(V x, V y), int depth) => this; - PersistentMap - _intersectionWithEmptyMap(_EmptyMap m, V combine(V x, V y), + NodeBase + _intersectionWithEmptyMap(Owner owner, _EmptyMap m, V combine(V x, V y), int depth) { throw "should never be called"; } - PersistentMap _intersectionWithLeaf( + NodeBase _intersectionWithLeaf(Owner owner, _Leaf m, V combine(V x, V y), int depth) => this; - PersistentMap _intersectionWithSubMap( + NodeBase _intersectionWithSubMap(Owner owner, _SubMap m, V combine(V x, V y), int depth) => this; - PersistentMap mapValues(f(V)) => this; + NodeBase mapValues(Owner owner, f(V)) => this; void forEachKeyValue(f(K, V)) {} - bool operator ==(PersistentMap other) => other is _EmptyMap; + bool operator ==(NodeBase other) => other is _EmptyMap; Iterator> get iterator => const _EmptyMapIterator(); @@ -164,16 +166,26 @@ class _EmptyMap extends _APersistentMap { toDebugString() => "_EmptyMap()"; } -class _Leaf extends _APersistentMap { +class _Leaf extends _ANodeBase { int _hash; LinkedList> _pairs; - _Leaf(this._hash, pairs, int size) : super(size, false, true) { + _Leaf.abc(Owner owner, this._hash, pairs, int size) : super(owner, size, false, true) { this._pairs = pairs; assert(size == pairs.length); } - PersistentMap _insertWith(LinkedList> keyValues, int size, + factory _Leaf.ensureOwner(_Leaf old, Owner owner, hash, pairs, int size) { + if(owner != null && owner == old._owner) { + old._pairs = pairs; + old._hash = hash; + old._length = size; + return old; + } + return new _Leaf.abc(owner, hash, pairs, size); + } + + NodeBase _insertWith(Owner owner, LinkedList> keyValues, int size, V combine(V x, V y), int hash, int depth) { assert(size == keyValues.length); // newsize is incremented as a side effect of insertPair @@ -218,21 +230,21 @@ class _Leaf extends _APersistentMap { if (depth > 5) { assert(_hash == hash); final LinkedList> newPairs = insertPairs(keyValues, _pairs); - return new _Leaf(hash, newPairs, newsize); + return new _Leaf.ensureOwner(this, owner, hash, newPairs, newsize); } else { if (hash == _hash) { final LinkedList> newPairs = insertPairs(keyValues, _pairs); - return new _Leaf(hash, newPairs, newsize); + return new _Leaf.ensureOwner(this, owner, hash, newPairs, newsize); } else { int branch = (_hash >> (depth * 5)) & 0x1f; - List<_APersistentMap> array = <_APersistentMap>[this]; - return new _SubMap(1 << branch, array, length) - ._insertWith(keyValues, size, combine, hash, depth); + List<_ANodeBase> array = <_ANodeBase>[this]; + return new _SubMap.abc(owner, 1 << branch, array, length) + ._insertWith(owner, keyValues, size, combine, hash, depth); } } } - PersistentMap _intersectWith(LinkedList> keyValues, int size, + NodeBase _intersectWith(Owner owner, LinkedList> keyValues, int size, V combine(V x, V y), int hash, int depth) { assert(size == keyValues.length); // TODO(polux): possibly faster implementation @@ -245,10 +257,10 @@ class _Leaf extends _APersistentMap { newsize++; } }); - return new _Leaf(_hash, builder.build(), newsize); + return new _Leaf.ensureOwner(this, owner, _hash, builder.build(), newsize); } - PersistentMap _delete(K key, int hash, int depth) { + NodeBase _delete(Owner owner, K key, int hash, int depth) { if (hash != _hash) return this; bool found = false; @@ -260,11 +272,11 @@ class _Leaf extends _APersistentMap { return true; }); return newPairs.isNil - ? new _EmptyMap() - : new _Leaf(_hash, newPairs, found ? length - 1 : length); + ? new _EmptyMap(owner) + : new _Leaf.ensureOwner(this, owner, _hash, newPairs, found ? length - 1 : length); } - PersistentMap _adjust(K key, V update(V), int hash, int depth) { + NodeBase _adjust(Owner owner, K key, V update(V), int hash, int depth) { LinkedList> adjustPairs() { LinkedListBuilder> builder = new LinkedListBuilder>(); @@ -284,41 +296,41 @@ class _Leaf extends _APersistentMap { return (hash != _hash) ? this - : new _Leaf(_hash, adjustPairs(), length); + : new _Leaf.ensureOwner(this, owner, _hash, adjustPairs(), length); } - PersistentMap - _unionWith(_APersistentMap m, V combine(V x, V y), int depth) => - m._unionWithLeaf(this, combine, depth); + NodeBase + _unionWith(Owner owner, _ANodeBase m, V combine(V x, V y), int depth) => + m._unionWithLeaf(owner, this, combine, depth); - PersistentMap - _unionWithEmptyMap(_EmptyMap m, V combine(V x, V y), int depth) => + NodeBase + _unionWithEmptyMap(Owner owner, _EmptyMap m, V combine(V x, V y), int depth) => this; - PersistentMap - _unionWithLeaf(_Leaf m, V combine(V x, V y), int depth) => - m._insertWith(_pairs, length, combine, _hash, depth); + NodeBase + _unionWithLeaf(Owner owner, _Leaf m, V combine(V x, V y), int depth) => + m._insertWith(owner, _pairs, length, combine, _hash, depth); - PersistentMap - _unionWithSubMap(_SubMap m, V combine(V x, V y), int depth) => - m._insertWith(_pairs, length, combine, _hash, depth); + NodeBase + _unionWithSubMap(Owner owner, _SubMap m, V combine(V x, V y), int depth) => + m._insertWith(owner, _pairs, length, combine, _hash, depth); - PersistentMap _intersectionWith(_APersistentMap m, + NodeBase _intersectionWith(Owner owner, _ANodeBase m, V combine(V x, V y), int depth) => - m._intersectionWithLeaf(this, combine, depth); + m._intersectionWithLeaf(owner, this, combine, depth); - PersistentMap _intersectionWithEmptyMap(_EmptyMap m, + NodeBase _intersectionWithEmptyMap(Owner owner, _EmptyMap m, V combine(V x, V y), int depth) => m; - PersistentMap _intersectionWithLeaf(_Leaf m, V combine(V x, V y), + NodeBase _intersectionWithLeaf(Owner owner, _Leaf m, V combine(V x, V y), int depth) => - m._intersectWith(_pairs, length, combine, _hash, depth); + m._intersectWith(owner, _pairs, length, combine, _hash, depth); - PersistentMap _intersectionWithSubMap(_SubMap m, + NodeBase _intersectionWithSubMap(Owner owner, _SubMap m, V combine(V x, V y), int depth) => - m._intersectWith(_pairs, length, combine, _hash, depth); + m._intersectWith(owner, _pairs, length, combine, _hash, depth); Option _lookup(K key, int hash, int depth) { if (hash != _hash) @@ -333,15 +345,15 @@ class _Leaf extends _APersistentMap { return new Option.none(); } - PersistentMap mapValues(f(V)) => - new _Leaf(_hash, + NodeBase mapValues(Owner owner, f(V)) => + new _Leaf.ensureOwner(this, owner, _hash, _pairs.strictMap((p) => new Pair(p.fst, f(p.snd))), length); void forEachKeyValue(f(K, V)) { _pairs.foreach((Pair pair) => f(pair.fst, pair.snd)); } - bool operator ==(PersistentMap other) { + bool operator ==(NodeBase other) { if (identical(this, other)) return true; if (other is! _Leaf) return false; _Leaf otherLeaf = other; @@ -385,7 +397,7 @@ class _Leaf extends _APersistentMap { } class _SubMapIterator implements Iterator> { - List<_APersistentMap> _array; + List<_ANodeBase> _array; int _index = 0; // invariant: _currentIterator != null => _currentIterator.current != null Iterator> _currentIterator = null; @@ -411,11 +423,20 @@ class _SubMapIterator implements Iterator> { } } -class _SubMap extends _APersistentMap { +class _SubMap extends _ANodeBase { int _bitmap; - List<_APersistentMap> _array; + List<_ANodeBase> _array; - _SubMap(this._bitmap, this._array, int size) : super(size, false, false); + _SubMap.abc(Owner owner, this._bitmap, this._array, int size) : super(owner, size, false, false); + + factory _SubMap.ensureOwner(_SubMap old, Owner owner, bitmap, array, int size) { + if(owner != null && owner == old._owner) { + old._bitmap = bitmap; + old._array = array; + old._length = size; + } + return new _SubMap.abc(owner , bitmap, array, size); + } static _popcount(int n) { n = n - ((n >> 1) & 0x55555555); @@ -431,14 +452,14 @@ class _SubMap extends _APersistentMap { int mask = 1 << branch; if ((_bitmap & mask) != 0) { int index = _popcount(_bitmap & (mask - 1)); - _APersistentMap map = _array[index]; + _ANodeBase map = _array[index]; return map._lookup(key, hash, depth + 1); } else { return new Option.none(); } } - PersistentMap _insertWith(LinkedList> keyValues, int size, + NodeBase _insertWith(Owner owner, LinkedList> keyValues, int size, V combine(V x, V y), int hash, int depth) { assert(size == keyValues.length); @@ -447,26 +468,26 @@ class _SubMap extends _APersistentMap { int index = _popcount(_bitmap & (mask - 1)); if ((_bitmap & mask) != 0) { - List<_APersistentMap> newarray = _array.sublist(0); - _APersistentMap m = _array[index]; - _APersistentMap newM = - m._insertWith(keyValues, size, combine, hash, depth + 1); + List<_ANodeBase> newarray = _array.sublist(0); + _ANodeBase m = _array[index]; + _ANodeBase newM = + m._insertWith(owner, keyValues, size, combine, hash, depth + 1); newarray[index] = newM; int delta = newM.length - m.length; - return new _SubMap(_bitmap, newarray, length + delta); + return new _SubMap.ensureOwner(this, owner, _bitmap, newarray, length + delta); } else { int newlength = _array.length + 1; - List<_APersistentMap> newarray = - new List<_APersistentMap>(newlength); + List<_ANodeBase> newarray = + new List<_ANodeBase>(newlength); // TODO: find out if there's a "copy array" native function somewhere for (int i = 0; i < index; i++) { newarray[i] = _array[i]; } for (int i = index; i < newlength - 1; i++) { newarray[i+1] = _array[i]; } - newarray[index] = new _Leaf(hash, keyValues, size); - return new _SubMap(_bitmap | mask, newarray, length + size); + newarray[index] = new _Leaf.abc(owner, hash, keyValues, size); + return new _SubMap.ensureOwner(this, owner, _bitmap | mask, newarray, length + size); } } - PersistentMap _intersectWith(LinkedList> keyValues, int size, + NodeBase _intersectWith(Owner owner, LinkedList> keyValues, int size, V combine(V x, V y), int hash, int depth) { assert(size == keyValues.length); @@ -475,21 +496,21 @@ class _SubMap extends _APersistentMap { if ((_bitmap & mask) != 0) { int index = _popcount(_bitmap & (mask - 1)); - _APersistentMap m = _array[index]; - return m._intersectWith(keyValues, size, combine, hash, depth + 1); + _ANodeBase m = _array[index]; + return m._intersectWith(owner, keyValues, size, combine, hash, depth + 1); } else { - return new _EmptyMap(); + return new _EmptyMap(owner); } } - PersistentMap _delete(K key, int hash, int depth) { + NodeBase _delete(owner, K key, int hash, int depth) { int branch = (hash >> (depth * 5)) & 0x1f; int mask = 1 << branch; if ((_bitmap & mask) != 0) { int index = _popcount(_bitmap & (mask - 1)); - _APersistentMap m = _array[index]; - _APersistentMap newm = m._delete(key, hash, depth + 1); + _ANodeBase m = _array[index]; + _ANodeBase newm = m._delete(owner, key, hash, depth + 1); int delta = newm.length - m.length; if (identical(m, newm)) { return this; @@ -497,101 +518,101 @@ class _SubMap extends _APersistentMap { if (newm.isEmpty) { if (_array.length > 2) { int newsize = _array.length - 1; - List<_APersistentMap> newarray = - new List<_APersistentMap>(newsize); + List<_ANodeBase> newarray = + new List<_ANodeBase>(newsize); for (int i = 0; i < index; i++) { newarray[i] = _array[i]; } for (int i = index; i < newsize; i++) { newarray[i] = _array[i + 1]; } assert(newarray.length >= 2); - return new _SubMap(_bitmap ^ mask, newarray, length + delta); + return new _SubMap.ensureOwner(this, owner, _bitmap ^ mask, newarray, length + delta); } else { assert(_array.length == 2); assert(index == 0 || index == 1); - _APersistentMap onlyValueLeft = _array[1 - index]; + _ANodeBase onlyValueLeft = _array[1 - index]; return onlyValueLeft._isLeaf ? onlyValueLeft - : new _SubMap(_bitmap ^ mask, - <_APersistentMap>[onlyValueLeft], + : new _SubMap.ensureOwner(this, owner, _bitmap ^ mask, + <_ANodeBase>[onlyValueLeft], length + delta); } } else if (newm._isLeaf){ if (_array.length == 1) { return newm; } else { - List<_APersistentMap> newarray = - new List<_APersistentMap>.from(_array, growable: false); + List<_ANodeBase> newarray = + new List<_ANodeBase>.from(_array, growable: false); newarray[index] = newm; - return new _SubMap(_bitmap, newarray, length + delta); + return new _SubMap.ensureOwner(this, owner, _bitmap, newarray, length + delta); } } else { - List<_APersistentMap> newarray = - new List<_APersistentMap>.from(_array, growable: false); + List<_ANodeBase> newarray = + new List<_ANodeBase>.from(_array, growable: false); newarray[index] = newm; - return new _SubMap(_bitmap, newarray, length + delta); + return new _SubMap.ensureOwner(this, owner, _bitmap, newarray, length + delta); } } else { return this; } } - PersistentMap _adjust(K key, V update(V), int hash, int depth) { + NodeBase _adjust(Owner owner, K key, V update(V), int hash, int depth) { int branch = (hash >> (depth * 5)) & 0x1f; int mask = 1 << branch; if ((_bitmap & mask) != 0) { int index = _popcount(_bitmap & (mask - 1)); - _APersistentMap m = _array[index]; - _APersistentMap newm = m._adjust(key, update, hash, depth + 1); + _ANodeBase m = _array[index]; + _ANodeBase newm = m._adjust(owner, key, update, hash, depth + 1); if (identical(newm, m)) { return this; } - List<_APersistentMap> newarray = - new List<_APersistentMap>.from(_array, growable: false); + List<_ANodeBase> newarray = + new List<_ANodeBase>.from(_array, growable: false); newarray[index] = newm; - return new _SubMap(_bitmap, newarray, length); + return new _SubMap.ensureOwner(this, owner, _bitmap, newarray, length); } else { return this; } } - PersistentMap - _unionWith(_APersistentMap m, V combine(V x, V y), int depth) => - m._unionWithSubMap(this, combine, depth); + NodeBase + _unionWith(Owner owner, _ANodeBase m, V combine(V x, V y), int depth) => + m._unionWithSubMap(owner, this, combine, depth); - PersistentMap - _unionWithEmptyMap(_EmptyMap m, V combine(V x, V y), int depth) => + NodeBase + _unionWithEmptyMap(Owner owner, _EmptyMap m, V combine(V x, V y), int depth) => this; - PersistentMap - _unionWithLeaf(_Leaf m, V combine(V x, V y), int depth) => - this._insertWith(m._pairs, m.length, (V v1, V v2) => combine(v2, v1), + NodeBase + _unionWithLeaf(Owner owner, _Leaf m, V combine(V x, V y), int depth) => + this._insertWith(owner, m._pairs, m.length, (V v1, V v2) => combine(v2, v1), m._hash, depth); - PersistentMap - _unionWithSubMap(_SubMap m, V combine(V x, V y), int depth) { + NodeBase + _unionWithSubMap(Owner owner, _SubMap m, V combine(V x, V y), int depth) { int ormap = _bitmap | m._bitmap; int andmap = _bitmap & m._bitmap; - List<_APersistentMap> newarray = - new List<_APersistentMap>(_popcount(ormap)); + List<_ANodeBase> newarray = + new List<_ANodeBase>(_popcount(ormap)); int mask = 1, i = 0, i1 = 0, i2 = 0; int newSize = 0; while (mask <= ormap) { if ((andmap & mask) != 0) { _array[i1]; m._array[i2]; - _APersistentMap newMap = - m._array[i2]._unionWith(_array[i1], combine, depth + 1); + _ANodeBase newMap = + m._array[i2]._unionWith(owner, _array[i1], combine, depth + 1); newarray[i] = newMap; newSize += newMap.length; i1++; i2++; i++; } else if ((_bitmap & mask) != 0) { - _APersistentMap newMap = _array[i1]; + _ANodeBase newMap = _array[i1]; newarray[i] = newMap; newSize += newMap.length; i1++; i++; } else if ((m._bitmap & mask) != 0) { - _APersistentMap newMap = m._array[i2]; + _ANodeBase newMap = m._array[i2]; newarray[i] = newMap; newSize += newMap.length; i2++; @@ -599,27 +620,27 @@ class _SubMap extends _APersistentMap { } mask <<= 1; } - return new _SubMap(ormap, newarray, newSize); + return new _SubMap.ensureOwner(this, owner, ormap, newarray, newSize); } - PersistentMap _intersectionWith(_APersistentMap m, + NodeBase _intersectionWith(Owner owner, _ANodeBase m, V combine(V x, V y), int depth) => - m._intersectionWithSubMap(this, combine, depth); + m._intersectionWithSubMap(owner, this, combine, depth); - PersistentMap _intersectionWithEmptyMap(_EmptyMap m, + NodeBase _intersectionWithEmptyMap(Owner owner, _EmptyMap m, V combine(V x, V y), int depth) => m; - PersistentMap _intersectionWithLeaf(_Leaf m, V combine(V x, V y), + NodeBase _intersectionWithLeaf(Owner owner, _Leaf m, V combine(V x, V y), int depth) => - _intersectWith(m._pairs, m.length, (V v1, V v2) => combine(v2, v1), + _intersectWith(owner, m._pairs, m.length, (V v1, V v2) => combine(v2, v1), m._hash, depth); - PersistentMap _intersectionWithSubMap( + NodeBase _intersectionWithSubMap(Owner owner, _SubMap m, V combine(V x, V y), int depth) { int andmap = _bitmap & m._bitmap; - List<_APersistentMap> newarray = new List<_APersistentMap>(); + List<_ANodeBase> newarray = new List<_ANodeBase>(); int mask = 1, i1 = 0, i2 = 0; int newSize = 0; int newMask = 0; @@ -627,8 +648,8 @@ class _SubMap extends _APersistentMap { if ((andmap & mask) != 0) { _array[i1]; m._array[i2]; - _APersistentMap newMap = - m._array[i2]._intersectionWith(_array[i1], combine, depth + 1); + _ANodeBase newMap = + m._array[i2]._intersectionWith(owner, _array[i1], combine, depth + 1); newarray.add(newMap); newSize += newMap.length; newMask |= mask; @@ -642,32 +663,32 @@ class _SubMap extends _APersistentMap { mask <<= 1; } if (newarray.length > 1) { - return new _SubMap(newMask, newarray, newSize); + return new _SubMap.ensureOwner(this, owner, newMask, newarray, newSize); } else if (newarray.length == 1) { - _APersistentMap onlyValueLeft = newarray[0]; + _ANodeBase onlyValueLeft = newarray[0]; return onlyValueLeft._isLeaf ? onlyValueLeft - : new _SubMap(newMask, newarray, newSize); + : new _SubMap.ensureOwner(this, owner, newMask, newarray, newSize); } else { - return new _EmptyMap(); + return new _EmptyMap(owner); } } - PersistentMap mapValues(f(V)) { - List<_APersistentMap> newarray = - new List<_APersistentMap>.from(_array, growable: false); + NodeBase mapValues(Owner owner, f(V)) { + List<_ANodeBase> newarray = + new List<_ANodeBase>.from(_array, growable: false); for (int i = 0; i < _array.length; i++) { - _APersistentMap mi = _array[i]; - newarray[i] = mi.mapValues(f); + _ANodeBase mi = _array[i]; + newarray[i] = mi.mapValues(owner, f); } - return new _SubMap(_bitmap, newarray, length); + return new _SubMap.ensureOwner(this, owner, _bitmap, newarray, length); } forEachKeyValue(f(K, V)) { _array.forEach((mi) => mi.forEachKeyValue(f)); } - bool operator ==(PersistentMap other) { + bool operator ==(NodeBase other) { if (identical(this, other)) return true; if (other is! _SubMap) return false; _SubMap otherSubMap = other; @@ -675,8 +696,8 @@ class _SubMap extends _APersistentMap { if (length != otherSubMap.length) return false; assert(_array.length == otherSubMap._array.length); for (int i = 0; i < _array.length; i++) { - _APersistentMap mi = _array[i]; - _APersistentMap omi = otherSubMap._array[i]; + _ANodeBase mi = _array[i]; + _ANodeBase omi = otherSubMap._array[i]; if (mi != omi) { return false; } diff --git a/test/src/map_model.dart b/test/src/map_model.dart index ebee435..d5ddd50 100644 --- a/test/src/map_model.dart +++ b/test/src/map_model.dart @@ -10,7 +10,7 @@ final _random = new Random(); /** * Naive implementation of PersistentMap using dart:core [Map]s. */ -class ModelMap extends PersistentMapBase { +class ModelMap extends PersistentMap { final Map _map; ModelMap(this._map); From 4c66b4012fe8c1063222b2393f1cff5562d1a44a Mon Sep 17 00:00:00 2001 From: Jozef Brandys Date: Tue, 19 Aug 2014 18:31:53 +0200 Subject: [PATCH 003/214] Transient to persistentmap + random_test --- lib/src/map.dart | 138 +++++++++++++++++++++++++-- lib/src/map_impl.dart | 32 +++++-- test/transient_map_test.dart | 175 +++++++++++++++++++++++++++++++++++ 3 files changed, 332 insertions(+), 13 deletions(-) create mode 100644 test/transient_map_test.dart diff --git a/lib/src/map.dart b/lib/src/map.dart index befa2e9..81edc0e 100644 --- a/lib/src/map.dart +++ b/lib/src/map.dart @@ -19,6 +19,28 @@ class PersistentMap implements Iterable> { NodeBase _root; + int _hash; + + int get hashCode { + if(_hash != null) return _hash; + _hash = 0; + this.forEachKeyValue((key, value) { + _hash += key.hashCode ^ value.hashCode; + }); + return _hash; + } + + bool operator==(other) { + if (other is! PersistentMap) return false; + if(other.hashCode != this.hashCode || this.length != other.length) + return false; + bool equals = true; + this.forEachKeyValue((key, value) { + equals = equals && other.contains(key) && other[key] == value; + }); + return equals; + } + /** Creates an empty [PersistentMap] using its default implementation. */ PersistentMap() { this._root = new _EmptyMap(null); @@ -40,12 +62,16 @@ class PersistentMap * implementation of [PersistentMap]. */ PersistentMap.fromPairs(Iterable> pairs) { - _root = new _EmptyMap(); + _root = new _EmptyMap(null); pairs.forEach((pair) { _root = _root.insert(null, pair.fst, pair.snd); }); } + PersistentMap.fromTransient(TransientMap map) { + this._root = map._root; + } + PersistentMap._new(NodeBase this._root); /** @@ -190,6 +216,110 @@ class PersistentMap Iterator get iterator => _root.iterator; int get length => _root.length; + + // Optimized version of Iterable's contains + bool contains(key) { + final value = this.lookup(key); + return value.isDefined; + } + + TransientMap asTransient() { + return new TransientMap.fromPersistent(this); + } + + toString() => 'PersistentMap$_root'; +} + +class TransientMap + extends IterableBase> + implements Iterable> { + NodeBase _root; + Owner _owner; + get owner { + if(_owner == null && false) + throw new Exception('Cannot use TransientMap after calling asPersistent persistent'); + return _owner; + } + + /** + * Creates an immutable copy of [map] using the default implementation of + * [TransientMap]. + */ + TransientMap.fromPersistent(PersistentMap map) { + _owner = new Owner(); + _root = map._root; + } + + TransientMap _adjustRootAndReturn(newRoot) { + _root = newRoot; + return this; + } + + TransientMap + doInsert(K key, V value, [V combine(V oldvalue, V newvalue)]) { + return _adjustRootAndReturn(_root.insert(owner, key, value, combine)); + } + + TransientMap doDelete(K key) { + return _adjustRootAndReturn(_root.delete(owner, key)); + } + + Option doLookup(K key) { + return _root.lookup(key); + } + + V operator [](K key) { + return _root.lookup(key).asNullable; + } + + void doForEachKeyValue(f(K key, V value)) => _root.forEachKeyValue(f); + + TransientMap doAdjust(K key, V update(V value)) { + return _adjustRootAndReturn(_root.adjust(owner, key, update)); + } + + TransientMap doMapValues(f(V value)) { + return _adjustRootAndReturn(_root.mapValues(owner, f)); + } + + TransientMap + doUnion(TransientMap other, [V combine(V left, V right)]) => + _adjustRootAndReturn(_root.union(owner, other._root, combine)); + + TransientMap + doIntersection(TransientMap other, [V combine(V left, V right)]) => + _adjustRootAndReturn(_root.intersection(owner, other._root, combine)); + + /// Returns a mutable copy of `this`. + Map toMap() { + return _root.toMap(); + } + + /// The keys of `this`. + Iterable get keys => _root.keys; + + /// The values of `this`. + Iterable get values => _root.values; + + /// Randomly picks an entry of `this`. + Pair doPickRandomEntry([Random random]) => _root.pickRandomEntry(random); + + Iterator get iterator => _root.iterator; + + int get length => _root.length; + + // Optimized version of Iterable's contains + bool contains(key) { + final value = this.doLookup(key); + return value.isDefined; + } + + PersistentMap asPersistent() { + _owner = null; + return new PersistentMap.fromTransient(this); + } + + toString() => 'TransientMap(${owner.hashCode}, $_root)'; } /** @@ -221,12 +351,6 @@ abstract class NodeBase return buffer.toString(); } - // Optimized version of Iterable's contains - bool contains(Pair entry) { - final value = this.lookup(entry.fst); - return value.isDefined && value.value == entry.snd; - } - Iterable get keys => this.map((Pair pair) => pair.fst); Iterable get values => this.map((Pair pair) => pair.snd); diff --git a/lib/src/map_impl.dart b/lib/src/map_impl.dart index 2b7dbfd..245f332 100644 --- a/lib/src/map_impl.dart +++ b/lib/src/map_impl.dart @@ -468,10 +468,20 @@ class _SubMap extends _ANodeBase { int index = _popcount(_bitmap & (mask - 1)); if ((_bitmap & mask) != 0) { - List<_ANodeBase> newarray = _array.sublist(0); _ANodeBase m = _array[index]; + int oldSize = m.length; _ANodeBase newM = - m._insertWith(owner, keyValues, size, combine, hash, depth + 1); + m._insertWith(owner, keyValues, size, combine, hash, depth + 1); + if(identical(m, newM)) { + if(oldSize != newM.length) this._length += m.length - oldSize; + return this; + } + + List<_ANodeBase> newarray; + if(!ownerEquals(this._owner, owner)) + newarray = _array.sublist(0); + else + newarray = _array; newarray[index] = newM; int delta = newM.length - m.length; return new _SubMap.ensureOwner(this, owner, _bitmap, newarray, length + delta); @@ -496,7 +506,7 @@ class _SubMap extends _ANodeBase { if ((_bitmap & mask) != 0) { int index = _popcount(_bitmap & (mask - 1)); - _ANodeBase m = _array[index]; + _ANodeBase m = c[index]; return m._intersectWith(owner, keyValues, size, combine, hash, depth + 1); } else { return new _EmptyMap(owner); @@ -510,9 +520,11 @@ class _SubMap extends _ANodeBase { if ((_bitmap & mask) != 0) { int index = _popcount(_bitmap & (mask - 1)); _ANodeBase m = _array[index]; + int oldSize = m.length; _ANodeBase newm = m._delete(owner, key, hash, depth + 1); - int delta = newm.length - m.length; + int delta = newm.length - oldSize; if (identical(m, newm)) { + this._length += delta; return this; } if (newm.isEmpty) { @@ -564,8 +576,12 @@ class _SubMap extends _ANodeBase { if (identical(newm, m)) { return this; } - List<_ANodeBase> newarray = - new List<_ANodeBase>.from(_array, growable: false); + List<_ANodeBase> newarray; + + if(!ownerEquals(this._owner, owner)) + newarray = new List<_ANodeBase>.from(_array, growable: false); + else + newarray = _array; newarray[index] = newm; return new _SubMap.ensureOwner(this, owner, _bitmap, newarray, length); } else { @@ -723,3 +739,7 @@ class _SubMap extends _ANodeBase { toDebugString() => "_SubMap($_array)"; } + +ownerEquals(Owner a, Owner b) { + return a != null && a == b; +} diff --git a/test/transient_map_test.dart b/test/transient_map_test.dart new file mode 100644 index 0000000..f7e80e0 --- /dev/null +++ b/test/transient_map_test.dart @@ -0,0 +1,175 @@ +import 'package:persistent/persistent.dart'; +import 'package:unittest/unittest.dart'; +import 'dart:math'; +import 'dart:core'; + +final NUM_OPERATIONS = 5000; +main() { + test('random_test', () { + Random r = new Random(47); + + int nextTransient = 1; + int nextPersistent = 1; + List transientMaps = []; + List persistentMaps = [new PersistentMap()]; + Map map = {}; + List keys = []; + + List actionsPrep = [{ + 'times': 10, + 'prep': prepInsert, + 'call': callInsert, + 'map': insertMap, + 'per': insertPer, + 'tra': insertTra + }, + { + 'times': 8, + 'prep': prepDelete, + 'call': callDelete, + 'map': deleteMap, + 'per': deletePer, + 'tra': deleteTra + }, + { + 'times': 7, + 'prep': prepAdjust, + 'call': callAdjust, + 'map': adjustMap, + 'per': adjustPer, + 'tra': adjustTra + }]; + + List actions = []; + actionsPrep.forEach((val) => actions.addAll(new List.filled(val['times'], val))); + + for(int i = 0; i < NUM_OPERATIONS; i++) { + //print(i); + if(nextTransient == i) { + nextTransient *= 2; + transientMaps.add(persistentMaps.last.asTransient()); + } + if(nextPersistent == i) { + nextPersistent *= 2; + persistentMaps.add(persistentMaps.last); + } + int nextRand = r.nextInt(actions.length); + Map action = actions[nextRand]; + var prep = action['prep'](r, keys); + + map = action['call'](map, action['map'], prep); + + transientMaps = + transientMaps.map((map) => action['call'](map, action['tra'], prep)).toList(); + + persistentMaps = + persistentMaps.map((map) => action['call'](map, action['per'], prep)).toList(); + + checkAll(map, transientMaps, persistentMaps); + } + }); +} + +checkAll(Map map, List tran, List per) { + //print(map.length); + /*print(map); + tran.forEach((m) => print('Tran: $m')); + per.forEach((m) => print('Per: $m'));*/ + + PersistentMap toCompare = new PersistentMap.fromMap(map); + map.forEach((k,v) { + expect(v, equals(toCompare[k])); + }); + per.forEach((PersistentMap m) { + expect(m.length, equals(toCompare.length)); + expect(m.hashCode, equals(toCompare.hashCode)); + expect(m == toCompare, isTrue); + }); + + tran.forEach((TransientMap mt) { + PersistentMap m = mt.asPersistent(); + expect(m.length, equals(toCompare.length)); + expect(m.hashCode, equals(toCompare.hashCode)); + expect(m == toCompare, isTrue); + }); +} + + +prepInsert(r, List keys) { + var a = r.nextInt(47474); + while(keys.contains(a)) a = r.nextInt(47474); + keys.add(a); + return { + 'key': keys.last, + 'val': r.nextInt(4747) + }; +} + +callInsert(map, fun, prep) { + return fun(map, prep['key'], prep['val']); +} +insertMap(map, key, value) { + map[key] = value; + return map; +} + +insertPer(map, key, value) { + return map.insert(key, value); +} + +insertTra(map, key, value) { + return map.doInsert(key, value); +} + +prepDelete(Random r, List keys) { + if(keys.length == 0) return null; + + var del = keys.removeAt(r.nextInt(keys.length)); + return del; +} +callDelete(map, fun, prep) { + if(prep != null) + return fun(map, prep); + else + return map; +} +deleteMap(Map map, key) { + map.remove(key); + return map; +} + +deletePer(map, key) { + return map.delete(key); +} + +deleteTra(map, key) { + return map.doDelete(key); +} + +prepAdjust(Random r, List keys) { + if(keys.length == 0) return null; + int toAdj = keys[r.nextInt(keys.length)]; + + return { + 'key': toAdj, + 'adj': (int a) => a + 47 + }; +} +callAdjust(map, fun, prep) { + if(prep != null) + return fun(map, prep['key'], prep['adj']); + else + return map; +} +adjustMap(Map map, key, adjust) { + map[key] = adjust(map[key]); + return map; +} + +adjustPer(map, key, adjust) { + return map.adjust(key, adjust); +} + +adjustTra(map, key, adjust) { + return map.doAdjust(key, adjust); +} \ No newline at end of file From 148ad14730f120a61b2b7ce15e6123f449d51529 Mon Sep 17 00:00:00 2001 From: Jozef Brandys Date: Wed, 20 Aug 2014 08:48:32 +0200 Subject: [PATCH 004/214] Use owner in persistent to speed up bulk operations. --- lib/src/map.dart | 20 +++++++++++++++----- lib/src/map_impl.dart | 2 +- test/transient_map_test.dart | 5 ----- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/lib/src/map.dart b/lib/src/map.dart index 81edc0e..93fbb31 100644 --- a/lib/src/map.dart +++ b/lib/src/map.dart @@ -52,8 +52,9 @@ class PersistentMap */ PersistentMap.fromMap(Map map) { _root = new _EmptyMap(null); + Owner owner = new Owner(); map.forEach((K key, V value) { - _root = _root.insert(null, key, value); + _root = _root.insert(owner, key, value); }); } @@ -63,8 +64,9 @@ class PersistentMap */ PersistentMap.fromPairs(Iterable> pairs) { _root = new _EmptyMap(null); + Owner owner = new Owner(); pairs.forEach((pair) { - _root = _root.insert(null, pair.fst, pair.snd); + _root = _root.insert(owner, pair.fst, pair.snd); }); } @@ -146,7 +148,8 @@ class PersistentMap * {}.mapValues((x) => x + 1) == {} */ PersistentMap mapValues(f(V value)) { - return new PersistentMap._new(_root.mapValues(null, f)); + Owner owner = new Owner(); + return new PersistentMap._new(_root.mapValues(owner, f)); } /** @@ -167,8 +170,10 @@ class PersistentMap * if it is commutative. */ PersistentMap - union(PersistentMap other, [V combine(V left, V right)]) => - new PersistentMap._new(_root.union(null, other._root, combine)); + union(PersistentMap other, [V combine(V left, V right)]) { + Owner owner = new Owner(); + return new PersistentMap._new(_root.union(owner, other._root, combine)); + } /** * Returns a new map whose (key, value) pairs are the intersection of those of @@ -227,6 +232,11 @@ class PersistentMap return new TransientMap.fromPersistent(this); } + PersistentMap doAsTransient(dynamic f(TransientMap)) { + TransientMap transient = this.asTransient(); + f(transient); + return transient.asPersistent(); + } toString() => 'PersistentMap$_root'; } diff --git a/lib/src/map_impl.dart b/lib/src/map_impl.dart index 245f332..70bbb54 100644 --- a/lib/src/map_impl.dart +++ b/lib/src/map_impl.dart @@ -506,7 +506,7 @@ class _SubMap extends _ANodeBase { if ((_bitmap & mask) != 0) { int index = _popcount(_bitmap & (mask - 1)); - _ANodeBase m = c[index]; + _ANodeBase m = _array[index]; return m._intersectWith(owner, keyValues, size, combine, hash, depth + 1); } else { return new _EmptyMap(owner); diff --git a/test/transient_map_test.dart b/test/transient_map_test.dart index f7e80e0..45adfec 100644 --- a/test/transient_map_test.dart +++ b/test/transient_map_test.dart @@ -71,11 +71,6 @@ main() { } checkAll(Map map, List tran, List per) { - //print(map.length); - /*print(map); - tran.forEach((m) => print('Tran: $m')); - per.forEach((m) => print('Per: $m'));*/ - PersistentMap toCompare = new PersistentMap.fromMap(map); map.forEach((k,v) { expect(v, equals(toCompare[k])); From c0e675d06d24440c73cb9e77e8dd1ed175586c3e Mon Sep 17 00:00:00 2001 From: Jozef Brandys Date: Wed, 20 Aug 2014 10:17:59 +0200 Subject: [PATCH 005/214] Remove unnecessary copy of array and repair tests. --- benchmark/map_bench_wordcount.dart | 22 ++++++++++- benchmark/src/benchmark.dart | 31 +++++++++++++++ benchmark/src/simple_map_1.dart | 2 +- benchmark/src/simple_map_2.dart | 2 +- lib/src/map.dart | 10 +---- lib/src/map_impl.dart | 39 +++++++++---------- test/transient_map_test.dart | 60 ++++++++++++++++++++++-------- 7 files changed, 117 insertions(+), 49 deletions(-) diff --git a/benchmark/map_bench_wordcount.dart b/benchmark/map_bench_wordcount.dart index 7bb12c2..aaa5f33 100644 --- a/benchmark/map_bench_wordcount.dart +++ b/benchmark/map_bench_wordcount.dart @@ -6,6 +6,9 @@ add(n, m) => n + m; insertWord(map, word) => map.insert(word, 1, add); count(words) => words.fold(new PersistentMap(), insertWord); +insertWordT(map, word) => map.doInsert(word, 1, add); +countT(words) => words.fold(new PersistentMap().asTransient(), insertWordT); + final AUSTEEN = 'http://www.gutenberg.org/files/1342/1342.txt'; final DOYLE = 'http://www.gutenberg.org/files/1661/1661.txt'; @@ -22,6 +25,15 @@ void run(List doyle, List austeen) { } } +void runT(List doyle, List austeen) { + final map1 = countT(austeen); + final map2 = countT(doyle); + final result = map1.doUnion(map2, add); + if (result.length != 36028) { + throw new StateError("something's wrong"); + } +} + void bench(List doyle, List austeen) { // warmup run(doyle, austeen); @@ -32,7 +44,15 @@ void bench(List doyle, List austeen) { run(doyle, austeen); } watch.stop(); - print(watch.elapsedMilliseconds / 10); + print('Persistent ${watch.elapsedMilliseconds / 10}'); + + watch.reset(); + watch.start(); + for (int i = 0; i < 10; i++) { + runT(doyle, austeen); + } + watch.stop(); + print('Transient ${watch.elapsedMilliseconds / 10}'); } main() { diff --git a/benchmark/src/benchmark.dart b/benchmark/src/benchmark.dart index 67a3c3d..0cb8ba1 100644 --- a/benchmark/src/benchmark.dart +++ b/benchmark/src/benchmark.dart @@ -20,6 +20,7 @@ class Benchmark { res["Linked List"] = _bench(() => new SimplePersistentMap()); res["Mutable Map"] = _bench(() => new SimplePersistentMap2()); res["Hash Trie"] = _bench(() => new PersistentMap()); + res["Hash Trie Transient"] = _benchT(() => new PersistentMap().asTransient()); return res; } @@ -52,4 +53,34 @@ class Benchmark { stopwatch.stop(); return stopwatch.elapsedMicroseconds; } + + int _benchT(TransientMap empty()) { + final stopwatch = new Stopwatch(); + stopwatch.start(); + + TransientMap map = empty(); + for (int i = 0; i < size; i++) { + map = map.doInsert("key$i", "foo", (String x, String y) => x + y); + map = map.doInsert("key$i", "bar", (String x, String y) => x + y); + } + + for (int i = size * 2; i >= 0; i--) { + map.doLookup("key$i"); + } + for (int i = 0; i <= size * 2; i++) { + map.doLookup("key$i"); + } + + TransientMap saved = map.asPersistent().asTransient(); + for (int i = size * 2; i >= 0; i--) { + map = map.doDelete("key$i"); + } + map = saved; + for (int i = 0; i < size * 2; i++) { + map = map.doDelete("key$i"); + } + + stopwatch.stop(); + return stopwatch.elapsedMicroseconds; + } } diff --git a/benchmark/src/simple_map_1.dart b/benchmark/src/simple_map_1.dart index 0dedcca..aa2d59e 100644 --- a/benchmark/src/simple_map_1.dart +++ b/benchmark/src/simple_map_1.dart @@ -8,7 +8,7 @@ part of map_bench; /** * Naive implementation of PersistentMap using a [LinkedList] of [Pair]s. */ -class SimplePersistentMap extends PersistentMapBase { +class SimplePersistentMap extends PersistentMap { final LinkedList> _list; bool get isEmpty => _list.isNil; diff --git a/benchmark/src/simple_map_2.dart b/benchmark/src/simple_map_2.dart index 9ef1b46..68e4a04 100644 --- a/benchmark/src/simple_map_2.dart +++ b/benchmark/src/simple_map_2.dart @@ -8,7 +8,7 @@ part of map_bench; /** * Naive implementation of PersistentMap dart:core [Map]s. */ -class SimplePersistentMap2 extends PersistentMapBase { +class SimplePersistentMap2 extends PersistentMap { final Map _map; bool get isEmpty => _map.isEmpty; diff --git a/lib/src/map.dart b/lib/src/map.dart index 93fbb31..42e897a 100644 --- a/lib/src/map.dart +++ b/lib/src/map.dart @@ -244,19 +244,14 @@ class TransientMap extends IterableBase> implements Iterable> { NodeBase _root; - Owner _owner; - get owner { - if(_owner == null && false) - throw new Exception('Cannot use TransientMap after calling asPersistent persistent'); - return _owner; - } + Owner owner; /** * Creates an immutable copy of [map] using the default implementation of * [TransientMap]. */ TransientMap.fromPersistent(PersistentMap map) { - _owner = new Owner(); + owner = new Owner(); _root = map._root; } @@ -325,7 +320,6 @@ class TransientMap } PersistentMap asPersistent() { - _owner = null; return new PersistentMap.fromTransient(this); } diff --git a/lib/src/map_impl.dart b/lib/src/map_impl.dart index 70bbb54..3ab0a9e 100644 --- a/lib/src/map_impl.dart +++ b/lib/src/map_impl.dart @@ -176,7 +176,7 @@ class _Leaf extends _ANodeBase { } factory _Leaf.ensureOwner(_Leaf old, Owner owner, hash, pairs, int size) { - if(owner != null && owner == old._owner) { + if(ownerEquals(owner, old._owner)) { old._pairs = pairs; old._hash = hash; old._length = size; @@ -237,7 +237,7 @@ class _Leaf extends _ANodeBase { return new _Leaf.ensureOwner(this, owner, hash, newPairs, newsize); } else { int branch = (_hash >> (depth * 5)) & 0x1f; - List<_ANodeBase> array = <_ANodeBase>[this]; + List<_ANodeBase> array = new List.filled(1, this); return new _SubMap.abc(owner, 1 << branch, array, length) ._insertWith(owner, keyValues, size, combine, hash, depth); } @@ -430,7 +430,7 @@ class _SubMap extends _ANodeBase { _SubMap.abc(Owner owner, this._bitmap, this._array, int size) : super(owner, size, false, false); factory _SubMap.ensureOwner(_SubMap old, Owner owner, bitmap, array, int size) { - if(owner != null && owner == old._owner) { + if(ownerEquals(owner, old._owner)) { old._bitmap = bitmap; old._array = array; old._length = size; @@ -476,14 +476,9 @@ class _SubMap extends _ANodeBase { if(oldSize != newM.length) this._length += m.length - oldSize; return this; } - - List<_ANodeBase> newarray; - if(!ownerEquals(this._owner, owner)) - newarray = _array.sublist(0); - else - newarray = _array; + List<_ANodeBase> newarray = makeCopyIfNeeded(owner, this._owner, _array); newarray[index] = newM; - int delta = newM.length - m.length; + int delta = newM.length - oldSize; return new _SubMap.ensureOwner(this, owner, _bitmap, newarray, length + delta); } else { int newlength = _array.length + 1; @@ -550,15 +545,14 @@ class _SubMap extends _ANodeBase { if (_array.length == 1) { return newm; } else { - List<_ANodeBase> newarray = - new List<_ANodeBase>.from(_array, growable: false); + List<_ANodeBase> newarray = makeCopyIfNeeded(owner, this._owner, _array); newarray[index] = newm; return new _SubMap.ensureOwner(this, owner, _bitmap, newarray, length + delta); } } else { - List<_ANodeBase> newarray = - new List<_ANodeBase>.from(_array, growable: false); + List<_ANodeBase> newarray = makeCopyIfNeeded(owner, this._owner, _array); newarray[index] = newm; + return new _SubMap.ensureOwner(this, owner, _bitmap, newarray, length + delta); } } else { @@ -576,13 +570,9 @@ class _SubMap extends _ANodeBase { if (identical(newm, m)) { return this; } - List<_ANodeBase> newarray; - - if(!ownerEquals(this._owner, owner)) - newarray = new List<_ANodeBase>.from(_array, growable: false); - else - newarray = _array; + List<_ANodeBase> newarray = makeCopyIfNeeded(owner, this._owner, _array); newarray[index] = newm; + return new _SubMap.ensureOwner(this, owner, _bitmap, newarray, length); } else { return this; @@ -691,8 +681,7 @@ class _SubMap extends _ANodeBase { } NodeBase mapValues(Owner owner, f(V)) { - List<_ANodeBase> newarray = - new List<_ANodeBase>.from(_array, growable: false); + List<_ANodeBase> newarray = makeCopyIfNeeded(owner, this._owner, _array); for (int i = 0; i < _array.length; i++) { _ANodeBase mi = _array[i]; newarray[i] = mi.mapValues(owner, f); @@ -743,3 +732,9 @@ class _SubMap extends _ANodeBase { ownerEquals(Owner a, Owner b) { return a != null && a == b; } + +makeCopyIfNeeded(Owner a, Owner b, List c) { + if(ownerEquals(a, b)) + return c; + else return c.sublist(0); +} diff --git a/test/transient_map_test.dart b/test/transient_map_test.dart index 45adfec..280a0c4 100644 --- a/test/transient_map_test.dart +++ b/test/transient_map_test.dart @@ -3,13 +3,13 @@ import 'package:unittest/unittest.dart'; import 'dart:math'; import 'dart:core'; -final NUM_OPERATIONS = 5000; +final NUM_OPERATIONS = 50000; main() { test('random_test', () { Random r = new Random(47); - int nextTransient = 1; - int nextPersistent = 1; + int next = 1; + List transientMaps = []; List persistentMaps = [new PersistentMap()]; Map map = {}; @@ -22,22 +22,27 @@ main() { 'map': insertMap, 'per': insertPer, 'tra': insertTra - }, - { + }, { 'times': 8, 'prep': prepDelete, 'call': callDelete, 'map': deleteMap, 'per': deletePer, 'tra': deleteTra - }, - { + }, { 'times': 7, 'prep': prepAdjust, 'call': callAdjust, 'map': adjustMap, 'per': adjustPer, 'tra': adjustTra + }, { + 'times': 1, + 'prep': prepMap, + 'call': callMap, + 'map': mapMap, + 'per': mapPer, + 'tra': mapTra }]; List actions = []; @@ -45,14 +50,7 @@ main() { for(int i = 0; i < NUM_OPERATIONS; i++) { //print(i); - if(nextTransient == i) { - nextTransient *= 2; - transientMaps.add(persistentMaps.last.asTransient()); - } - if(nextPersistent == i) { - nextPersistent *= 2; - persistentMaps.add(persistentMaps.last); - } + int nextRand = r.nextInt(actions.length); Map action = actions[nextRand]; var prep = action['prep'](r, keys); @@ -65,12 +63,22 @@ main() { persistentMaps = persistentMaps.map((map) => action['call'](map, action['per'], prep)).toList(); - checkAll(map, transientMaps, persistentMaps); + if(next == i) { + next *= 2; + transientMaps.add(persistentMaps.last.asTransient()); + persistentMaps.add(persistentMaps.last); + checkAll(map, transientMaps, persistentMaps); + } } + checkAll(map, transientMaps, persistentMaps); }); } checkAll(Map map, List tran, List per) { + /*print(map.length); + print(map); + tran.forEach((e) => print(e)); + per.forEach((e) => print(e));*/ PersistentMap toCompare = new PersistentMap.fromMap(map); map.forEach((k,v) { expect(v, equals(toCompare[k])); @@ -167,4 +175,24 @@ adjustPer(map, key, adjust) { adjustTra(map, key, adjust) { return map.doAdjust(key, adjust); +} + +prepMap(Random r, List keys) { + return (a) => a - 1; +} +callMap(map, fun, prep) { + return fun(map, prep); +} +mapMap(Map map, f) { + var newMap = {}; + map.forEach((k,v) => newMap[k] = f(v)); + return newMap; +} + +mapPer(PersistentMap map, f) { + return map.mapValues(f); +} + +mapTra(TransientMap map, f) { + return map.doMapValues(f); } \ No newline at end of file From 6b240582a5ce191fc6e1577c099f2e81934ab65f Mon Sep 17 00:00:00 2001 From: Jozef Brandys Date: Wed, 20 Aug 2014 10:58:16 +0200 Subject: [PATCH 006/214] withTransient with tests --- lib/src/map.dart | 2 +- test/transient_map_test.dart | 46 +++++++++++++++++++++++++++++------- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/lib/src/map.dart b/lib/src/map.dart index 42e897a..5c1747d 100644 --- a/lib/src/map.dart +++ b/lib/src/map.dart @@ -232,7 +232,7 @@ class PersistentMap return new TransientMap.fromPersistent(this); } - PersistentMap doAsTransient(dynamic f(TransientMap)) { + PersistentMap withTransient(dynamic f(TransientMap)) { TransientMap transient = this.asTransient(); f(transient); return transient.asPersistent(); diff --git a/test/transient_map_test.dart b/test/transient_map_test.dart index 280a0c4..6a7e12e 100644 --- a/test/transient_map_test.dart +++ b/test/transient_map_test.dart @@ -3,7 +3,8 @@ import 'package:unittest/unittest.dart'; import 'dart:math'; import 'dart:core'; -final NUM_OPERATIONS = 50000; +final NUM_OPERATIONS = 10000; + main() { test('random_test', () { Random r = new Random(47); @@ -12,6 +13,7 @@ main() { List transientMaps = []; List persistentMaps = [new PersistentMap()]; + List persistentWithTransient = [new PersistentMap()]; Map map = {}; List keys = []; @@ -21,28 +23,32 @@ main() { 'call': callInsert, 'map': insertMap, 'per': insertPer, - 'tra': insertTra + 'tra': insertTra, + 'withTra': insertWithTra }, { 'times': 8, 'prep': prepDelete, 'call': callDelete, 'map': deleteMap, 'per': deletePer, - 'tra': deleteTra + 'tra': deleteTra, + 'withTra': deleteWithTra }, { 'times': 7, 'prep': prepAdjust, 'call': callAdjust, 'map': adjustMap, 'per': adjustPer, - 'tra': adjustTra + 'tra': adjustTra, + 'withTra': adjustWithTra }, { 'times': 1, 'prep': prepMap, 'call': callMap, 'map': mapMap, 'per': mapPer, - 'tra': mapTra + 'tra': mapTra, + 'withTra': mapWithTra }]; List actions = []; @@ -63,18 +69,21 @@ main() { persistentMaps = persistentMaps.map((map) => action['call'](map, action['per'], prep)).toList(); + persistentWithTransient = + persistentWithTransient.map((map) => action['call'](map, action['withTra'], prep)).toList(); + if(next == i) { next *= 2; transientMaps.add(persistentMaps.last.asTransient()); persistentMaps.add(persistentMaps.last); - checkAll(map, transientMaps, persistentMaps); + checkAll(map, transientMaps, persistentMaps, persistentWithTransient); } } - checkAll(map, transientMaps, persistentMaps); + checkAll(map, transientMaps, persistentMaps, persistentWithTransient); }); } -checkAll(Map map, List tran, List per) { +checkAll(Map map, List tran, List per, List withTran) { /*print(map.length); print(map); tran.forEach((e) => print(e)); @@ -95,6 +104,12 @@ checkAll(Map map, List tran, List per) { expect(m.hashCode, equals(toCompare.hashCode)); expect(m == toCompare, isTrue); }); + + withTran.forEach((PersistentMap m) { + expect(m.length, equals(toCompare.length)); + expect(m.hashCode, equals(toCompare.hashCode)); + expect(m == toCompare, isTrue); + }); } @@ -124,6 +139,10 @@ insertTra(map, key, value) { return map.doInsert(key, value); } +insertWithTra(PersistentMap map, key, value) { + return map.withTransient((TransientMap map) => map.doInsert(key, value)); +} + prepDelete(Random r, List keys) { if(keys.length == 0) return null; @@ -149,6 +168,10 @@ deleteTra(map, key) { return map.doDelete(key); } +deleteWithTra(map, key) { + return map.withTransient((TransientMap map) => map.doDelete(key)); +} + prepAdjust(Random r, List keys) { if(keys.length == 0) return null; int toAdj = keys[r.nextInt(keys.length)]; @@ -176,6 +199,9 @@ adjustPer(map, key, adjust) { adjustTra(map, key, adjust) { return map.doAdjust(key, adjust); } +adjustWithTra(map, key, adjust) { + return map.withTransient((TransientMap map) => map.doAdjust(key, adjust)); +} prepMap(Random r, List keys) { return (a) => a - 1; @@ -195,4 +221,8 @@ mapPer(PersistentMap map, f) { mapTra(TransientMap map, f) { return map.doMapValues(f); +} + +mapWithTra(PersistentMap map, f) { + return map.withTransient((TransientMap map) => map.doMapValues(f)); } \ No newline at end of file From 421f9a414def962891682ba852a90ad03366e2f5 Mon Sep 17 00:00:00 2001 From: Jozef Brandys Date: Wed, 20 Aug 2014 14:42:48 +0200 Subject: [PATCH 007/214] First implementation of deep Map. --- lib/src/map.dart | 267 +++++++------------------------ lib/src/map_impl.dart | 341 ++++++++++++++++++++++++++++++++++++++++ test/deep_map_test.dart | 52 ++++++ 3 files changed, 447 insertions(+), 213 deletions(-) create mode 100644 test/deep_map_test.dart diff --git a/lib/src/map.dart b/lib/src/map.dart index 5c1747d..446a1bd 100644 --- a/lib/src/map.dart +++ b/lib/src/map.dart @@ -14,67 +14,25 @@ part of persistent; * In all the examples below `{k1: v1, k2: v2, ...}` is a shorthand for * `PersistentMap.fromMap({k1: v1, k2: v2, ...})`. */ -class PersistentMap - extends IterableBase> +abstract class PersistentMap implements Iterable> { - NodeBase _root; - - int _hash; - - int get hashCode { - if(_hash != null) return _hash; - _hash = 0; - this.forEachKeyValue((key, value) { - _hash += key.hashCode ^ value.hashCode; - }); - return _hash; - } - - bool operator==(other) { - if (other is! PersistentMap) return false; - if(other.hashCode != this.hashCode || this.length != other.length) - return false; - bool equals = true; - this.forEachKeyValue((key, value) { - equals = equals && other.contains(key) && other[key] == value; - }); - return equals; - } /** Creates an empty [PersistentMap] using its default implementation. */ - PersistentMap() { - this._root = new _EmptyMap(null); - } + factory PersistentMap() => new PersistentMapImpl(); /** * Creates an immutable copy of [map] using the default implementation of * [PersistentMap]. */ - PersistentMap.fromMap(Map map) { - _root = new _EmptyMap(null); - Owner owner = new Owner(); - map.forEach((K key, V value) { - _root = _root.insert(owner, key, value); - }); - } + factory PersistentMap.fromMap(Map map) => + new PersistentMapImpl.fromMap(map); /** * Creates a [PersistentMap] from an [Iterable] of [Pair]s using the default * implementation of [PersistentMap]. */ - PersistentMap.fromPairs(Iterable> pairs) { - _root = new _EmptyMap(null); - Owner owner = new Owner(); - pairs.forEach((pair) { - _root = _root.insert(owner, pair.fst, pair.snd); - }); - } - - PersistentMap.fromTransient(TransientMap map) { - this._root = map._root; - } - - PersistentMap._new(NodeBase this._root); + factory PersistentMap.fromPairs(Iterable> pairs) => + new PersistentMapImpl.fromPairs(pairs); /** * Returns a new map identical to `this` except that it binds [key] to @@ -90,9 +48,8 @@ class PersistentMap * {'a': 1, 'b': 2}.insert('b', 3, (x,y) => x - y) == {'a': 3, 'b', -1} */ PersistentMap - insert(K key, V value, [V combine(V oldvalue, V newvalue)]) { - return new PersistentMap._new(_root.insert(null, key, value, combine)); - } + insert(K key, V value, [V combine(V oldvalue, V newvalue)]); + PersistentMap insertIn(List path, V value, [V combine(V oldvalue, V newvalue)]); /** * Returns a new map identical to `this` except that it doesn't bind [key] @@ -101,9 +58,8 @@ class PersistentMap * {'a': 1, 'b': 2}.delete('b') == {'a': 1} * {'a': 1}.delete('b') == {'a': 1} */ - PersistentMap delete(K key) { - return new PersistentMap._new(_root.delete(null, key)); - } + PersistentMap delete(K key); + PersistentMap deleteIn(List path); /** * Looks up the value possibly bound to [key] in `this`. Returns @@ -112,22 +68,19 @@ class PersistentMap * {'a': 1}.lookup('b') == Option.none() * {'a': 1, 'b': 2}.lookup('b') == Option.some(2) */ - Option lookup(K key) { - return _root.lookup(key); - } + Option lookup(K key); + Option lookupIn(List path); /** * Returns the value for the given [key] or [:null:] if [key] * is not in the map. */ - V operator [](K key) { - return _root.lookup(key).asNullable; - } + V operator [](K key); /** * Evaluates `f(key, value)` for each (`key`, `value`) pair in `this`. */ - void forEachKeyValue(f(K key, V value)) => _root.forEachKeyValue(f); + void forEachKeyValue(f(K key, V value)); /** * Returns a new map identical to `this` except that the value it possibly @@ -136,9 +89,8 @@ class PersistentMap * {'a': 1, 'b': 2}.adjust('b', (x) => x + 1) == {'a', 1, 'b', 3} * {'a': 1}.adjust('b', (x) => x + 1) == {'a', 1} */ - PersistentMap adjust(K key, V update(V value)) { - return new PersistentMap._new(_root.adjust(null, key, update)); - } + PersistentMap adjust(K key, V update(V value)); + PersistentMap adjustIn(List path, V update(V value)); /** * Returns a new map identical to `this` where each value has been updated by @@ -147,10 +99,7 @@ class PersistentMap * {'a': 1, 'b': 2}.mapValues((x) => x + 1) == {'a', 2, 'b', 3} * {}.mapValues((x) => x + 1) == {} */ - PersistentMap mapValues(f(V value)) { - Owner owner = new Owner(); - return new PersistentMap._new(_root.mapValues(owner, f)); - } + PersistentMap mapValues(f(V value)); /** * Returns a new map whose (key, value) pairs are the union of those of `this` @@ -170,10 +119,7 @@ class PersistentMap * if it is commutative. */ PersistentMap - union(PersistentMap other, [V combine(V left, V right)]) { - Owner owner = new Owner(); - return new PersistentMap._new(_root.union(owner, other._root, combine)); - } + union(PersistentMap other, [V combine(V left, V right)]); /** * Returns a new map whose (key, value) pairs are the intersection of those of @@ -193,22 +139,19 @@ class PersistentMap * provided and if it is commutative. */ PersistentMap - intersection(PersistentMap other, [V combine(V left, V right)]) => - new PersistentMap._new(_root.intersection(null, other._root, combine)); + intersection(PersistentMap other, [V combine(V left, V right)]); /// Returns a mutable copy of `this`. - Map toMap() { - return _root.toMap(); - } + Map toMap(); /// The keys of `this`. - Iterable get keys => _root.keys; + Iterable get keys; /// The values of `this`. - Iterable get values => _root.values; + Iterable get values; /// Randomly picks an entry of `this`. - Pair pickRandomEntry([Random random]) => _root.pickRandomEntry(random); + Pair pickRandomEntry([Random random]); /// A strict (non-lazy) version of [map]. PersistentMap strictMap(Pair f(Pair pair)) => @@ -218,169 +161,67 @@ class PersistentMap PersistentMap strictWhere(bool f(Pair pair)) => new PersistentMap.fromPairs(this.where(f)); - Iterator get iterator => _root.iterator; - - int get length => _root.length; + Iterator get iterator; - // Optimized version of Iterable's contains - bool contains(key) { - final value = this.lookup(key); - return value.isDefined; - } + int get length; - TransientMap asTransient() { - return new TransientMap.fromPersistent(this); - } + TransientMap asTransient(); - PersistentMap withTransient(dynamic f(TransientMap)) { - TransientMap transient = this.asTransient(); - f(transient); - return transient.asPersistent(); - } - toString() => 'PersistentMap$_root'; + PersistentMap withTransient(dynamic f(TransientMap)); } -class TransientMap - extends IterableBase> +abstract class TransientMap implements Iterable> { - NodeBase _root; - Owner owner; /** - * Creates an immutable copy of [map] using the default implementation of + * Creates an mutable copy of [map] using the default implementation of * [TransientMap]. */ - TransientMap.fromPersistent(PersistentMap map) { - owner = new Owner(); - _root = map._root; - } + factory TransientMap() => new TransientMapImpl(); - TransientMap _adjustRootAndReturn(newRoot) { - _root = newRoot; - return this; - } + TransientMap + doInsert(K key, V value, [V combine(V oldvalue, V newvalue)]); TransientMap - doInsert(K key, V value, [V combine(V oldvalue, V newvalue)]) { - return _adjustRootAndReturn(_root.insert(owner, key, value, combine)); - } + doInsertIn(List path, V value, [V combine(V oldvalue, V newvalue)]); + + TransientMap doDelete(K key) ; + + TransientMap doDeleteIn(List path); + + Option doLookup(K key); - TransientMap doDelete(K key) { - return _adjustRootAndReturn(_root.delete(owner, key)); - } + Option doLookupIn(List path); - Option doLookup(K key) { - return _root.lookup(key); - } + V operator [](K key); - V operator [](K key) { - return _root.lookup(key).asNullable; - } + void doForEachKeyValue(f(K key, V value)); - void doForEachKeyValue(f(K key, V value)) => _root.forEachKeyValue(f); + TransientMap doAdjust(K key, V update(V value)); - TransientMap doAdjust(K key, V update(V value)) { - return _adjustRootAndReturn(_root.adjust(owner, key, update)); - } + TransientMap doAdjustIn(List key, V update(V value)); - TransientMap doMapValues(f(V value)) { - return _adjustRootAndReturn(_root.mapValues(owner, f)); - } + + TransientMap doMapValues(f(V value)) ; TransientMap - doUnion(TransientMap other, [V combine(V left, V right)]) => - _adjustRootAndReturn(_root.union(owner, other._root, combine)); + doUnion(TransientMap other, [V combine(V left, V right)]); TransientMap - doIntersection(TransientMap other, [V combine(V left, V right)]) => - _adjustRootAndReturn(_root.intersection(owner, other._root, combine)); + doIntersection(TransientMap other, [V combine(V left, V right)]); /// Returns a mutable copy of `this`. - Map toMap() { - return _root.toMap(); - } + Map toMap(); /// The keys of `this`. - Iterable get keys => _root.keys; + Iterable get keys; /// The values of `this`. - Iterable get values => _root.values; + Iterable get values; /// Randomly picks an entry of `this`. - Pair doPickRandomEntry([Random random]) => _root.pickRandomEntry(random); - - Iterator get iterator => _root.iterator; - - int get length => _root.length; - - // Optimized version of Iterable's contains - bool contains(key) { - final value = this.doLookup(key); - return value.isDefined; - } - - PersistentMap asPersistent() { - return new PersistentMap.fromTransient(this); - } - - toString() => 'TransientMap(${owner.hashCode}, $_root)'; -} - -/** - * A base class for implementations of [PersistentMap]. - */ -abstract class NodeBase - extends IterableBase> { - - int _length; - get length => _length; - - NodeBase(this._length); - - Map toMap() { - Map result = new Map(); - this.forEachKeyValue((K k, V v) { result[k] = v; }); - return result; - } - - String toString() { - StringBuffer buffer = new StringBuffer('{'); - bool comma = false; - this.forEachKeyValue((K k, V v) { - if (comma) buffer.write(', '); - buffer.write('$k: $v'); - comma = true; - }); - buffer.write('}'); - return buffer.toString(); - } - - Iterable get keys => this.map((Pair pair) => pair.fst); - - Iterable get values => this.map((Pair pair) => pair.snd); - - Pair pickRandomEntry([Random random]) => - elementAt((random != null ? random : _random).nextInt(this.length)); - - V operator [](K key) => this.lookup(key).asNullable; - - NodeBase - insert(Owner owner, K key, V value, [V combine(V oldvalue, V newvalue)]); - - NodeBase delete(Owner owner, K key); - - Option lookup(K key); - - void forEachKeyValue(f(K key, V value)); - - NodeBase adjust(Owner owner, K key, V update(V value)); - - NodeBase mapValues(Owner owner, f(V value)); - - NodeBase - union(Owner owner, NodeBase other, [V combine(V left, V right)]); - - NodeBase - intersection(Owner owner, NodeBase other, [V combine(V left, V right)]); + Pair doPickRandomEntry([Random random]); + Iterator get iterator; + PersistentMap asPersistent() => null; } diff --git a/lib/src/map_impl.dart b/lib/src/map_impl.dart index 3ab0a9e..f7d6537 100644 --- a/lib/src/map_impl.dart +++ b/lib/src/map_impl.dart @@ -7,6 +7,291 @@ part of persistent; final _random = new Random(); +class PersistentMapImpl + extends IterableBase> + implements PersistentMap { + NodeBase _root; + + int _hash; + + int get hashCode { + if(_hash != null) return _hash; + _hash = 0; + this.forEachKeyValue((key, value) { + _hash += key.hashCode ^ value.hashCode; + }); + return _hash; + } + + bool operator==(other) { + if (other is! PersistentMapImpl) return false; + if(other.hashCode != this.hashCode || this.length != other.length) + return false; + bool equals = true; + this.forEachKeyValue((key, value) { + equals = equals && other.contains(key) && other[key] == value; + }); + return equals; + } + + PersistentMapImpl() { + this._root = new _EmptyMap(null); + } + + PersistentMapImpl.fromMap(Map map) { + _root = new _EmptyMap(null); + Owner owner = new Owner(); + map.forEach((K key, V value) { + _root = _root.insert(owner, key, value); + }); + } + + PersistentMapImpl.fromPairs(Iterable> pairs) { + _root = new _EmptyMap(null); + Owner owner = new Owner(); + pairs.forEach((pair) { + _root = _root.insert(owner, pair.fst, pair.snd); + }); + } + + PersistentMapImpl.fromTransient(TransientMapImpl map) { + this._root = map._root; + } + + PersistentMapImpl._new(NodeBase this._root); + + PersistentMapImpl + insert(K key, V value, [V combine(V oldvalue, V newvalue)]) { + return new PersistentMapImpl._new(_root.insert(null, key, value, combine)); + } + + PersistentMapImpl + insertIn(List path, V value, [V combine(V oldvalue, V newvalue), offset = 0]) { + if(offset +1 == path.length) + return new PersistentMapImpl._new(_root.insert(null, path.last, value)); + + return new PersistentMapImpl._new( + _root.adjust(null, path[offset], + (e) => e.insertIn(path, value, combine, offset+1))); + } + + PersistentMapImpl delete(K key) { + return new PersistentMapImpl._new(_root.delete(null, key)); + } + + PersistentMapImpl deleteIn(List path, [offset = 0]) { + if(offset +1 == path.length) + return new PersistentMapImpl._new(_root.delete(null, path.last)); + + return new PersistentMapImpl._new( + _root.adjust(null, path[offset], + (e) => e.deleteIn(path, offset+1))); + } + + Option lookup(K key) { + return _root.lookup(key); + } + + Option lookupIn(List path, [offset = 0]) { + Option e = lookup(path[offset]); + offset++; + + if(path.length == offset) return e; + else return e.isDefined ? e.value.lookupIn(path, offset) : null; + } + + V operator [](K key) { + return _root.lookup(key).asNullable; + } + + void forEachKeyValue(f(K key, V value)) => _root.forEachKeyValue(f); + + PersistentMapImpl adjust(K key, V update(V value)) { + return new PersistentMapImpl._new(_root.adjust(null, key, update)); + } + + PersistentMapImpl adjustIn(List path, V update(V value), [offset = 0]) { + if(path.length == offset +1) return adjust(path[offset], update); + + return new PersistentMapImpl._new( + _root.adjust(null, path[offset], + (e) => e.adjustIn(path, update, offset+1))); + } + + PersistentMapImpl mapValues(f(V value)) { + Owner owner = new Owner(); + return new PersistentMapImpl._new(_root.mapValues(owner, f)); + } + + PersistentMapImpl + union(PersistentMapImpl other, [V combine(V left, V right)]) { + Owner owner = new Owner(); + return new PersistentMapImpl._new(_root.union(owner, other._root, combine)); + } + + PersistentMapImpl + intersection(PersistentMapImpl other, [V combine(V left, V right)]) => + new PersistentMapImpl._new(_root.intersection(null, other._root, combine)); + + Map toMap() { + return _root.toMap(); + } + + Iterable get keys => _root.keys; + + Iterable get values => _root.values; + + Pair pickRandomEntry([Random random]) => _root.pickRandomEntry(random); + + PersistentMapImpl strictMap(Pair f(Pair pair)) => + new PersistentMapImpl.fromPairs(this.map(f)); + + PersistentMapImpl strictWhere(bool f(Pair pair)) => + new PersistentMapImpl.fromPairs(this.where(f)); + + Iterator get iterator => _root.iterator; + + int get length => _root.length; + + // Optimized version of Iterable's contains + bool contains(key) { + final value = this.lookup(key); + return value.isDefined; + } + + TransientMap asTransient() { + return new TransientMapImpl.fromPersistent(this); + } + + PersistentMapImpl withTransient(dynamic f(TransientMap map)) { + TransientMap transient = this.asTransient(); + f(transient); + return transient.asPersistent(); + } + toString() => 'PersistentMap$_root'; +} + +class TransientMapImpl + extends IterableBase> + implements Iterable>, TransientMap { + NodeBase _root; + Owner owner; + + factory TransientMapImpl() => new TransientMapImpl.fromPersistent(new PersistentMap()); + + /** + * Creates an immutable copy of [map] using the default implementation of + * [TransientMap]. + */ + TransientMapImpl.fromPersistent(PersistentMapImpl map) { + owner = new Owner(); + _root = map._root; + } + + TransientMap _adjustRootAndReturn(newRoot) { + _root = newRoot; + return this; + } + + TransientMap + doInsert(K key, V value, [V combine(V oldvalue, V newvalue)]) { + return _adjustRootAndReturn(_root.insert(owner, key, value, combine)); + } + + TransientMap + doInsertIn(List path, V value, [V combine(V oldvalue, V newvalue), offset = 0]) { + if(offset +1 == path.length) + return _adjustRootAndReturn(_root.insert(null, path.last, value)); + + return _adjustRootAndReturn( + _root.adjust(null, path[offset], + (e) => e.insertIn(path, offset+1))); + } + + TransientMap doDelete(K key) { + return _adjustRootAndReturn(_root.delete(owner, key)); + } + + TransientMap doDeleteIn(List path, [offset = 0]) { + if(offset +1 == path.length) + return doDelete(path.last); + + return _adjustRootAndReturn( + _root.adjust(null, path[offset], + (e) => e.deleteIn(path, offset+1))); + } + + Option doLookup(K key) { + return _root.lookup(key); + } + + Option doLookupIn(List path, [offset = 0]) { + Option e = doLookup(path[offset]); + offset++; + + if(path.length == offset) return e; + else return e.isDefined ? e.value.lookupIn(path, offset) : null; + } + + V operator [](K key) { + return _root.lookup(key).asNullable; + } + + void doForEachKeyValue(f(K key, V value)) => _root.forEachKeyValue(f); + + TransientMap doAdjust(K key, V update(V value)) { + return _adjustRootAndReturn(_root.adjust(owner, key, update)); + } + + TransientMap doAdjustIn(List path, V update(V value), [offset = 0]) { + if(path.length == offset +1) return doAdjust(path[offset], update); + + return _adjustRootAndReturn( + _root.adjust(null, path[offset], (e) => e.adjustIn(path, update, offset+1))); + } + + TransientMap doMapValues(f(V value)) { + return _adjustRootAndReturn(_root.mapValues(owner, f)); + } + + TransientMap + doUnion(TransientMapImpl other, [V combine(V left, V right)]) => + _adjustRootAndReturn(_root.union(owner, other._root, combine)); + + TransientMap + doIntersection(TransientMapImpl other, [V combine(V left, V right)]) => + _adjustRootAndReturn(_root.intersection(owner, other._root, combine)); + + /// Returns a mutable copy of `this`. + Map toMap() { + return _root.toMap(); + } + + /// The keys of `this`. + Iterable get keys => _root.keys; + + /// The values of `this`. + Iterable get values => _root.values; + + /// Randomly picks an entry of `this`. + Pair doPickRandomEntry([Random random]) => _root.pickRandomEntry(random); + + Iterator get iterator => _root.iterator; + + int get length => _root.length; + + // Optimized version of Iterable's contains + bool contains(key) { + final value = this.doLookup(key); + return value.isDefined; + } + + PersistentMap asPersistent() => + new PersistentMapImpl.fromTransient(this); + + toString() => 'TransientMap(${owner.hashCode}, $_root)'; +} + /** * Exception used for aborting forEach loops. */ @@ -14,6 +299,62 @@ class _Stop implements Exception {} class Owner {} +abstract class NodeBase + extends IterableBase> { + + int _length; + get length => _length; + + NodeBase(this._length); + + Map toMap() { + Map result = new Map(); + this.forEachKeyValue((K k, V v) { result[k] = v; }); + return result; + } + + String toString() { + StringBuffer buffer = new StringBuffer('{'); + bool comma = false; + this.forEachKeyValue((K k, V v) { + if (comma) buffer.write(', '); + buffer.write('$k: $v'); + comma = true; + }); + buffer.write('}'); + return buffer.toString(); + } + + Iterable get keys => this.map((Pair pair) => pair.fst); + + Iterable get values => this.map((Pair pair) => pair.snd); + + Pair pickRandomEntry([Random random]) => + elementAt((random != null ? random : _random).nextInt(this.length)); + + V operator [](K key) => this.lookup(key).asNullable; + + NodeBase + insert(Owner owner, K key, V value, [V combine(V oldvalue, V newvalue)]); + + NodeBase delete(Owner owner, K key); + + Option lookup(K key); + + void forEachKeyValue(f(K key, V value)); + + NodeBase adjust(Owner owner, K key, V update(V value)); + + NodeBase mapValues(Owner owner, f(V value)); + + NodeBase + union(Owner owner, NodeBase other, [V combine(V left, V right)]); + + NodeBase + intersection(Owner owner, NodeBase other, [V combine(V left, V right)]); + +} + /** * Superclass for _EmptyMap, _Leaf and _SubMap. */ diff --git a/test/deep_map_test.dart b/test/deep_map_test.dart new file mode 100644 index 0000000..ba33136 --- /dev/null +++ b/test/deep_map_test.dart @@ -0,0 +1,52 @@ +// Copyright (c) 2013, Google Inc. All rights reserved. Use of this source code +// is governed by a BSD-style license that can be found in the LICENSE file. + +// Author: Paul Brauner (polux@google.com) + +library option_test; + +import 'package:persistent/persistent.dart'; +import 'package:unittest/unittest.dart'; + +main() { + group('deep persistent data', () { + test('insertIn', () { + PersistentMap map = new PersistentMap(); + map = map.insert('a', new PersistentMap()); + PersistentMap map2 = map.insertIn(['a', 'b'], 'c'); + + expect(map2['a'], equals(new PersistentMap.fromMap({'b': 'c'}))); + expect(map == map2, isFalse); + expect(map, equals(new PersistentMap.fromMap({'a': new PersistentMap()}))); + }); + + test('adjustIn', () { + PersistentMap map = new PersistentMap(); + map = map.insert('a', new PersistentMap()); + map = map.insertIn(['a', 'b'], 'c'); + PersistentMap map2 = map.adjustIn(['a', 'b'], (a) => a+'c'); + expect(map2['a'], equals(new PersistentMap.fromMap({'b': 'cc'}))); + expect(map == map2, isFalse); + expect(map['a'], equals(new PersistentMap.fromMap({'b': 'c'}))); + }); + + test('deleteIn', () { + PersistentMap map = new PersistentMap(); + map = map.insert('a', new PersistentMap()); + map = map.insertIn(['a', 'b'], 'c'); + PersistentMap map2 = map.deleteIn(['a', 'b']); + + expect(map2['a'], equals(new PersistentMap.fromMap({}))); + expect(map == map2, isFalse); + expect(map, equals(new PersistentMap.fromMap({'a': new PersistentMap.fromMap({'b': 'c'})}))); + }); + + test('lookupIn', () { + PersistentMap map = new PersistentMap(); + map = map.insert('a', new PersistentMap()); + map = map.insertIn(['a', 'b'], 'c'); + + expect(map.lookupIn(['a', 'b']).asNullable, equals('c')); + }); + }); +} From 645b88bd937a8451c80f1d50bec3a5fb7a267277 Mon Sep 17 00:00:00 2001 From: Jozef Brandys Date: Thu, 21 Aug 2014 11:33:02 +0200 Subject: [PATCH 008/214] Remove null --- lib/src/map.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/map.dart b/lib/src/map.dart index 446a1bd..169ee2f 100644 --- a/lib/src/map.dart +++ b/lib/src/map.dart @@ -223,5 +223,5 @@ abstract class TransientMap Pair doPickRandomEntry([Random random]); Iterator get iterator; - PersistentMap asPersistent() => null; + PersistentMap asPersistent(); } From b75d37c218913bc5a6e2ba1cbb26a708b6e7477a Mon Sep 17 00:00:00 2001 From: Jozef Brandys Date: Thu, 21 Aug 2014 13:24:35 +0200 Subject: [PATCH 009/214] Fix class definitions --- benchmark/map_bench.dart | 1 + benchmark/src/simple_map_1.dart | 2 +- benchmark/src/simple_map_2.dart | 2 +- test/src/map_model.dart | 2 +- test/src/test_util.dart | 1 + 5 files changed, 5 insertions(+), 3 deletions(-) diff --git a/benchmark/map_bench.dart b/benchmark/map_bench.dart index 5ecfa00..c1b996e 100644 --- a/benchmark/map_bench.dart +++ b/benchmark/map_bench.dart @@ -8,6 +8,7 @@ library map_bench; import 'package:persistent/persistent.dart'; import 'dart:math'; +import 'dart:collection'; part 'src/benchmark.dart'; part 'src/simple_map_1.dart'; diff --git a/benchmark/src/simple_map_1.dart b/benchmark/src/simple_map_1.dart index aa2d59e..0365de6 100644 --- a/benchmark/src/simple_map_1.dart +++ b/benchmark/src/simple_map_1.dart @@ -8,7 +8,7 @@ part of map_bench; /** * Naive implementation of PersistentMap using a [LinkedList] of [Pair]s. */ -class SimplePersistentMap extends PersistentMap { +class SimplePersistentMap extends IterableBase implements PersistentMap { final LinkedList> _list; bool get isEmpty => _list.isNil; diff --git a/benchmark/src/simple_map_2.dart b/benchmark/src/simple_map_2.dart index 68e4a04..730f509 100644 --- a/benchmark/src/simple_map_2.dart +++ b/benchmark/src/simple_map_2.dart @@ -8,7 +8,7 @@ part of map_bench; /** * Naive implementation of PersistentMap dart:core [Map]s. */ -class SimplePersistentMap2 extends PersistentMap { +class SimplePersistentMap2 extends IterableBase implements PersistentMap { final Map _map; bool get isEmpty => _map.isEmpty; diff --git a/test/src/map_model.dart b/test/src/map_model.dart index d5ddd50..2460e6b 100644 --- a/test/src/map_model.dart +++ b/test/src/map_model.dart @@ -10,7 +10,7 @@ final _random = new Random(); /** * Naive implementation of PersistentMap using dart:core [Map]s. */ -class ModelMap extends PersistentMap { +class ModelMap extends IterableBase implements PersistentMap { final Map _map; ModelMap(this._map); diff --git a/test/src/test_util.dart b/test/src/test_util.dart index 3dc4bf2..42d86d8 100644 --- a/test/src/test_util.dart +++ b/test/src/test_util.dart @@ -6,6 +6,7 @@ library test_util; import 'dart:math'; +import 'dart:collection'; import 'package:args/args.dart' as args; import 'package:propcheck/propcheck.dart'; From b2ef9abb838b02c58bd6b021dba02dd7ef9fa212 Mon Sep 17 00:00:00 2001 From: Jozef Brandys Date: Thu, 21 Aug 2014 16:07:33 +0200 Subject: [PATCH 010/214] Cache implementation without tests+ equality in set --- example/cache_example.dart | 38 +++++++++++++++++++++++++ lib/cache.dart | 57 ++++++++++++++++++++++++++++++++++++++ lib/src/set_impl.dart | 8 +++++- 3 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 example/cache_example.dart create mode 100644 lib/cache.dart diff --git a/example/cache_example.dart b/example/cache_example.dart new file mode 100644 index 0000000..233eb3a --- /dev/null +++ b/example/cache_example.dart @@ -0,0 +1,38 @@ +library cache_example; + +import 'dart:async'; + +import 'package:persistent/cache.dart'; + +main() { + Cache cache1 = new Cache(2); + Cache cache2 = new Cache(5); + + var bar = (bar, {foo}) => cache([bar], {#foo: foo}, id: 'fn', fn: f); + + //Calling with defaultCache + print(bar('bar', foo: 'foo')); + + //Calling with cache cache1 + print(do_with_cache(cache1, () => bar('bar', foo: 'foo'))); + + //Calling with cache cache2 + print(do_with_cache(cache2, () => bar('bar', foo: 'foo'))); + + //Calling with cache cache1, using cached + print(do_with_cache(cache1, () => bar('bar', foo: 'foo'))); + print(do_with_cache(cache1, () => bar('bar', foo: 'foo'))); + + //Calling with cache cache1 + print(do_with_cache(cache1, () => bar('bar', foo: 'foo2'))); + //Calling with cache cache1, removes old cached values + print(do_with_cache(cache1, () => bar('bar', foo: 'foo3'))); + + //Calling with cache cache1, old was already removed + print(do_with_cache(cache1, () => bar('bar', foo: 'foo'))); +} + +int counter = 0; +f(bar, {foo: 'foo'}) { + return '$foo $bar ${counter++}'; +} \ No newline at end of file diff --git a/lib/cache.dart b/lib/cache.dart new file mode 100644 index 0000000..a04d141 --- /dev/null +++ b/lib/cache.dart @@ -0,0 +1,57 @@ +library cache; + +import 'dart:collection'; +import 'dart:async'; + +import 'package:persistent/persistent.dart'; + +Cache get cache => Zone.current[#cache] == null ? defaultCache : Zone.current[#cache]; +Cache defaultCache = new EmptyCache(); + +class Cache implements Function { + // Must be greater than 0 + final int size; + + Cache(this.size); + + Queue _cacheQueue = new Queue(); + Map _cacheMap = {}; + + + int get free => size - _cacheQueue.length; + + call(List args, Map kvargs, {id, fn}) { + Function function = fn; + PersistentSet argsImmutable = new PersistentSet.from(args); + PersistentMap map = new PersistentMap.fromMap(kvargs); + map = map.insert('args', argsImmutable); + map = map.insert('id', id); + + if(!_cacheMap.containsKey(map)) { + if(_cacheQueue.length == size) { + _cacheMap.remove(_cacheQueue.removeFirst()); + } + _cacheMap[map] = Function.apply(function, args, kvargs); + _cacheQueue.addLast(map); + } + return _cacheMap[map]; + } +} + +class EmptyCache implements Cache { + EmptyCache(); + + call(List args, Map kvargs, {id, fn}) { + return Function.apply(fn, args, kvargs); + } + + int get size => 0; + int get free => 0; +} + +do_with_cache(Cache cache, Function f) { + var a = new ZoneSpecification(); + return runZoned(() { + return f(); + }, zoneValues: {#cache: cache}); +} \ No newline at end of file diff --git a/lib/src/set_impl.dart b/lib/src/set_impl.dart index 501737c..8485ab8 100644 --- a/lib/src/set_impl.dart +++ b/lib/src/set_impl.dart @@ -69,7 +69,6 @@ class _PersistentSetImpl extends PersistentSetBase { return result; } - bool operator ==(_PersistentSetImpl other) => _map == other._map; Iterator get iterator => _map.map((Pair pair) => pair.fst).iterator; @@ -81,4 +80,11 @@ class _PersistentSetImpl extends PersistentSetBase { // PersistentMap's "elementAt" is optimized E elementAt(int index) => _map.elementAt(index).fst; + + int get hashCode => _map.hashCode; + + bool operator==(other) { + if (other is! _PersistentSetImpl) return false; + return this._map == other._map; + } } From 524b235d828c46aaf9a544398a6c2576711f6e65 Mon Sep 17 00:00:00 2001 From: Jozef Brandys Date: Thu, 21 Aug 2014 16:14:30 +0200 Subject: [PATCH 011/214] Small refactor --- example/cache_example.dart | 2 -- lib/cache.dart | 19 ++++++++++++++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/example/cache_example.dart b/example/cache_example.dart index 233eb3a..afbc0d4 100644 --- a/example/cache_example.dart +++ b/example/cache_example.dart @@ -1,7 +1,5 @@ library cache_example; -import 'dart:async'; - import 'package:persistent/cache.dart'; main() { diff --git a/lib/cache.dart b/lib/cache.dart index a04d141..5317d17 100644 --- a/lib/cache.dart +++ b/lib/cache.dart @@ -6,13 +6,22 @@ import 'dart:async'; import 'package:persistent/persistent.dart'; Cache get cache => Zone.current[#cache] == null ? defaultCache : Zone.current[#cache]; -Cache defaultCache = new EmptyCache(); +Cache defaultCache = new DummyCache(); -class Cache implements Function { +abstract class Cache implements Function { + factory Cache(int size) => + size == 0 ? new DummyCache() : new _CacheImpl(size); + + call(List args, Map kvargs, {id, fn}); + get free; + get size; +} + +class _CacheImpl implements Cache { // Must be greater than 0 final int size; - Cache(this.size); + _CacheImpl(this.size); Queue _cacheQueue = new Queue(); Map _cacheMap = {}; @@ -38,8 +47,8 @@ class Cache implements Function { } } -class EmptyCache implements Cache { - EmptyCache(); +class DummyCache implements Cache { + DummyCache(); call(List args, Map kvargs, {id, fn}) { return Function.apply(fn, args, kvargs); From 7ddf634bc19baf4c4ed54d66ccfef827f413242d Mon Sep 17 00:00:00 2001 From: Sysel Date: Thu, 21 Aug 2014 16:56:49 +0200 Subject: [PATCH 012/214] Map documentation --- lib/src/map.dart | 217 +++++++++++++++++++++++++++++++++++++----- lib/src/map_impl.dart | 4 +- 2 files changed, 195 insertions(+), 26 deletions(-) diff --git a/lib/src/map.dart b/lib/src/map.dart index 169ee2f..ae2b959 100644 --- a/lib/src/map.dart +++ b/lib/src/map.dart @@ -8,11 +8,14 @@ part of persistent; /** - * An immutable map, binding keys of type [K] to values of type [V]. Null values - * are supported but null keys are not. + * An persistent map, binding keys of type [K] to values of type [V]. Null + * values are supported but null keys are not. * + * Persistent data structure is an immutable structure, that provides effective + * creation of slightly mutated copies. + * * In all the examples below `{k1: v1, k2: v2, ...}` is a shorthand for - * `PersistentMap.fromMap({k1: v1, k2: v2, ...})`. + * `new PersistentMap.fromMap({k1: v1, k2: v2, ...})`. */ abstract class PersistentMap implements Iterable> { @@ -40,15 +43,19 @@ abstract class PersistentMap * * If [key] was bound to some `oldvalue` in `this`, it is nevertheless bound * to [value] in the new map. If [key] was bound to some `oldvalue` in `this` - * and if [combine] is provided then [key] it is bound to + * and if [combine] is provided then [key] it bound to * `combine(oldvalue, value)` in the new map. * - * {'a': 1}.insert('b', 2) == {'a': 1, 'b', 2} - * {'a': 1, 'b': 2}.insert('b', 3) == {'a': 3, 'b', 3} - * {'a': 1, 'b': 2}.insert('b', 3, (x,y) => x - y) == {'a': 3, 'b', -1} + * {'a': 1}.insert('b', 2) == {'a': 1, 'b': 2} + * {'a': 1, 'b': 2}.insert('b', 3) == {'a': 3, 'b': 3} + * {'a': 1, 'b': 2}.insert('b', 3, (x,y) => x - y) == {'a': 3, 'b': -1} */ PersistentMap insert(K key, V value, [V combine(V oldvalue, V newvalue)]); + + /** + * Calls [insert] recursively using [path] elemenets as keys. + */ PersistentMap insertIn(List path, V value, [V combine(V oldvalue, V newvalue)]); /** @@ -59,16 +66,24 @@ abstract class PersistentMap * {'a': 1}.delete('b') == {'a': 1} */ PersistentMap delete(K key); + + /** + * Calls [delete] recursively using [path] elemenets as keys. + */ PersistentMap deleteIn(List path); /** * Looks up the value possibly bound to [key] in `this`. Returns - * `Option.some(value)` if it exists, `Option.none()` otherwise. + * [new Option.some(value)] if it exists, [new Option.none()] otherwise. * * {'a': 1}.lookup('b') == Option.none() * {'a': 1, 'b': 2}.lookup('b') == Option.some(2) */ Option lookup(K key); + + /** + * Calls [lookup] recursively using [path] elemenets as keys. + */ Option lookupIn(List path); /** @@ -86,17 +101,21 @@ abstract class PersistentMap * Returns a new map identical to `this` except that the value it possibly * binds to [key] has been adjusted by [update]. * - * {'a': 1, 'b': 2}.adjust('b', (x) => x + 1) == {'a', 1, 'b', 3} - * {'a': 1}.adjust('b', (x) => x + 1) == {'a', 1} + * {'a': 1, 'b': 2}.adjust('b', (x) => x + 1) == {'a': 1, 'b': 3} + * {'a': 1}.adjust('b', (x) => x + 1) == {'a': 1} */ PersistentMap adjust(K key, V update(V value)); + + /** + * Calls [adjust] recursively using [path] elemenets as keys. + */ PersistentMap adjustIn(List path, V update(V value)); /** * Returns a new map identical to `this` where each value has been updated by * [f]. * - * {'a': 1, 'b': 2}.mapValues((x) => x + 1) == {'a', 2, 'b', 3} + * {'a': 1, 'b': 2}.mapValues((x) => x + 1) == {'a': 2, 'b': 3} * {}.mapValues((x) => x + 1) == {} */ PersistentMap mapValues(f(V value)); @@ -161,52 +180,189 @@ abstract class PersistentMap PersistentMap strictWhere(bool f(Pair pair)) => new PersistentMap.fromPairs(this.where(f)); - Iterator get iterator; + /// An iterator through the entries of `this`. + Iterator> get iterator; + /// The number of entries of `this`. int get length; - TransientMap asTransient(); + /** + * Returns a transient copy of `this`. + * + * This is ussualy called to do some changes and + * then create a new [PersistentMap]. + * + * var persistent1 = new PersistentMap.from({'a':1}); + * var transient = persistent1.asTransient(); + * transient.doInsert({'b':2}); + * var persistent2 = new transient.asPersistent(); + */ + TransientMap asTransient(); - PersistentMap withTransient(dynamic f(TransientMap)); + /** + * Creates transient copy of `this`, lets it to be modified by [change] + * and returns persistent result. + * + * var persistent1 = new PersistentMap.from({'a':1}); + * var persistent2 = persistent1.withTransient((m){ + * m.doInsert({'b':2}); + * }); + */ + PersistentMap withTransient(dynamic change(TransientMap)); } +/** + * A transient map, binding keys of type [K] to values of type [V]. Null values + * are supported but null keys are not. + * + * Transient data structure is a mutable structure, that can be effectively + * converted to the persistent data structure. It is ussualy created from + * a persistent structure to apply some changes and obtain a new persistent + * structure. The less changes are done, the more efficient the conversion + * is. + * + * In all the examples below `{k1: v1, k2: v2, ...}` is a shorthand for + * `new PersistentMap.fromMap({k1: v1, k2: v2, ...}).asTransient()`. + */ abstract class TransientMap implements Iterable> { /** - * Creates an mutable copy of [map] using the default implementation of + * Creates an empty map using the default implementation of * [TransientMap]. */ factory TransientMap() => new TransientMapImpl(); + /** + * Binds [key] to [value]. + * + * If [key] was bound to some `oldvalue`, it is nevertheless bound + * to [value]. If [key] was bound to some `oldvalue` + * and if [combine] is provided then [key] is bound to + * `combine(oldvalue, value)`. + * + * var map = PersistentMap.fromMap({'a': 1}).asTransient(); + * map.doInsert('b', 2); // map is now {'a': 1, 'b': 2} + * map.doInsert('b', 3); // map is now {'a': 1, 'b': 3} + * map.doInsert('b', 2, (x,y) => x - y); // map is now {'a': 3, 'b': 1} + */ TransientMap doInsert(K key, V value, [V combine(V oldvalue, V newvalue)]); + /** + * Calls [doInsert] recursively using [path] elemenets as keys. + */ TransientMap - doInsertIn(List path, V value, [V combine(V oldvalue, V newvalue)]); - + doInsertIn(List path, V value, [V combine(V oldvalue, V newvalue)]); + + /** + * Unbinds [key]. + * + * If [key] isn't bound this function has no effect. + * + * var map = PersistentMap.fromMap({'a': 1, 'b': 2}).asTransient(); + * map.doDelete('b', 2); // map is now {'a': 1} + * map.doDelete('b', 2); // map is still {'a': 1} + */ TransientMap doDelete(K key) ; + /** + * Calls [doDelete] recursively using [path] elemenets as keys. + */ TransientMap doDeleteIn(List path); + /** + * Looks up the value possibly bound to [key] in `this`. Returns + * [new Option.some(value)] if it exists, [new Option.none()] otherwise. + * + * var map = PersistentMap.fromMap({'a': 1, 'b': 2}).asTransient(); + * map.doLookup('b') == Option.none(); + * map.doInsert('b', 2); + * map.doLookup('b') == Option.some(2); + */ Option doLookup(K key); + /** + * Calls [doLookup] recursively using [path] elemenets as keys. + */ Option doLookupIn(List path); + /** + * Returns the value for the given [key] or [:null:] if [key] + * is not in the map. + * + * var map = PersistentMap.fromMap({'a': 1, 'b': 2}).asTransient(); + * map['b'] == null; + * map.doInsert('b', 2); + * map['b'] == 2; + */ V operator [](K key); + /** + * Evaluates `f(key, value)` for each (`key`, `value`) pair in `this`. + */ void doForEachKeyValue(f(K key, V value)); - + + /** + * Adjusts the value that is possibly bound to [key] by [update]. + * + * var map = PersistentMap.fromMap({'a': 1}).asTransient(); + * map.doAdjust('b', (x) => x + 1); // map is still {'a': 1} + * map.doUpdate('b', 2); + * map.doAdjust('b', (x) => x + 1); // map is now {'a': 1, 'b': 2} + */ TransientMap doAdjust(K key, V update(V value)); - TransientMap doAdjustIn(List key, V update(V value)); - + /** + * Calls [doAdjust] recursively using [path] elemenets as keys. + */ + TransientMap doAdjustIn(List path, V update(V value)); + /** + * Updates all values by passing them to [f] and replacing them by results. + * + * var map = PersistentMap.fromMap({'a': 1, 'b': 2}).asTransient(); + * map.mapValues((x) => x + 1) // map is now {'a': 2, 'b': 3} + */ TransientMap doMapValues(f(V value)) ; + /** + * Returns a new map whose (key, value) pairs are the union of those of `this` + * and [other]. + * + * The union is right-biased: if a key is present in both `this` and [other], + * the value from [other] is retained. If [combine] is provided, the retained + * value for a `key` present in both `this` and [other] is then + * `combine(leftvalue, rightvalue)` where `leftvalue` is the value bound to + * `key` in `this` and `rightvalue` is the one bound to `key` in [other]. + * + * var mapA = PersistentMap.fromMap({'a': 1}).asTransient(); + * var mapB = PersistentMap.fromMap({'b': 2}).asTransient(); + * var mapAB = PersistentMap.fromMap({'a': 3, 'b': 2}).asTransient(); + * mapA.doUnion(mapB) // returns {'a': 1, 'b': 2} + * mapA.doUnion(mapAB) // returns {'a': 3, 'b': 2} + * mapA.doUnion(mapAB, (x,y) => x + y) // returns {'a': 4, 'b': 2} + */ TransientMap doUnion(TransientMap other, [V combine(V left, V right)]); - + + /** + * Returns a new map whose (key, value) pairs are the intersection of those of + * `this` and [other]. + * + * The intersection is right-biased: values from [other] are retained. If + * [combine] is provided, the retained value for a `key` present in both + * `this` and [other] is then `combine(leftvalue, rightvalue)` where + * `leftvalue` is the value bound to `key` in `this` and `rightvalue` is the + * one bound to `key` in [other]. + * + * var mapA = PersistentMap.fromMap({'a': 1}).asTransient(); + * var mapB = PersistentMap.fromMap({'b': 2}).asTransient(); + * var mapAB = PersistentMap.fromMap({'a': 3, 'b': 2}).asTransient(); + * mapA.doIntersection(mapB) // returns {} + * mapA.doIntersection(mapAB) // returns {'a': 1} + * mapA.doIntersection(mapAB, (x,y) => x + y) // returns {'a': 4} + */ TransientMap doIntersection(TransientMap other, [V combine(V left, V right)]); @@ -214,14 +370,27 @@ abstract class TransientMap Map toMap(); /// The keys of `this`. - Iterable get keys; + /// The values of `this`. - Iterable get values; + /// Randomly picks an entry of `this`. Pair doPickRandomEntry([Random random]); - Iterator get iterator; + + /// An iterator through the entries of `this`. + Iterator> get iterator; + /** + * Returns a persistent copy of `this`. + * + * This is ussualy called when changes to `this` + * are finished + * + * var persistent1 = new PersistentMap.from({'a':1}); + * var transient = persistent1.asTransient(); + * transient.doInsert({'b':2}); + * var persistent2 = new transient.asPersistent(); + */ PersistentMap asPersistent(); } diff --git a/lib/src/map_impl.dart b/lib/src/map_impl.dart index f7d6537..17d2805 100644 --- a/lib/src/map_impl.dart +++ b/lib/src/map_impl.dart @@ -9,7 +9,7 @@ final _random = new Random(); class PersistentMapImpl extends IterableBase> - implements PersistentMap { + implements PersistentMap { NodeBase _root; int _hash; @@ -173,7 +173,7 @@ class PersistentMapImpl class TransientMapImpl extends IterableBase> - implements Iterable>, TransientMap { + implements Iterable>, TransientMap { NodeBase _root; Owner owner; From 7163e2bb83f265743448762578447d60f0c808d0 Mon Sep 17 00:00:00 2001 From: Sysel Date: Tue, 26 Aug 2014 10:56:56 +0200 Subject: [PATCH 013/214] Pair docs, updated Map docs --- lib/src/map.dart | 19 ++++++++++++++++--- lib/src/pair.dart | 21 ++++++++++++++++++++- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/lib/src/map.dart b/lib/src/map.dart index ae2b959..e55c968 100644 --- a/lib/src/map.dart +++ b/lib/src/map.dart @@ -37,6 +37,22 @@ abstract class PersistentMap factory PersistentMap.fromPairs(Iterable> pairs) => new PersistentMapImpl.fromPairs(pairs); + /** + * The equality operator. + * + * Two persistent maps are equal if and only if their sets of keys are equal, + * and the equal keys are bound to the equal values. + * + * Two sets of keys are equal if and only if for each key exists + * an equal key in the other set. + */ + bool operator== (PersistentMap other); + + /* + * The documentation is inherited from the Object + */ + int get hashCode; + /** * Returns a new map identical to `this` except that it binds [key] to * [value]. @@ -220,9 +236,6 @@ abstract class PersistentMap * a persistent structure to apply some changes and obtain a new persistent * structure. The less changes are done, the more efficient the conversion * is. - * - * In all the examples below `{k1: v1, k2: v2, ...}` is a shorthand for - * `new PersistentMap.fromMap({k1: v1, k2: v2, ...}).asTransient()`. */ abstract class TransientMap implements Iterable> { diff --git a/lib/src/pair.dart b/lib/src/pair.dart index 5e55132..3f9b533 100644 --- a/lib/src/pair.dart +++ b/lib/src/pair.dart @@ -5,12 +5,31 @@ part of persistent; + +/** + * A pair of two values encapsulated to the single object. + * + * Mostly used to represent key-value pairs in dictionaries. + */ class Pair { + + /// First value final A fst; + + /// Second value final B snd; - + + /** + * Creates a new pair of given values + */ Pair(this.fst, this.snd); + /** + * The equality operator. + * + * Two pairs are equal if and only if both their first and second + * values are equal. + */ bool operator ==(Pair other) { return (other is Pair) && fst == other.fst From b50850770247c7720d020b47632e731f9ec6b095 Mon Sep 17 00:00:00 2001 From: Jozef Brandys Date: Tue, 26 Aug 2014 10:59:27 +0200 Subject: [PATCH 014/214] Tests for cache. --- example/cache_example.dart | 2 +- lib/cache.dart | 6 +-- pubspec.lock | 8 ++++ pubspec.yaml | 8 ++-- test/cache_test.dart | 92 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 109 insertions(+), 7 deletions(-) create mode 100644 test/cache_test.dart diff --git a/example/cache_example.dart b/example/cache_example.dart index afbc0d4..d704cf6 100644 --- a/example/cache_example.dart +++ b/example/cache_example.dart @@ -6,7 +6,7 @@ main() { Cache cache1 = new Cache(2); Cache cache2 = new Cache(5); - var bar = (bar, {foo}) => cache([bar], {#foo: foo}, id: 'fn', fn: f); + var bar = (bar, {foo}) => cache(args: [bar], kvargs: {#foo: foo}, id: 'fn', fn: f); //Calling with defaultCache print(bar('bar', foo: 'foo')); diff --git a/lib/cache.dart b/lib/cache.dart index 5317d17..dfa8c1e 100644 --- a/lib/cache.dart +++ b/lib/cache.dart @@ -12,7 +12,7 @@ abstract class Cache implements Function { factory Cache(int size) => size == 0 ? new DummyCache() : new _CacheImpl(size); - call(List args, Map kvargs, {id, fn}); + call({List args, Map kvargs, id, fn}); get free; get size; } @@ -29,7 +29,7 @@ class _CacheImpl implements Cache { int get free => size - _cacheQueue.length; - call(List args, Map kvargs, {id, fn}) { + call({List args: const [], Map kvargs: const {}, id, fn}) { Function function = fn; PersistentSet argsImmutable = new PersistentSet.from(args); PersistentMap map = new PersistentMap.fromMap(kvargs); @@ -50,7 +50,7 @@ class _CacheImpl implements Cache { class DummyCache implements Cache { DummyCache(); - call(List args, Map kvargs, {id, fn}) { + call({List args, Map kvargs, id, fn}) { return Function.apply(fn, args, kvargs); } diff --git a/pubspec.lock b/pubspec.lock index e4139c3..70f28ac 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -17,6 +17,14 @@ packages: description: http source: hosted version: "0.9.2+3" + matcher: + description: matcher + source: hosted + version: "0.11.1" + mock: + description: mock + source: hosted + version: "0.11.0+2" path: description: path source: hosted diff --git a/pubspec.yaml b/pubspec.yaml index c3c8753..0c54df1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,12 +3,14 @@ version: 0.8.1 author: Paul Brauner description: Efficient persistent data structures for Dart homepage: https://github.com/polux/persistent -documentation: http://goo.gl/NTNxHk environment: sdk: '>=0.8.10+6 <2.0.0' +documentation: http://goo.gl/NTNxHk +dependencies: + mock: any dev_dependencies: - unittest: '>=0.9.0 <0.10.0' + args: '>=0.9.0 <0.10.0' enumerators: '>=0.5.0 <0.6.0' http: '>=0.9.0 <0.10.0' - args: '>=0.9.0 <0.10.0' propcheck: '>=0.4.0 <0.5.0' + unittest: '>=0.9.0 <0.10.0' diff --git a/test/cache_test.dart b/test/cache_test.dart new file mode 100644 index 0000000..70ef47f --- /dev/null +++ b/test/cache_test.dart @@ -0,0 +1,92 @@ +library cache_example; + +import 'package:persistent/cache.dart'; +import 'package:unittest/unittest.dart'; +import "package:mock/mock.dart"; +import 'dart:async'; + +class MockCache extends Mock implements Cache { + noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); +} + +main() { + group('Cache', () { + Cache dummy, oneCache, twoCache; + MockCache mockCache; + setUp(() { + dummy = new DummyCache(); + oneCache = new Cache(1); + twoCache = new Cache(2); + mockCache = new MockCache(); + }); + + test('function is called', () { + var foo = expectAsync0(() => 'val'); + var cacheFoo = () => cache(id: 'foo', fn: foo); + expect(cacheFoo(), equals('val')); + }); + + test('function is called with correct cache', () { + var foo = () => null; + var cacheFoo = () => cache(id: 'foo', fn: foo); + mockCache.when(callsTo('call')).alwaysReturn('val'); + + expect(do_with_cache(mockCache, cacheFoo), equals('val')); + + mockCache.getLogs(callsTo('call')).verify(happenedOnce); + }); + + test('function is called only once when using same cache', () { + var foo = expectAsync0(() => 'val'); + var cacheFoo = () => cache(id: 'foo', fn: foo); + expect(do_with_cache(oneCache, cacheFoo), equals('val')); + expect(do_with_cache(oneCache, cacheFoo), equals('val')); + }); + + test('function is called twice, when has overflowed', () { + var foo = expectAsync0(() => 'val', count: 2); + var bar = expectAsync0(() => 'val'); + var cacheFoo = () => cache(id: 'foo', fn: foo); + var cacheBar = () => cache(id: 'bar', fn: bar); + + expect(do_with_cache(oneCache, cacheFoo), equals('val')); + expect(do_with_cache(oneCache, cacheBar), equals('val')); + expect(do_with_cache(oneCache, cacheFoo), equals('val')); + }); + + test('calling cache during cache call is called with same cache.', () { + var foo = expectAsync0(() => 'val', count: 1); + var cacheFoo = () => cache(id: 'foo', fn: foo); + + var bar = expectAsync0(() { + expect(cache, equals(twoCache)); + return cacheFoo(); + }); + var cacheBar = () => cache(id: 'bar', fn: bar); + + expect(do_with_cache(twoCache, cacheFoo), equals('val')); //puts in cache + expect(do_with_cache(twoCache, cacheBar), equals('val')); //uses cache for foo + }); + + group('async', () { + test('calling cache during cache call is called with same cache.', () { + var foo = expectAsync0(() => 'val', count: 2); + var cacheFoo = () => cache(id: 'foo', fn: foo); + + var bar = expectAsync0(() => new Future.microtask(() { + expect(cache, equals(twoCache)); + return cacheFoo(); + })); + var cacheBar = () => cache(id: 'bar', fn: bar); + + var checkEqualsVal = expectAsync1((val) => expect(val, equals('val'))); + + expect(do_with_cache(twoCache, cacheFoo), equals('val')); //puts in cache + do_with_cache(twoCache, cacheBar).then(checkEqualsVal); //uses cache for foo + + //replaces cache for test, before executing microtask + expect(do_with_cache(oneCache, cacheFoo), equals('val')); + }); + }); + }); +} From e40a7ae1f45e3dd93e02a80f7d05ee1946d08251 Mon Sep 17 00:00:00 2001 From: Jozef Brandys Date: Tue, 26 Aug 2014 13:50:31 +0200 Subject: [PATCH 015/214] Add tests --- example/cache_example.dart | 11 ++++++++ lib/cache.dart | 3 +-- test/cache_test.dart | 51 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 2 deletions(-) diff --git a/example/cache_example.dart b/example/cache_example.dart index d704cf6..0adfb46 100644 --- a/example/cache_example.dart +++ b/example/cache_example.dart @@ -1,6 +1,7 @@ library cache_example; import 'package:persistent/cache.dart'; +import 'dart:async'; main() { Cache cache1 = new Cache(2); @@ -28,6 +29,16 @@ main() { //Calling with cache cache1, old was already removed print(do_with_cache(cache1, () => bar('bar', foo: 'foo'))); + + try { + var a = do_with_cache(cache1, () => cache(id: '', fn: () => throw new Exception())); + } + catch(e,s) { + print('Catched error'); + } + + Future a = do_with_cache(cache1, () => cache(id: '', fn: () => new Future.microtask(() => throw 5))); + a.catchError((e) => print('Catched Future error')); } int counter = 0; diff --git a/lib/cache.dart b/lib/cache.dart index dfa8c1e..57de923 100644 --- a/lib/cache.dart +++ b/lib/cache.dart @@ -59,8 +59,7 @@ class DummyCache implements Cache { } do_with_cache(Cache cache, Function f) { - var a = new ZoneSpecification(); return runZoned(() { return f(); }, zoneValues: {#cache: cache}); -} \ No newline at end of file +} diff --git a/test/cache_test.dart b/test/cache_test.dart index 70ef47f..8179e24 100644 --- a/test/cache_test.dart +++ b/test/cache_test.dart @@ -54,6 +54,57 @@ main() { expect(do_with_cache(oneCache, cacheFoo), equals('val')); }); + skip_test('function throws - non testable because of weird error catching', () { + var foo = expectAsync2((hello, [world = 'world']) { + expect(hello, equals('hello')); + if(world != 'world') throw new Exception('$world is not world'); + return 'hello world'; + }, count: 2); + + var cacheFoo = (hello, [world]) => + cache(args: [hello, world], id: 'foo', fn: foo); + + Cache cacheError = new Cache(2); + + expect(do_with_cache(cacheError, () => cacheFoo('hello', 'world')), + equals('hello world')); + + try { + do_with_cache(cacheError, () => cacheFoo('hello')); + } + catch(e,s) {} + }); + + test('function called with positional arguments', () { + var foo = expectAsync2((hello, [world = 'world']) { + return '$hello $world'; + }, count: 2); + + var cacheFoo = (hello, [world]) => + cache(args: [hello, world], id: 'foo', fn: foo); + + expect(do_with_cache(twoCache, () => cacheFoo('hello', 'world')), + equals('hello world')); + + expect(do_with_cache(twoCache, () => cacheFoo('hello')), + equals('hello null')); + }); + + test('function called with named arguments', () { + var foo = ({hello, world}) { + return '$hello $world'; + }; + + var cacheFoo = ({hello, world}) => + cache(kvargs: {#hello: hello, #world: world}, id: 'foo', fn: foo); + + expect(do_with_cache(twoCache, () => cacheFoo(hello: 'hello', world: 'world')), + equals('hello world')); + + expect(do_with_cache(twoCache, () => cacheFoo(hello: 'hello')), + equals('hello null')); + }); + test('calling cache during cache call is called with same cache.', () { var foo = expectAsync0(() => 'val', count: 1); var cacheFoo = () => cache(id: 'foo', fn: foo); From 96fe69cb6d6a584174afb8575cdee635541a4cfd Mon Sep 17 00:00:00 2001 From: Sysel Date: Tue, 26 Aug 2014 15:41:41 +0200 Subject: [PATCH 016/214] Benchmark rewritten to use benchmark_harness --- benchmark/map_bench.dart | 26 ++++--- benchmark/src/benchmark.dart | 91 +++++++----------------- benchmark/src/interface.dart | 30 ++++++++ benchmark/src/interface_impl.dart | 114 ++++++++++++++++++++++++++++++ benchmark/src/simple_map_1.dart | 97 ------------------------- benchmark/src/simple_map_2.dart | 86 ---------------------- pubspec.lock | 8 +++ pubspec.yaml | 7 +- 8 files changed, 199 insertions(+), 260 deletions(-) create mode 100644 benchmark/src/interface.dart create mode 100644 benchmark/src/interface_impl.dart delete mode 100644 benchmark/src/simple_map_1.dart delete mode 100644 benchmark/src/simple_map_2.dart diff --git a/benchmark/map_bench.dart b/benchmark/map_bench.dart index c1b996e..da1251f 100644 --- a/benchmark/map_bench.dart +++ b/benchmark/map_bench.dart @@ -6,18 +6,26 @@ library map_bench; import 'package:persistent/persistent.dart'; - -import 'dart:math'; -import 'dart:collection'; +import 'package:benchmark_harness/benchmark_harness.dart'; part 'src/benchmark.dart'; -part 'src/simple_map_1.dart'; -part 'src/simple_map_2.dart'; +part 'src/interface.dart'; +part 'src/interface_impl.dart'; + +var interfaces = { + "LinkedList": () => new LinkedListInterface(), + "PersistentMap": () => new PersistentMapInterface(), + "TransientMap": () => new TransientMapInterface(), + "StandartMap": () => new StandardMapInterface(), + "CopyMap": () => new CopyMapInterface(), +}; void main() { - Benchmark.warmup(); + for (int n = 0; n < 20000; n += 100) { - Benchmark benchmark = new Benchmark(n); - print("$n: ${benchmark.bench()}"); - } + for (String name in interfaces.keys){ + new OverallBenchmark(n, interfaces[name](), name).report(); + } + } + } diff --git a/benchmark/src/benchmark.dart b/benchmark/src/benchmark.dart index 0cb8ba1..f4407aa 100644 --- a/benchmark/src/benchmark.dart +++ b/benchmark/src/benchmark.dart @@ -5,82 +5,43 @@ part of map_bench; -class Benchmark { - - int size; - - Benchmark([this.size = 1000]); - - static warmup() { - new Benchmark(1000).bench(); - } - - Map bench() { - var res = {}; - res["Linked List"] = _bench(() => new SimplePersistentMap()); - res["Mutable Map"] = _bench(() => new SimplePersistentMap2()); - res["Hash Trie"] = _bench(() => new PersistentMap()); - res["Hash Trie Transient"] = _benchT(() => new PersistentMap().asTransient()); - return res; - } - - int _bench(PersistentMap empty()) { - final stopwatch = new Stopwatch(); - stopwatch.start(); - - PersistentMap map = empty(); +class OverallBenchmark extends BenchmarkBase{ + + final int size; + final BenchmarkInterface object; + + + OverallBenchmark(size, object, name): + size = size, + object = object, + super("$name($size)"); + + + void run(){ + + object.create(); + for (int i = 0; i < size; i++) { - map = map.insert("key$i", "foo", (String x, String y) => x + y); - map = map.insert("key$i", "bar", (String x, String y) => x + y); + object.insert("key$i", "foo", (String x, String y) => x + y); + object.insert("key$i", "bar", (String x, String y) => x + y); } for (int i = size * 2; i >= 0; i--) { - map.lookup("key$i"); + object.lookup("key$i"); } + for (int i = 0; i <= size * 2; i++) { - map.lookup("key$i"); + object.lookup("key$i"); } - PersistentMap saved = map; + object.save(); for (int i = size * 2; i >= 0; i--) { - map = map.delete("key$i"); + object.delete("key$i"); } - map = saved; + + object.restore(); for (int i = 0; i < size * 2; i++) { - map = map.delete("key$i"); + object.delete("key$i"); } - - stopwatch.stop(); - return stopwatch.elapsedMicroseconds; - } - - int _benchT(TransientMap empty()) { - final stopwatch = new Stopwatch(); - stopwatch.start(); - - TransientMap map = empty(); - for (int i = 0; i < size; i++) { - map = map.doInsert("key$i", "foo", (String x, String y) => x + y); - map = map.doInsert("key$i", "bar", (String x, String y) => x + y); - } - - for (int i = size * 2; i >= 0; i--) { - map.doLookup("key$i"); - } - for (int i = 0; i <= size * 2; i++) { - map.doLookup("key$i"); - } - - TransientMap saved = map.asPersistent().asTransient(); - for (int i = size * 2; i >= 0; i--) { - map = map.doDelete("key$i"); - } - map = saved; - for (int i = 0; i < size * 2; i++) { - map = map.doDelete("key$i"); - } - - stopwatch.stop(); - return stopwatch.elapsedMicroseconds; } } diff --git a/benchmark/src/interface.dart b/benchmark/src/interface.dart new file mode 100644 index 0000000..6411797 --- /dev/null +++ b/benchmark/src/interface.dart @@ -0,0 +1,30 @@ +part of map_bench; + +abstract class BenchmarkInterface{ + + void create(); + void insert(K key, V value, V combine(V left, V right)); + void lookup(K key); + void delete(K key); + + void save(); + void restore(); +} + + +abstract class EncapsulatingInterface + extends BenchmarkInterface{ + + T object = null; + T object_copy = null; + + T _copy(); + + save(){ + object_copy = _copy(); + } + + restore(){ + return object_copy; + } +} diff --git a/benchmark/src/interface_impl.dart b/benchmark/src/interface_impl.dart new file mode 100644 index 0000000..886054a --- /dev/null +++ b/benchmark/src/interface_impl.dart @@ -0,0 +1,114 @@ +part of map_bench; + +class PersistentMapInterface + extends EncapsulatingInterface>{ + + create() => object = new PersistentMap(); + + insert(K key, V value, V combine(V left, V right)) => + object = object.insert(key, value, combine); + + lookup(K key) => object.lookup(key); + + delete(K key) => object = object.delete(key); + + _copy() => object; +} + + +class TransientMapInterface + extends EncapsulatingInterface>{ + + create() => object = new PersistentMap().asTransient(); + + insert(K key, V value, V combine(V left, V right)) => + object.doInsert(key, value, combine); + + lookup(K key) => object.doLookup(key); + + delete(K key) => object.doDelete(key); + + _copy() => object.asPersistent().asTransient(); +} + + +class StandardMapInterface + extends EncapsulatingInterface>{ + + create() => object = new Map(); + + insert(K key, V value, V combine(V left, V right)) => + object[key] = + object.containsKey(key) ? combine(object[key], value) : value; + + lookup(K key) => object[key]; + + delete(K key) => object.remove(key); + + _copy() => new Map.from(object); +} + + +class CopyMapInterface + extends EncapsulatingInterface>{ + + create() => object = new Map(); + + insert(K key, V value, V combine(V left, V right)){ + object = new Map.from(object); + object[key] = + object.containsKey(key) ? combine(object[key], value) : value; + } + + lookup(K key) => object[key]; + + delete(K key){ + object = new Map.from(object); + object.remove(key); + } + + _copy() => object; +} + + +class LinkedListInterface + extends EncapsulatingInterface>>{ + + create() => object = new Nil>(); + + insert(K key, V value, V combine(V left, V right)){ + LinkedListBuilder> builder = + new LinkedListBuilder>(); + LinkedList> it = object; + while (it.isCons) { + Cons> cons = it.asCons; + Pair elem = cons.elem; + if (elem.fst == key) { + builder.add(new Pair(key, combine(elem.snd, value))); + return builder.build(cons.tail); + } + builder.add(elem); + it = cons.tail; + } + builder.add(new Pair(key, value)); + object = builder.build(); + } + + lookup(K key){ + LinkedList> it = object; + while (it.isCons) { + Cons> cons = it.asCons; + Pair elem = cons.elem; + if (elem.fst == key) return new Option.some(elem.snd); + it = cons.tail; + } + return new Option.none(); + } + + delete(K key){ + object = object.strictWhere((p) => p.fst != key); + } + + _copy() => object; + } + diff --git a/benchmark/src/simple_map_1.dart b/benchmark/src/simple_map_1.dart deleted file mode 100644 index 0365de6..0000000 --- a/benchmark/src/simple_map_1.dart +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code -// is governed by a BSD-style license that can be found in the LICENSE file. - -// Author: Paul Brauner (polux@google.com) - -part of map_bench; - -/** - * Naive implementation of PersistentMap using a [LinkedList] of [Pair]s. - */ -class SimplePersistentMap extends IterableBase implements PersistentMap { - final LinkedList> _list; - - bool get isEmpty => _list.isNil; - - SimplePersistentMap._internal(this._list); - factory SimplePersistentMap() => - new SimplePersistentMap._internal(new Nil()); - - PersistentMap insert(K key, V value, [V combine(V x, V y)]) { - combine = (combine != null) ? combine : (V x, V y) => y; - LinkedList> newList() { - LinkedListBuilder> builder = - new LinkedListBuilder>(); - LinkedList> it = _list; - while (it.isCons) { - Cons> cons = it.asCons; - Pair elem = cons.elem; - if (elem.fst == key) { - builder.add(new Pair(key, combine(elem.snd, value))); - return builder.build(cons.tail); - } - builder.add(elem); - it = cons.tail; - } - builder.add(new Pair(key, value)); - return builder.build(); - } - return new SimplePersistentMap._internal(newList()); - } - - PersistentMap delete(K key) => - new SimplePersistentMap._internal(_list.strictWhere((p) => p.fst != key)); - - Option lookup(K key) { - LinkedList> it = _list; - while (it.isCons) { - Cons> cons = it.asCons; - Pair elem = cons.elem; - if (elem.fst == key) return new Option.some(elem.snd); - it = cons.tail; - } - return new Option.none(); - } - - PersistentMap mapValues(f(V)) => - new SimplePersistentMap._internal( - _list.strictMap((p) => new Pair(p.fst, f(p.snd)))); - - forEachKeyValue(f(K, V)) { - Map tmp = new Map(); - _list.foreach((pair) { - if (!tmp.containsKey(pair.fst)) - tmp[pair.fst] = pair.snd; - }); - tmp.forEach(f); - } - - String toString() => _list.toString(); - - int get length => toMap().length; - - PersistentMap union(PersistentMap other, [V combine(V x, V y)]) { - throw new UnsupportedError("union is not supported"); - } - - PersistentMap intersection(PersistentMap other, - [V combine(V x, V y)]) { - throw new UnsupportedError("intersection is not supported"); - } - - PersistentMap adjust(K key, V update(V)) { - throw new UnsupportedError("adjust is not supported"); - } - - Iterator> get iterator { - final res = >[]; - this.forEachKeyValue((k, v) { - res.add(new Pair(k, v)); - }); - return res.iterator; - } - - Pair pickRandomEntry([Random random]) { - throw new UnsupportedError(""); - } -} diff --git a/benchmark/src/simple_map_2.dart b/benchmark/src/simple_map_2.dart deleted file mode 100644 index 730f509..0000000 --- a/benchmark/src/simple_map_2.dart +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code -// is governed by a BSD-style license that can be found in the LICENSE file. - -// Author: Paul Brauner (polux@google.com) - -part of map_bench; - -/** - * Naive implementation of PersistentMap dart:core [Map]s. - */ -class SimplePersistentMap2 extends IterableBase implements PersistentMap { - final Map _map; - - bool get isEmpty => _map.isEmpty; - - SimplePersistentMap2._internal(this._map); - factory SimplePersistentMap2() => - new SimplePersistentMap2._internal(new Map()); - - PersistentMap insert(K key, V value, [V combine(V x, V y)]) { - combine = (combine != null) ? combine : (V x, V y) => y; - Map newmap = new Map.from(_map); - newmap[key] = _map.containsKey(key) ? combine(_map[key], value) : value; - return new SimplePersistentMap2._internal(newmap); - } - - PersistentMap delete(K key) { - Map newmap = new Map.from(_map); - newmap.remove(key); - return new SimplePersistentMap2._internal(newmap); - } - - Option lookup(K key) { - if (_map.containsKey(key)) { - return new Option.some(_map[key]); - } else { - return new Option.none(); - } - } - - PersistentMap mapValues(f(V)) { - Map newmap = new Map.from(_map); - _map.forEach((K key, V value) { - newmap[key] = f(value); - }); - return new SimplePersistentMap2._internal(newmap); - } - - PersistentMap adjust(K key, V update(V)) { - if (_map.containsKey(key)) { - Map newmap = new Map.from(_map); - newmap[key] = update(_map[key]); - return new SimplePersistentMap2._internal(newmap); - } - return this; - } - - void forEachKeyValue(f(K, V)) { - _map.forEach(f); - } - - String toString() => _map.toString(); - - int get length => _map.length; - - PersistentMap union(PersistentMap other, [V combine(V x, V y)]) { - throw new UnsupportedError("union is not supported"); - } - - PersistentMap intersection(PersistentMap other, - [V combine(V x, V y)]) { - throw new UnsupportedError("intersection is not supported"); - } - - Iterator> get iterator { - final res = >[]; - this.forEachKeyValue((k, v) { - res.add(new Pair(k, v)); - }); - return res.iterator; - } - - Pair pickRandomEntry([Random random]) { - throw new UnsupportedError(""); - } -} diff --git a/pubspec.lock b/pubspec.lock index e4139c3..7d219c0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,6 +5,14 @@ packages: description: args source: hosted version: "0.9.0" + benchmark_harness: + description: benchmark_harness + source: hosted + version: "1.0.4" + browser: + description: browser + source: hosted + version: "0.10.0+2" collection: description: collection source: hosted diff --git a/pubspec.yaml b/pubspec.yaml index c3c8753..9f440e3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,12 +3,13 @@ version: 0.8.1 author: Paul Brauner description: Efficient persistent data structures for Dart homepage: https://github.com/polux/persistent -documentation: http://goo.gl/NTNxHk environment: sdk: '>=0.8.10+6 <2.0.0' +documentation: http://goo.gl/NTNxHk dev_dependencies: - unittest: '>=0.9.0 <0.10.0' + args: '>=0.9.0 <0.10.0' + benchmark_harness: any enumerators: '>=0.5.0 <0.6.0' http: '>=0.9.0 <0.10.0' - args: '>=0.9.0 <0.10.0' propcheck: '>=0.4.0 <0.5.0' + unittest: '>=0.9.0 <0.10.0' From 8c93f6cfaf0be1fcda3f7343c126e40a06dd6dd7 Mon Sep 17 00:00:00 2001 From: Jozef Brandys Date: Tue, 26 Aug 2014 17:27:49 +0200 Subject: [PATCH 017/214] Transient map is not editable after calling asPersistent on it --- lib/src/map_impl.dart | 14 ++++++++++---- test/transient_map_test.dart | 3 ++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/src/map_impl.dart b/lib/src/map_impl.dart index 17d2805..be91a9b 100644 --- a/lib/src/map_impl.dart +++ b/lib/src/map_impl.dart @@ -175,7 +175,11 @@ class TransientMapImpl extends IterableBase> implements Iterable>, TransientMap { NodeBase _root; - Owner owner; + Owner _owner; + get owner => _owner != null ? + _owner + : + throw new Exception('Cannot modify TransientMap after calling asPersistent.'); factory TransientMapImpl() => new TransientMapImpl.fromPersistent(new PersistentMap()); @@ -184,7 +188,7 @@ class TransientMapImpl * [TransientMap]. */ TransientMapImpl.fromPersistent(PersistentMapImpl map) { - owner = new Owner(); + _owner = new Owner(); _root = map._root; } @@ -286,8 +290,10 @@ class TransientMapImpl return value.isDefined; } - PersistentMap asPersistent() => - new PersistentMapImpl.fromTransient(this); + PersistentMap asPersistent() { + _owner = null; + return new PersistentMapImpl.fromTransient(this); + } toString() => 'TransientMap(${owner.hashCode}, $_root)'; } diff --git a/test/transient_map_test.dart b/test/transient_map_test.dart index 6a7e12e..e0c5e22 100644 --- a/test/transient_map_test.dart +++ b/test/transient_map_test.dart @@ -98,11 +98,12 @@ checkAll(Map map, List tran, List per, List Date: Tue, 26 Aug 2014 18:09:21 +0200 Subject: [PATCH 018/214] vector first version (not transient yet) --- lib/persistent.dart | 1 + lib/src/vector.dart | 392 +++++++++++++++++++++++++++++++++++++ test/src/test_util.dart | 10 + test/src/vector_model.dart | 36 ++++ test/vector_test.dart | 81 ++++++++ 5 files changed, 520 insertions(+) create mode 100644 lib/src/vector.dart create mode 100644 test/src/vector_model.dart create mode 100644 test/vector_test.dart diff --git a/lib/persistent.dart b/lib/persistent.dart index 58f0977..aba587e 100644 --- a/lib/persistent.dart +++ b/lib/persistent.dart @@ -15,3 +15,4 @@ part 'src/set_impl.dart'; part 'src/linked_list.dart'; part 'src/option.dart'; part 'src/pair.dart'; +part 'src/vector.dart'; \ No newline at end of file diff --git a/lib/src/vector.dart b/lib/src/vector.dart new file mode 100644 index 0000000..1f5e610 --- /dev/null +++ b/lib/src/vector.dart @@ -0,0 +1,392 @@ +part of persistent; + +const int _SHIFT = 5; +const int _SIZE = 1 << _SHIFT; +const int _MASK = _SIZE - 1; +var _NOT_SET; + +getNotSet() { + if (_NOT_SET == null) + _NOT_SET = {}; + return _NOT_SET; +} + +// Wrapper for referencing bool values +class Bool { + bool value = false; +} + +class Owner {} + +abstract class PersistentVectorInterface { + E get(int index, [E notSetValue = null]); + PersistentVectorInterface set(int index, E value); + E get first; + E get last; + + PersistentVectorInterface delete(int index); + String toString(); + PersistentVectorInterface push(E value); + PersistentVectorInterface pop(); + PersistentVectorInterface from(Iterable values); +} + +abstract class PersistentVectorBase extends IterableBase implements PersistentVectorInterface { + int _size; + + E get first => get(0); + E get last => get(this.length ? this.length - 1 : 0); + int get length => _size; + Iterator get iterator => new PersistentVectorIterator(this); +} + +class PersistentVectorIterator extends Iterator { + PersistentVectorBase _parentVector; + int _position = -1; + int _length; + + PersistentVectorIterator(this._parentVector) { + _length = _parentVector.length; + } + + bool moveNext() { + if (_length == _position + 1) return false; + _position++; + return true; + } + + E get current => _parentVector.get(_position); +} + +class PersistentVector extends PersistentVectorBase { + int _origin; + Owner __ownerID; + VNode _root; + VNode _tail; + int _level; + // cached hashCode. + int _hashCode = null; + + factory PersistentVector.from(Iterable values) { + if (values.length == 0) { + return new PersistentVector.empty(); + } + PersistentVector result = new PersistentVector.empty(); + values.forEach((E value) { + result = result.push(value); + }); + return result; + } + + factory PersistentVector.empty() { + var x = new PersistentVector._prototype(); + return x; + } + + PersistentVector._prototype() { + this._origin = 0; + this.__ownerID = null; + this._root = new VNode([], __ownerID); + this._tail = _root; + this._level = _SHIFT; + this._size = 0; + } + + factory PersistentVector._make(int origin, int size, int level, VNode root, VNode tail, Owner ownerID) { + var x = new PersistentVector._prototype(); + x._origin = origin; + x._size = size; + x._level = level; + x._root = root; + x._tail = tail; + x.__ownerID = ownerID; + return x; + } + + E get(int index, [E notSetValue = null]) { + index = _checkIndex(index); + if (index >= this._size) { + return notSetValue; + } + var node = _vectorNodeFor(index); + var maskedIndex = index & _MASK; + return (node != null && (notSetValue == null || node._array.length > maskedIndex)) ? + node._array[maskedIndex] : notSetValue; + } + + PersistentVector set(int index, E value) { + if (index >= this.length) { + if (value == getNotSet()) + return this; + // TODO: withMutations when ready + return this._resize(index+1).set(index, value); + } + + var vector = this; + var newTail = vector._tail; + var newRoot = vector._root; + var didAlter = new Bool(); + if (index >= _getTailOffset(vector._size)) { + newTail = _updateVNode(newTail, vector.__ownerID, 0, index, value, didAlter); + } else { + newRoot = _updateVNode(newRoot, vector.__ownerID, vector._level, index, value, didAlter); + } + if (!didAlter.value) { + return vector; + } + if (vector.__ownerID != null) { + vector._root = newRoot; + vector._tail = newTail; + vector.__altered = true; + return vector; + } + return new PersistentVector._make(vector._origin, vector._size, vector._level, newRoot, newTail, null); + } + + PersistentVector push(E value) { + // TODO: withMutations + return this._resize(this.length+1).set(this.length, value); + } + + PersistentVector pushAll(List values) { + var t = this._resize(this.length + values.length); + for (int i = 0; i < values.length; i++) { + t = t.set(this.length+i, values[i]); + } + return t; + } + + PersistentVector pop() { + return this._resize(this.length-1); + } + + int _getTailOffset(int size) { + return size < _SIZE ? 0 : (((size - 1) >> _SHIFT) << _SHIFT); + } + + int _checkIndex(int index) { + if (index < 0 || index >= _size) { + throw new RangeError.value(index); + } + return index; + } + + VNode _vectorNodeFor(int index) { + if (index >= _getTailOffset(this._size)) { + return this._tail; + } + if (index < 1 << (this._level + _SHIFT)) { + var node = this._root; + var level = this._level; + while (node != null && level > 0) { + node = node._array[(index >> level) & _MASK]; + level -= _SHIFT; + } + return node; + } + } + + PersistentVector _resize(int end) { + var owner; + if (__ownerID == null) { + owner = __ownerID; + } else { + owner = new Owner(); + } + //var owner = __ownerID || new Owner(); + var oldSize = _size; + var newSize = end; + + if (oldSize == newSize) return this; + var newLevel = _level; + var newRoot = _root; + + var oldTailOffset = _getTailOffset(oldSize); + var newTailOffset = _getTailOffset(newSize); + while (newTailOffset >= 1 << (newLevel + _SHIFT)) { + newRoot = new VNode(newRoot != null && newRoot._array.length > 0 ? [newRoot] : [], owner); + newLevel += _SHIFT; + } + + var oldTail = _tail; + var newTail = newTailOffset < oldTailOffset ? + _vectorNodeFor(newSize - 1) : + newTailOffset > oldTailOffset ? new VNode([], owner) : oldTail; + + if (newTailOffset > oldTailOffset && oldSize > 0 && oldTail.length > 0) { + newRoot = newRoot._ensureOwner(owner); + var node = newRoot; + for (var level = newLevel; level > _SHIFT; level -= _SHIFT) { + var idx = (oldTailOffset >> level) & _MASK; + node._array[idx] = node._array[idx] == null ? node._array[idx]._ensureOwner(owner) : new VNode([], owner); + node = node._array[idx]; + } + node._set((oldTailOffset >> _SHIFT) & _MASK, oldTail); + } + + if (newSize < oldSize) { + newTail = newTail._removeAfter(owner, 0, newSize); + } + + if (__ownerID) { + _size = newSize; + _origin = 0; + _level = newLevel; + _root = newRoot; + _tail = newTail; + return this; + } + return new PersistentVector._make(0, newSize, newLevel, newRoot, newTail, null); + + } + + void printInfo() { + print("Size: $_size"); + print("Origin: $_origin"); + print("Level: $_level"); + print("Root: $_root"); + print("Tail: $_tail"); + } + + int get hashCode { + if (this._hashCode == null) { + int result = 17; + for (E value in this) { + result = 37 * result + value.hashCode % 1000000009; + } + this._hashCode = result; + } + return this._hashCode; + } + + bool operator==(other) { + if (other is! PersistentVector) return false; + PersistentVector otherVector = other; + if (this.hashCode != otherVector.hashCode) return false; + if (this.length != otherVector.length) return false; + for (int i = 0; i < this.length; i++) { + if (this.get(i) != otherVector.get(i)) return false; + } + return true; + } + +} + +class VNode { + List _array; + int _ownerID; + + int get length => _array.length; + + String toString() { + return "VNode: " + _array.toString(); + } + + VNode(this._array, this._ownerID); + + void _set(int index, value) { + if (_array.length > index) { + _array[index] = value; + } else { + for (int i = _array.length; i < index; i++) { + _array.add(null); + } + _array.add(value); + } + } + + VNode _removeAfter(Owner ownerID, int level, int index) { + if (index == level ? 1 << level : 0 || this.length == 0) { + return this; + } + var sizeIndex = ((index - 1) >> level) & _MASK; + if (sizeIndex >= this.length) { + return this; + } + var removingLast = sizeIndex == this.length - 1; + var newChild; + if (level > 0) { + var oldChild = this._array[sizeIndex]; + if (oldChild == null) newChild = null; + else { + newChild = oldChild.removeAfter(ownerID, level - SHIFT, index); + } + if (newChild == oldChild && removingLast) { + return this; + } + } + if (removingLast && newChild == null) { + return this; + } + var editable = _mutableVNode(this, ownerID); + if (!removingLast) { + editable._array.removeRange(sizeIndex + 1, editable._array.length); + } + if (newChild != null) { + editable._array[sizeIndex] = newChild; + } + return editable; + + } + + _ensureOwner(ownerID) { + if (ownerID && ownerID == _ownerID) + return this; + return new VNode(_array.sublist(0), ownerID); + } + + VNode _update(ownerID, level, index, value, Bool didAlter) { + var deleted = value == _NOT_SET; + var node = this; + var newNode; + var idx = (index >> level) & _MASK; + var nodeHas = node != null && idx < node._array.length; + if (deleted && !nodeHas) { + return node; + } + if (level > 0) { + var lowerNode; + if (node != null && node._array.length > idx) { + lowerNode = node._array[idx]; + } else { + lowerNode = null; + } + var newLowerNode = _updateVNode(lowerNode, ownerID, level - _SHIFT, index, value, didAlter); + if (newLowerNode == lowerNode) { + return node; + } + var newNode = _mutableVNode(node, ownerID); + newNode._set(idx, newLowerNode); + return newNode; + } + + if (!deleted && nodeHas && node._array[idx] == value) { + return node; + } + + didAlter.value = true; + + newNode = _mutableVNode(node, ownerID); + if (deleted) { + newNode._set(idx, null); + } else { + newNode._set(idx, value); + } + return newNode; + } +} + +_updateVNode(VNode node, Owner ownerID, int level, int index, value, Bool didAlter) { + if (node == null) { + var t = new VNode([], new Owner()); + return t._update(ownerID, level, index, value, didAlter); + } + return node._update(ownerID, level, index, value, didAlter); +} + +VNode _mutableVNode(VNode node, Owner ownerID) { + if (ownerID != null && node != null && ownerID == node._ownerID) { + return node; + } + return new VNode(node != null ? node._array.sublist(0) : [], ownerID); +} diff --git a/test/src/test_util.dart b/test/src/test_util.dart index 3dc4bf2..64343df 100644 --- a/test/src/test_util.dart +++ b/test/src/test_util.dart @@ -13,9 +13,11 @@ import 'package:enumerators/combinators.dart' as c; import 'package:enumerators/enumerators.dart' as en; import 'package:persistent/persistent.dart'; import 'package:unittest/unittest.dart'; +import 'dart:collection'; part 'map_model.dart'; part 'set_model.dart'; +part 'vector_model.dart'; /** * A datatype with an imperfect hash function to use as a key for testing maps. @@ -163,6 +165,14 @@ bool setEquals(Set s1, Set s2) { return true; } +bool vectorEquals(List l1, List l2) { + if (l1.length != l2.length) return false; + for (int i = 0; i < l1.length; i++) { + if (l1[i] != l2[i]) return false; + } + return true; +} + bool sameMap(PersistentMap pm, ModelMap mm) => mapEquals(pm.toMap(), mm._map); bool sameSet(PersistentSet ps, ModelSet ms) => setEquals(ps.toSet(), ms.zet); diff --git a/test/src/vector_model.dart b/test/src/vector_model.dart new file mode 100644 index 0000000..bbb0bf3 --- /dev/null +++ b/test/src/vector_model.dart @@ -0,0 +1,36 @@ +part of test_util; + +class ModelVector extends IterableBase implements PersistentVectorInterface { + final List _vector; + int get length => _vector.length; + + ModelVector(this._vector); + + bool get isEmpty => _vector.isEmpty; + + ModelVector push(E value) { + var result = new List.from(_vector); + result.add(value); + return new ModelVector(result); + } + + ModelVector pop() { + var result = new List.from(_vector); + result.removeLast(); + return new ModelVector(result); + } + + ModelVector set(int key, E value) { + var result = new List.from(_vector); + result[key] = value; + return new ModelVector(result); + } + + factory ModelVector.empty() { + return new ModelVector([]); + } + + String toString() => _vector.toString(); + + Iterator get iterator => _vector.iterator; +} \ No newline at end of file diff --git a/test/vector_test.dart b/test/vector_test.dart new file mode 100644 index 0000000..b8ee9e9 --- /dev/null +++ b/test/vector_test.dart @@ -0,0 +1,81 @@ +library vector_test; + +import 'package:unittest/unittest.dart'; +import 'package:persistent/persistent.dart'; +import 'src/test_util.dart'; +import 'dart:math'; +import 'dart:core'; + +final NUM_OPERATIONS = 10000; + +main() { + test('random_test', () { + Random r = new Random(47); + int next = 1; + PersistentVector vector = new PersistentVector.empty(); + ModelVector model = new ModelVector.empty(); + List actions = []; + + List actionsPrep = [{ + 'times': 10, + 'prep': prepInsert, + 'call': callInsert, + }, { + 'times': 8, + 'prep': prepDelete, + 'call': callDelete, + }, { + 'times': 7, + 'prep': prepSet, + 'call': callSet, + }]; + actionsPrep.forEach((val) => actions.addAll(new List.filled(val['times'], val))); + + for (int i = 0; i < NUM_OPERATIONS; i++) { + //print(i); + int nextRand = r.nextInt(actions.length); + Map action = actions[nextRand]; + var prep; + if (action.containsKey('prep')) { + prep = action['prep'](r, vector); + } else { + prep = null; + } + vector = action['call'](vector, prep); + model = action['call'](model, prep); + checker(vector, model); + } + checker(vector, model); + var c = new PersistentVector.from(vector); + var d = new PersistentVector.from(model); + expect(c, orderedEquals(d)); + }); +} + +checker(vector, model) { + //print("checking"); + var a = new List.from(vector); + var b = new List.from(model); + expect (a, orderedEquals(b)); +} + +prepInsert(Random r, PersistentVector vector) { + return r.nextInt(47474); +} + +prepSet(Random r, PersistentVector vector) { + var a = r.nextInt(vector.length); + var b = r.nextInt(47474); + return { + 'key': a, + 'value': b, + }; +} + +prepDelete(Random r, PersistentVector vector) { + return vector.length != 0; +} + +callInsert(PersistentVectorInterface vector, prep) => vector.push(prep); +callSet(PersistentVectorInterface vector, prep) => vector.set(prep['key'], prep['value']); +callDelete(PersistentVectorInterface vector, prep) => prep ? vector.pop() : vector; \ No newline at end of file From ba9c770fd202fa11ae67b911c3239ef0da7fae1c Mon Sep 17 00:00:00 2001 From: Roman Hudec Date: Wed, 27 Aug 2014 13:13:17 +0200 Subject: [PATCH 019/214] vector with mutations --- lib/src/vector.dart | 61 ++++++++++++++++++++++++++++++++++++++----- test/vector_test.dart | 25 ++++++++++++------ 2 files changed, 71 insertions(+), 15 deletions(-) diff --git a/lib/src/vector.dart b/lib/src/vector.dart index 1f5e610..e2daaab 100644 --- a/lib/src/vector.dart +++ b/lib/src/vector.dart @@ -66,6 +66,7 @@ class PersistentVector extends PersistentVectorBase { int _level; // cached hashCode. int _hashCode = null; + bool __altered = false; factory PersistentVector.from(Iterable values) { if (values.length == 0) { @@ -92,7 +93,7 @@ class PersistentVector extends PersistentVectorBase { this._size = 0; } - factory PersistentVector._make(int origin, int size, int level, VNode root, VNode tail, Owner ownerID) { + factory PersistentVector._make(int origin, int size, int level, VNode root, VNode tail, [Owner ownerID = null, int hashCode = null]) { var x = new PersistentVector._prototype(); x._origin = origin; x._size = size; @@ -118,8 +119,7 @@ class PersistentVector extends PersistentVectorBase { if (index >= this.length) { if (value == getNotSet()) return this; - // TODO: withMutations when ready - return this._resize(index+1).set(index, value); + return this.withMutations((vect) => vect._resize(index+1).set(index,value)); } var vector = this; @@ -140,12 +140,12 @@ class PersistentVector extends PersistentVectorBase { vector.__altered = true; return vector; } - return new PersistentVector._make(vector._origin, vector._size, vector._level, newRoot, newTail, null); + return new PersistentVector._make(vector._origin, vector._size, vector._level, newRoot, newTail); } PersistentVector push(E value) { - // TODO: withMutations - return this._resize(this.length+1).set(this.length, value); + var len = this.length; + return this.withMutations((vect) => vect._resize(len+1).set(len, value)); } PersistentVector pushAll(List values) { @@ -228,18 +228,20 @@ class PersistentVector extends PersistentVectorBase { newTail = newTail._removeAfter(owner, 0, newSize); } - if (__ownerID) { + if (__ownerID != null) { _size = newSize; _origin = 0; _level = newLevel; _root = newRoot; _tail = newTail; + __altered = true; return this; } return new PersistentVector._make(0, newSize, newLevel, newRoot, newTail, null); } + // TODO: debug funkcia, umazat void printInfo() { print("Size: $_size"); print("Origin: $_origin"); @@ -270,6 +272,51 @@ class PersistentVector extends PersistentVectorBase { return true; } + PersistentVector clear() { + if (this.length == 0) { + return this; + } + if (this.__ownerID != null) { + this._size = 0; + this._origin = 0; + this._level = _SHIFT; + this._root = this._tail = null; + this._hashCode = null; + this.__altered = true; + return this; + } + return new PersistentVector.empty(); + } + + PersistentVector _ensureOwner(Owner ownerID) { + if (ownerID == this.__ownerID) { + return this; + } + if (ownerID == null) { + this.__ownerID = ownerID; + return this; + } + return new PersistentVector._make(this._origin, this._size, this._level, this._root, this._tail, ownerID, this._hashCode); + } + + PersistentVector asMutable() { + return this.__ownerID != null ? this : this._ensureOwner(new Owner()); + } + + PersistentVector asImmutable() { + return this._ensureOwner(null); + } + + bool wasAltered() { + return this.__altered; + } + + PersistentVector withMutations(fn) { + var mutable = this.asMutable(); + fn(mutable); + return mutable.wasAltered() ? mutable._ensureOwner(this.__ownerID) : this; + } + } class VNode { diff --git a/test/vector_test.dart b/test/vector_test.dart index b8ee9e9..507bbb5 100644 --- a/test/vector_test.dart +++ b/test/vector_test.dart @@ -12,6 +12,7 @@ main() { test('random_test', () { Random r = new Random(47); int next = 1; + PersistentVector mut = new PersistentVector.empty(); PersistentVector vector = new PersistentVector.empty(); ModelVector model = new ModelVector.empty(); List actions = []; @@ -20,40 +21,44 @@ main() { 'times': 10, 'prep': prepInsert, 'call': callInsert, + 'mut': mutInsert, }, { 'times': 8, 'prep': prepDelete, 'call': callDelete, + 'mut': mutDelete, }, { 'times': 7, 'prep': prepSet, 'call': callSet, + 'mut': mutSet, }]; actionsPrep.forEach((val) => actions.addAll(new List.filled(val['times'], val))); for (int i = 0; i < NUM_OPERATIONS; i++) { - //print(i); int nextRand = r.nextInt(actions.length); Map action = actions[nextRand]; var prep; - if (action.containsKey('prep')) { - prep = action['prep'](r, vector); - } else { - prep = null; - } + prep = action['prep'](r, vector); vector = action['call'](vector, prep); model = action['call'](model, prep); + mut = action['mut'](mut, prep); checker(vector, model); + checker(vector, mut); } checker(vector, model); + checker(mut, model); + + // test conversion var c = new PersistentVector.from(vector); var d = new PersistentVector.from(model); + var e = new PersistentVector.from(mut); expect(c, orderedEquals(d)); + expect(e, orderedEquals(d)); }); } checker(vector, model) { - //print("checking"); var a = new List.from(vector); var b = new List.from(model); expect (a, orderedEquals(b)); @@ -78,4 +83,8 @@ prepDelete(Random r, PersistentVector vector) { callInsert(PersistentVectorInterface vector, prep) => vector.push(prep); callSet(PersistentVectorInterface vector, prep) => vector.set(prep['key'], prep['value']); -callDelete(PersistentVectorInterface vector, prep) => prep ? vector.pop() : vector; \ No newline at end of file +callDelete(PersistentVectorInterface vector, prep) => prep ? vector.pop() : vector; + +mutInsert(PersistentVector vector, prep) => vector.withMutations((vect) => vect.push(prep)); +mutSet(PersistentVector vector, prep) => vector.withMutations((vect) => vect.set(prep['key'], prep['value'])); +mutDelete(PersistentVector vector, prep) => prep ? vector.withMutations((vect) => vect.pop()) : vector; \ No newline at end of file From 3fb0d9013ed8ed0c8baddf1242e3a851978d5397 Mon Sep 17 00:00:00 2001 From: Sysel Date: Wed, 27 Aug 2014 16:42:17 +0200 Subject: [PATCH 020/214] Reorganized benchmark structure --- .../benchmark_overall.dart} | 2 +- benchmark/{src => map_bench}/interface.dart | 0 .../{src => map_bench}/interface_impl.dart | 0 benchmark/{ => map_bench}/map_bench.dart | 12 ++-- benchmark/map_bench_wordcount.csv | 23 ------- benchmark/map_bench_wordcount.dart | 62 ------------------- 6 files changed, 7 insertions(+), 92 deletions(-) rename benchmark/{src/benchmark.dart => map_bench/benchmark_overall.dart} (93%) rename benchmark/{src => map_bench}/interface.dart (100%) rename benchmark/{src => map_bench}/interface_impl.dart (100%) rename benchmark/{ => map_bench}/map_bench.dart (74%) delete mode 100644 benchmark/map_bench_wordcount.csv delete mode 100644 benchmark/map_bench_wordcount.dart diff --git a/benchmark/src/benchmark.dart b/benchmark/map_bench/benchmark_overall.dart similarity index 93% rename from benchmark/src/benchmark.dart rename to benchmark/map_bench/benchmark_overall.dart index f4407aa..42bcc3d 100644 --- a/benchmark/src/benchmark.dart +++ b/benchmark/map_bench/benchmark_overall.dart @@ -23,7 +23,7 @@ class OverallBenchmark extends BenchmarkBase{ for (int i = 0; i < size; i++) { object.insert("key$i", "foo", (String x, String y) => x + y); - object.insert("key$i", "bar", (String x, String y) => x + y); + //object.insert("key$i", "bar", (String x, String y) => x + y); } for (int i = size * 2; i >= 0; i--) { diff --git a/benchmark/src/interface.dart b/benchmark/map_bench/interface.dart similarity index 100% rename from benchmark/src/interface.dart rename to benchmark/map_bench/interface.dart diff --git a/benchmark/src/interface_impl.dart b/benchmark/map_bench/interface_impl.dart similarity index 100% rename from benchmark/src/interface_impl.dart rename to benchmark/map_bench/interface_impl.dart diff --git a/benchmark/map_bench.dart b/benchmark/map_bench/map_bench.dart similarity index 74% rename from benchmark/map_bench.dart rename to benchmark/map_bench/map_bench.dart index da1251f..c215a93 100644 --- a/benchmark/map_bench.dart +++ b/benchmark/map_bench/map_bench.dart @@ -8,21 +8,21 @@ library map_bench; import 'package:persistent/persistent.dart'; import 'package:benchmark_harness/benchmark_harness.dart'; -part 'src/benchmark.dart'; -part 'src/interface.dart'; -part 'src/interface_impl.dart'; +part 'benchmark_overall.dart'; +part 'interface.dart'; +part 'interface_impl.dart'; var interfaces = { - "LinkedList": () => new LinkedListInterface(), + //"LinkedList": () => new LinkedListInterface(), "PersistentMap": () => new PersistentMapInterface(), "TransientMap": () => new TransientMapInterface(), "StandartMap": () => new StandardMapInterface(), - "CopyMap": () => new CopyMapInterface(), + //"CopyMap": () => new CopyMapInterface(), }; void main() { - for (int n = 0; n < 20000; n += 100) { + for (int n in [1,10,100,1000,10000]) { for (String name in interfaces.keys){ new OverallBenchmark(n, interfaces[name](), name).report(); } diff --git a/benchmark/map_bench_wordcount.csv b/benchmark/map_bench_wordcount.csv deleted file mode 100644 index dcedf2d..0000000 --- a/benchmark/map_bench_wordcount.csv +++ /dev/null @@ -1,23 +0,0 @@ -Dart VM version: 0.5.0.1_r21823 (Mon Apr 22 14:02:11 2013), 535c1ae834b3bb23f004a712f36fdf8681d7d5db, Wed Apr 24 22:48:18 UTC 2013, 1178.0 -Dart VM version: 0.5.0.1_r21823 (Mon Apr 22 14:02:11 2013), 30cf32f6e403b60a8734b2d8780f39bcdee339e3, Thu Apr 25 17:40:55 UTC 2013, 1186.4 -Dart VM version: 0.5.1.0_r22072 (Fri Apr 26 12:54:33 2013), 32df58d26606334e00338c947c4f00fbdfc277ae, Mon Apr 29 21:16:52 UTC 2013, 883.4 -Dart VM version: 0.5.5.0_r22416 (Mon May 6 10:31:00 2013), c4c634e4d987c1461aa80e3d8627623ec04165f6, Tue May 7 21:58:28 UTC 2013, 663.5 -Dart VM version: 0.5.7.2_r22611 (Fri May 10 22:19:57 2013) on "linux_x64", 82ef833adcb9ff1af44ca3d02431cec36a605451, Mon May 13 23:00:06 UTC 2013, 2397.7 -Dart VM version: 0.5.9.0_r22879 (Sat May 18 21:32:26 2013) on "linux_x64", 18e8301013b444cc0a0ba89605dd784a6baf0546, Tue May 21 00:35:15 UTC 2013, 501.3 -Dart VM version: 0.5.11.1_r23200 (Mon May 27 00:02:29 2013) on "linux_x64", c08c2f50683e6ad8647ec2572bbb99a1d368a819, Wed Jun 5 06:18:28 UTC 2013, 581.8 -Dart VM version: 0.5.20.2_r24160 (Tue Jun 18 20:43:32 2013) on "linux_x64", 4004a85d1e6e9a9b16a39ea7d81b6cac7d80dc2f, Wed Jun 19 19:09:31 UTC 2013, 513.6 -Dart VM version: 0.6.3.3_r24898 (Thu Jul 11 07:47:12 2013) on "linux_x64", b1323f8a6bd330f89b48ca18e7821aab898a58c3, Tue Jul 16 00:01:25 UTC 2013, 522.8 -Dart VM version: 0.6.5.0_r25017 (Mon Jul 15 15:01:03 2013) on "linux_x64", 248ae0108cce466418185170214125b9ce0768bd, Wed Jul 17 22:35:09 UTC 2013, 528.6 -Dart VM version: 0.6.9.2_r25388 (Tue Jul 23 20:28:04 2013) on "linux_x64", 248ae0108cce466418185170214125b9ce0768bd, Fri Jul 26 20:37:00 UTC 2013, 506.0 -Dart VM version: 0.6.13.0_r25630 (Tue Jul 30 14:33:28 2013) on "linux_x64", 8c941a35b8168300626389780bacee56c6842552, Wed Jul 31 17:12:06 UTC 2013, 553.8 -Dart VM version: 0.6.15.3_r25822 (Tue Aug 6 13:39:55 2013) on "linux_x64", 196bed96bf2a209249aa945658e51567febe5006, Sun Aug 11 20:50:07 UTC 2013, 518.6 -Dart VM version: 0.7.3.1_r27487 (Fri Sep 13 13:27:39 2013) on "linux_x64", e1fbb8e11d30a9d7f7788d8846755aac296a0af4, Tue Sep 17 22:38:58 UTC 2013, 541.5 -Dart VM version: 0.7.5.3_r27776 (Mon Sep 23 14:31:35 2013) on "linux_x64", 800e9cc2bc700d9d87533772e8b537ae0fd42e46, Tue Sep 24 19:20:20 UTC 2013, 556.3 -Dart VM version: 0.7.6.4_r28108 (Tue Oct 1 14:32:31 2013) on "linux_x64", 5c80ab2f0f72d2607b41a2d08c80fe680449053b, Thu Oct 3 00:54:55 UTC 2013, 519.6 -Dart VM version: 0.8.5.1_r28990 (Tue Oct 22 04:50:58 2013) on "linux_x64", ffb7eee674f3be752956e8d99a9079ab4d3d8e3c, Tue Oct 22 19:41:31 UTC 2013, 559.7 -Dart VM version: 0.8.7.0_r29341 (Mon Oct 28 02:00:50 2013) on "linux_x64", 147768fc8239ad23d1074b5f4029d7dab0f2113d, Mon Oct 28 22:13:49 UTC 2013, 510.3 -Dart VM version: 0.8.10.8_r30039 (Thu Nov 7 03:03:33 2013) on "linux_x64", bd99a6c075b968fee23c541eee631e6396fbc262, Thu Nov 7 22:28:22 UTC 2013, 533.3 -Dart VM version: 1.1.0-dev.5.0 (Fri Dec 20 05:55:37 2013) on "linux_x64", 4cdf453c59369653d3e9432632ef4420e732fb36, Sat Jan 4 15:40:45 UTC 2014, 432.6 -Dart VM version: 1.2.0-dev.4.0 (Fri Feb 7 10:21:55 2014) on "linux_x64", 41c8cd12714847609f1647a4df5c20131cde759e, Tue Feb 11 22:37:52 UTC 2014, 463.3 -Dart VM version: 1.5.0-dev.2.0 (Mon May 26 07:29:28 2014) on "linux_x64", d3a3d1b4917de54dc1ddbe2f61169b35f21f85fb, Sun Jun 1 16:40:46 UTC 2014, 359.7 -Dart VM version: 1.5.0-dev.4.7 (Wed Jun 11 01:57:57 2014) on "linux_x64", dc6fe1c592a83e15d798a37f77013cafcb46c500, Thu Jun 12 09:00:32 UTC 2014, 345.6 diff --git a/benchmark/map_bench_wordcount.dart b/benchmark/map_bench_wordcount.dart deleted file mode 100644 index aaa5f33..0000000 --- a/benchmark/map_bench_wordcount.dart +++ /dev/null @@ -1,62 +0,0 @@ -import 'dart:async'; -import 'package:http/http.dart' as http; -import 'package:persistent/persistent.dart'; - -add(n, m) => n + m; -insertWord(map, word) => map.insert(word, 1, add); -count(words) => words.fold(new PersistentMap(), insertWord); - -insertWordT(map, word) => map.doInsert(word, 1, add); -countT(words) => words.fold(new PersistentMap().asTransient(), insertWordT); - -final AUSTEEN = 'http://www.gutenberg.org/files/1342/1342.txt'; -final DOYLE = 'http://www.gutenberg.org/files/1661/1661.txt'; - -Future> getWords(String url) { - return http.get(url).then((response) => response.body.split(' ')); -} - -void run(List doyle, List austeen) { - final map1 = count(austeen); - final map2 = count(doyle); - final result = map1.union(map2, add); - if (result.length != 36028) { - throw new StateError("something's wrong"); - } -} - -void runT(List doyle, List austeen) { - final map1 = countT(austeen); - final map2 = countT(doyle); - final result = map1.doUnion(map2, add); - if (result.length != 36028) { - throw new StateError("something's wrong"); - } -} - -void bench(List doyle, List austeen) { - // warmup - run(doyle, austeen); - - final watch = new Stopwatch(); - watch.start(); - for (int i = 0; i < 10; i++) { - run(doyle, austeen); - } - watch.stop(); - print('Persistent ${watch.elapsedMilliseconds / 10}'); - - watch.reset(); - watch.start(); - for (int i = 0; i < 10; i++) { - runT(doyle, austeen); - } - watch.stop(); - print('Transient ${watch.elapsedMilliseconds / 10}'); -} - -main() { - Future - .wait([getWords(AUSTEEN), getWords(DOYLE)]) - .then((result) => bench(result[0], result[1])); -} From fde04e200183db79a1b047b0824203d9cd6fca1b Mon Sep 17 00:00:00 2001 From: Sysel Date: Wed, 27 Aug 2014 16:43:01 +0200 Subject: [PATCH 021/214] Memory map benchmarks --- benchmark/map_memory/.gitignore | 3 ++ benchmark/map_memory/makefile | 17 +++++++++++ benchmark/map_memory/map_memory.dart | 45 ++++++++++++++++++++++++++++ benchmark/map_memory/map_memory.py | 43 ++++++++++++++++++++++++++ 4 files changed, 108 insertions(+) create mode 100644 benchmark/map_memory/.gitignore create mode 100644 benchmark/map_memory/makefile create mode 100644 benchmark/map_memory/map_memory.dart create mode 100644 benchmark/map_memory/map_memory.py diff --git a/benchmark/map_memory/.gitignore b/benchmark/map_memory/.gitignore new file mode 100644 index 0000000..4bc3d95 --- /dev/null +++ b/benchmark/map_memory/.gitignore @@ -0,0 +1,3 @@ +*.js +*.js.* +*.csv \ No newline at end of file diff --git a/benchmark/map_memory/makefile b/benchmark/map_memory/makefile new file mode 100644 index 0000000..f1932da --- /dev/null +++ b/benchmark/map_memory/makefile @@ -0,0 +1,17 @@ +all: map_memory.js + +map_memory.js: map_memory.dart + dart2js -o map_memory.js map_memory.dart + echo "function dartMainRunner(main, args){" >> map_memory.js + echo " main(process.argv.slice(2));" >> map_memory.js + echo "}" >> map_memory.js + +clean: + rm -f map_memory.js map_memory.js.* map_memory.precompiled.js + +run: + python2 map_memory.py > results.csv.part + mv results.csv.part results.csv + +view: + column -t -s, < results.csv | less \ No newline at end of file diff --git a/benchmark/map_memory/map_memory.dart b/benchmark/map_memory/map_memory.dart new file mode 100644 index 0000000..bf974e9 --- /dev/null +++ b/benchmark/map_memory/map_memory.dart @@ -0,0 +1,45 @@ +library map_memory; + +import 'package:persistent/persistent.dart'; +import 'dart:convert'; + +Map template = {}; +List data = new List.filled(100000, null); + +var creators = { + + "persistent": () => new PersistentMap.fromMap(template), + + "transient": (){ + var res = new TransientMap(); + template.forEach((k, v) => res.doInsert(k, v)); + return res; + }, + + "json": () => JSON.encode(template), +}; + +void run(int template_size, String mode) { + + for (int i = 0; i < template_size; i++) { + template["$i".padLeft(8)] = "$i".padRight(8); + } + + int allocated = 0; + for(bool go = true; go; allocated++){ + try{ + go = false; + var a = creators[mode](); + data[allocated] = a; + go = true; + print(allocated); + } catch(e) { + data = null; + } + } +} + +main(List args){ + + run(int.parse(args[0]), args[1]); +} diff --git a/benchmark/map_memory/map_memory.py b/benchmark/map_memory/map_memory.py new file mode 100644 index 0000000..2cb13dc --- /dev/null +++ b/benchmark/map_memory/map_memory.py @@ -0,0 +1,43 @@ +import numpy +from subprocess import Popen, PIPE +from sys import stderr, stdout +import os + +os.chdir(os.path.dirname(os.path.realpath(__file__))) + +COMMANDS = { + "DartVM": lambda (limit): + ["dart", "--old_gen_heap_size=%d" % limit,"map_memory.dart"], + "NodeJS": lambda (limit): + ["node", "--max-old-space-size=%d" % limit,"map_memory.js"], +} + +def run_inst(mode, name, size, limit): + stderr.write("%s, %s, size: %d, limit %d\n" % (mode, name, size, limit)) + process = Popen( + COMMANDS[mode](limit) + + [str(size), name], + stdin=PIPE, stdout=PIPE, stderr=PIPE + ) + return int(process.communicate()[0].split()[-1]) + + +def run_test(mode, name, size): + limits = list(range(50, 350, 50)) + allocations = [] + + for limit in limits: + allocations.append(run_inst(mode, name, size, limit)) + + return (numpy.polyfit(allocations, limits, 1)[0])*(10**6) + + +stdout.write("mode,size,persistent,transient,json,\n") +for mode in ["DartVM", "NodeJS"]: + for size in [100, 500, 1500, 5000, 20000]: + stdout.write("%s,%d," % (mode, size)) + for name in ["persistent","transient","json"]: + res = run_test(mode, name, size) + stdout.write("%f," % (res)); + stdout.write("\n") + stdout.flush() \ No newline at end of file From 20146216f88ce6982e68e3c988c189e5011febf0 Mon Sep 17 00:00:00 2001 From: Sysel Date: Thu, 28 Aug 2014 14:51:29 +0200 Subject: [PATCH 022/214] map memory benchmark - Added PureJS and standart map --- benchmark/map_memory/.gitignore | 3 +- benchmark/map_memory/makefile | 8 +++-- benchmark/map_memory/map_memory.dart | 6 ++-- benchmark/map_memory/map_memory.pure.js | 42 +++++++++++++++++++++++++ benchmark/map_memory/map_memory.py | 14 ++++++--- benchmark/map_memory/postproces.awk | 3 ++ benchmark/map_memory/results.csv.part | 2 ++ 7 files changed, 68 insertions(+), 10 deletions(-) create mode 100644 benchmark/map_memory/map_memory.pure.js create mode 100644 benchmark/map_memory/postproces.awk create mode 100644 benchmark/map_memory/results.csv.part diff --git a/benchmark/map_memory/.gitignore b/benchmark/map_memory/.gitignore index 4bc3d95..e08687f 100644 --- a/benchmark/map_memory/.gitignore +++ b/benchmark/map_memory/.gitignore @@ -1,3 +1,4 @@ *.js *.js.* -*.csv \ No newline at end of file +*.csv +!map_memory.pure.js \ No newline at end of file diff --git a/benchmark/map_memory/makefile b/benchmark/map_memory/makefile index f1932da..7ab6610 100644 --- a/benchmark/map_memory/makefile +++ b/benchmark/map_memory/makefile @@ -1,4 +1,6 @@ -all: map_memory.js +all: run clean view + +compile: map_memory.js map_memory.js: map_memory.dart dart2js -o map_memory.js map_memory.dart @@ -9,9 +11,9 @@ map_memory.js: map_memory.dart clean: rm -f map_memory.js map_memory.js.* map_memory.precompiled.js -run: +run: map_memory.js python2 map_memory.py > results.csv.part mv results.csv.part results.csv view: - column -t -s, < results.csv | less \ No newline at end of file + awk -f postproces.awk results.csv | column -s, -t | less \ No newline at end of file diff --git a/benchmark/map_memory/map_memory.dart b/benchmark/map_memory/map_memory.dart index bf974e9..75fadfc 100644 --- a/benchmark/map_memory/map_memory.dart +++ b/benchmark/map_memory/map_memory.dart @@ -16,7 +16,9 @@ var creators = { return res; }, - "json": () => JSON.encode(template), + "json": () => JSON.encode(template), + + "map": () => new Map.from(template), }; void run(int template_size, String mode) { @@ -28,11 +30,11 @@ void run(int template_size, String mode) { int allocated = 0; for(bool go = true; go; allocated++){ try{ + print(allocated); go = false; var a = creators[mode](); data[allocated] = a; go = true; - print(allocated); } catch(e) { data = null; } diff --git a/benchmark/map_memory/map_memory.pure.js b/benchmark/map_memory/map_memory.pure.js new file mode 100644 index 0000000..80624bb --- /dev/null +++ b/benchmark/map_memory/map_memory.pure.js @@ -0,0 +1,42 @@ +var template = {}; +var data = Array(100000); + +var creators = { + + "persistent": function () { throw "NO WAY !"}, + + "transient": function (){ throw "NO WAY !" }, + + "json": function (){ return JSON.stringify(template) }, + + "map": function (){ return JSON.parse(JSON.stringify(template)) }, +}; + +function run(template_size, mode) { + + for (var i = 0; i < template_size; i++) { + template[(" " + i).slice(-8)] = (" " + i).slice(-8); + } + + var allocated = 0; + for(var go = true; go; allocated++){ + try{ + console.log(allocated); + go = false; + var a = creators[mode](); + data[allocated] = a; + go = true; + + } catch(e) { + data = null; + console.error(e) + } + } +} + +function main(){ + + run(parseInt(process.argv[2]), process.argv[3]); +} + +main() \ No newline at end of file diff --git a/benchmark/map_memory/map_memory.py b/benchmark/map_memory/map_memory.py index 2cb13dc..7eb693d 100644 --- a/benchmark/map_memory/map_memory.py +++ b/benchmark/map_memory/map_memory.py @@ -10,8 +10,11 @@ ["dart", "--old_gen_heap_size=%d" % limit,"map_memory.dart"], "NodeJS": lambda (limit): ["node", "--max-old-space-size=%d" % limit,"map_memory.js"], + "PureJS": lambda (limit): + ["node", "--max-old-space-size=%d" % limit,"map_memory.pure.js"], } + def run_inst(mode, name, size, limit): stderr.write("%s, %s, size: %d, limit %d\n" % (mode, name, size, limit)) process = Popen( @@ -23,20 +26,23 @@ def run_inst(mode, name, size, limit): def run_test(mode, name, size): - limits = list(range(50, 350, 50)) + limits = list(range(100, 251, 30)) allocations = [] for limit in limits: allocations.append(run_inst(mode, name, size, limit)) + if(allocations[0] == 0): + return float("inf") + return (numpy.polyfit(allocations, limits, 1)[0])*(10**6) -stdout.write("mode,size,persistent,transient,json,\n") -for mode in ["DartVM", "NodeJS"]: +stdout.write("mode,size,persistent,transient,json,map,\n") +for mode in ["NodeJS", "PureJS", "DartVM"]: for size in [100, 500, 1500, 5000, 20000]: stdout.write("%s,%d," % (mode, size)) - for name in ["persistent","transient","json"]: + for name in ["persistent","transient", "json", "map",]: res = run_test(mode, name, size) stdout.write("%f," % (res)); stdout.write("\n") diff --git a/benchmark/map_memory/postproces.awk b/benchmark/map_memory/postproces.awk new file mode 100644 index 0000000..9cb60bc --- /dev/null +++ b/benchmark/map_memory/postproces.awk @@ -0,0 +1,3 @@ +BEGIN { OFS = ","; FS = ","; } +NR==1 {print $0} +NR>1 {print $1,$2,$3/$2/16,$4/$2/16,$5/$2/16,$6/$2/16,$7/$2/16,""} \ No newline at end of file diff --git a/benchmark/map_memory/results.csv.part b/benchmark/map_memory/results.csv.part new file mode 100644 index 0000000..9da5204 --- /dev/null +++ b/benchmark/map_memory/results.csv.part @@ -0,0 +1,2 @@ +mode,size,persistent,transient,json,map, +NodeJS,100, \ No newline at end of file From 3d0dbebd9c5b005755a977b9c7c26c1b2e65cb5c Mon Sep 17 00:00:00 2001 From: Roman Hudec Date: Thu, 28 Aug 2014 14:55:09 +0200 Subject: [PATCH 023/214] split API for transient and persistent vectors --- lib/src/vector.dart | 215 +++++++++++++++++++++++++----------------- test/vector_test.dart | 6 +- 2 files changed, 133 insertions(+), 88 deletions(-) diff --git a/lib/src/vector.dart b/lib/src/vector.dart index e2daaab..91d3e7f 100644 --- a/lib/src/vector.dart +++ b/lib/src/vector.dart @@ -18,24 +18,36 @@ class Bool { class Owner {} -abstract class PersistentVectorInterface { - E get(int index, [E notSetValue = null]); - PersistentVectorInterface set(int index, E value); +abstract class FirstLastInterface { E get first; E get last; +} + +abstract class PersistentVectorInterface extends FirstLastInterface { + E get(int index, [E notSetValue = null]); + PersistentVectorInterface set(int index, E value); - PersistentVectorInterface delete(int index); - String toString(); PersistentVectorInterface push(E value); PersistentVectorInterface pop(); PersistentVectorInterface from(Iterable values); + TransientVectorInterface asImmutable(); +} + +abstract class TransientVectorInterface extends FirstLastInterface { + E doGet(int index, [E notSetValue = null]); + TransientVectorInterface doSet(int index, E value); + TransientVectorInterface doPush(E value); + TransientVectorInterface doPop(); + PersistentVectorInterface asMutable(); } -abstract class PersistentVectorBase extends IterableBase implements PersistentVectorInterface { +abstract class PersistentVectorBase extends IterableBase { int _size; - E get first => get(0); - E get last => get(this.length ? this.length - 1 : 0); + E _get(int index, [E notSetValue = null]); + + E get first => _get(0); + E get last => _get(this.length ? this.length - 1 : 0); int get length => _size; Iterator get iterator => new PersistentVectorIterator(this); } @@ -55,10 +67,10 @@ class PersistentVectorIterator extends Iterator { return true; } - E get current => _parentVector.get(_position); + E get current => _parentVector._get(_position); } -class PersistentVector extends PersistentVectorBase { +abstract class PersistentVectorImpl extends PersistentVectorBase { int _origin; Owner __ownerID; VNode _root; @@ -68,23 +80,7 @@ class PersistentVector extends PersistentVectorBase { int _hashCode = null; bool __altered = false; - factory PersistentVector.from(Iterable values) { - if (values.length == 0) { - return new PersistentVector.empty(); - } - PersistentVector result = new PersistentVector.empty(); - values.forEach((E value) { - result = result.push(value); - }); - return result; - } - - factory PersistentVector.empty() { - var x = new PersistentVector._prototype(); - return x; - } - - PersistentVector._prototype() { + PersistentVectorImpl._prototype() { this._origin = 0; this.__ownerID = null; this._root = new VNode([], __ownerID); @@ -93,18 +89,7 @@ class PersistentVector extends PersistentVectorBase { this._size = 0; } - factory PersistentVector._make(int origin, int size, int level, VNode root, VNode tail, [Owner ownerID = null, int hashCode = null]) { - var x = new PersistentVector._prototype(); - x._origin = origin; - x._size = size; - x._level = level; - x._root = root; - x._tail = tail; - x.__ownerID = ownerID; - return x; - } - - E get(int index, [E notSetValue = null]) { + E _get(int index, [E notSetValue = null]) { index = _checkIndex(index); if (index >= this._size) { return notSetValue; @@ -115,11 +100,11 @@ class PersistentVector extends PersistentVectorBase { node._array[maskedIndex] : notSetValue; } - PersistentVector set(int index, E value) { + PersistentVectorImpl _set(int index, E value) { if (index >= this.length) { if (value == getNotSet()) return this; - return this.withMutations((vect) => vect._resize(index+1).set(index,value)); + return this._withMutations((vect) => vect._resize(index+1)._set(index,value)); } var vector = this; @@ -143,12 +128,12 @@ class PersistentVector extends PersistentVectorBase { return new PersistentVector._make(vector._origin, vector._size, vector._level, newRoot, newTail); } - PersistentVector push(E value) { + PersistentVectorImpl _push(E value) { var len = this.length; - return this.withMutations((vect) => vect._resize(len+1).set(len, value)); + return this._withMutations((vect) => vect._resize(len+1)._set(len, value)); } - PersistentVector pushAll(List values) { + PersistentVectorImpl _pushAll(List values) { var t = this._resize(this.length + values.length); for (int i = 0; i < values.length; i++) { t = t.set(this.length+i, values[i]); @@ -156,7 +141,7 @@ class PersistentVector extends PersistentVectorBase { return t; } - PersistentVector pop() { + PersistentVectorImpl _pop() { return this._resize(this.length-1); } @@ -186,7 +171,7 @@ class PersistentVector extends PersistentVectorBase { } } - PersistentVector _resize(int end) { + PersistentVectorImpl _resize(int end) { var owner; if (__ownerID == null) { owner = __ownerID; @@ -237,7 +222,7 @@ class PersistentVector extends PersistentVectorBase { __altered = true; return this; } - return new PersistentVector._make(0, newSize, newLevel, newRoot, newTail, null); + return new PersistentVector._make(0, newSize, newLevel, newRoot, newTail); } @@ -250,69 +235,39 @@ class PersistentVector extends PersistentVectorBase { print("Tail: $_tail"); } - int get hashCode { - if (this._hashCode == null) { - int result = 17; - for (E value in this) { - result = 37 * result + value.hashCode % 1000000009; - } - this._hashCode = result; - } - return this._hashCode; - } - bool operator==(other) { if (other is! PersistentVector) return false; PersistentVector otherVector = other; if (this.hashCode != otherVector.hashCode) return false; if (this.length != otherVector.length) return false; for (int i = 0; i < this.length; i++) { - if (this.get(i) != otherVector.get(i)) return false; + if (this._get(i) != otherVector.get(i)) return false; } return true; } - PersistentVector clear() { - if (this.length == 0) { - return this; - } - if (this.__ownerID != null) { - this._size = 0; - this._origin = 0; - this._level = _SHIFT; - this._root = this._tail = null; - this._hashCode = null; - this.__altered = true; - return this; - } - return new PersistentVector.empty(); - } - - PersistentVector _ensureOwner(Owner ownerID) { + PersistentVectorImpl _ensureOwner(Owner ownerID) { if (ownerID == this.__ownerID) { return this; } if (ownerID == null) { this.__ownerID = ownerID; + return new PersistentVector._make(this._origin, this._size, this._level, this._root, this._tail); return this; } - return new PersistentVector._make(this._origin, this._size, this._level, this._root, this._tail, ownerID, this._hashCode); + return new TransientVector._make(this._origin, this._size, this._level, this._root, this._tail, ownerID); } - PersistentVector asMutable() { + TransientVector _asMutable() { return this.__ownerID != null ? this : this._ensureOwner(new Owner()); } - PersistentVector asImmutable() { + PersistentVector _asImmutable() { return this._ensureOwner(null); } - bool wasAltered() { - return this.__altered; - } - - PersistentVector withMutations(fn) { - var mutable = this.asMutable(); + PersistentVector _withMutations(fn) { + var mutable = this._asMutable(); fn(mutable); return mutable.wasAltered() ? mutable._ensureOwner(this.__ownerID) : this; } @@ -437,3 +392,93 @@ VNode _mutableVNode(VNode node, Owner ownerID) { } return new VNode(node != null ? node._array.sublist(0) : [], ownerID); } + +class PersistentVector extends PersistentVectorImpl implements PersistentVectorInterface { + factory PersistentVector.from(Iterable values) { + if (values.length == 0) { + return new PersistentVector.empty(); + } + PersistentVector result = new PersistentVector.empty(); + result = result.withMutations((vector) { + values.forEach((E value) { + vector = vector.doPush(value); + }); + return vector; + }); + return result; + } + + factory PersistentVector.empty() => new PersistentVector._prototype(); + PersistentVector._prototype() : super._prototype(); + + factory PersistentVector._make(int origin, int size, int level, VNode root, VNode tail) { + var x = new PersistentVector._prototype(); + x._origin = origin; + x._size = size; + x._level = level; + x._root = root; + x._tail = tail; + x.__ownerID = null; + return x; + } + + int get hashCode { + if (this._hashCode == null) { + int result = 17; + for (E value in this) { + result = 37 * result + value.hashCode % 1000000009; + } + this._hashCode = result; + } + return this._hashCode; + } + + PersistentVector _clear() { + if (this.length == 0) { + return this; + } + return new PersistentVector.empty(); + } + + TransientVector asMutable() => _asMutable(); + PersistentVector withMutations(fn) => _withMutations(fn); + PersistentVector push(E value) => _push(value); + PersistentVector pop() => _pop(); + PersistentVector set(int index, E value) => _set(index, value); + PersistentVector get(int index) => _get(index); +} + +class TransientVector extends PersistentVectorImpl implements TransientVectorInterface { + TransientVector._prototype() : super._prototype(); + + factory TransientVector._make(int origin, int size, int level, VNode root, VNode tail, Owner ownerID) { + var x = new TransientVector._prototype(); + x._origin = origin; + x._size = size; + x._level = level; + x._root = root; + x._tail = tail; + x.__ownerID = ownerID; + return x; + } + + bool wasAltered() { + return this.__altered; + } + + TransientVector _clear() { + this._size = 0; + this._origin = 0; + this._level = _SHIFT; + this._root = this._tail = null; + this._hashCode = null; + this.__altered = true; + return this; + } + + PersistentVector asImmutable() => _asImmutable(); + TransientVector doPush(E value) => _push(value); + TransientVector doPop() => _pop(); + TransientVector doGet(int index) => _get(index); + TransientVector doSet(int index, E value) => _set(index, value); +} \ No newline at end of file diff --git a/test/vector_test.dart b/test/vector_test.dart index 507bbb5..7f95789 100644 --- a/test/vector_test.dart +++ b/test/vector_test.dart @@ -85,6 +85,6 @@ callInsert(PersistentVectorInterface vector, prep) => vector.push(prep); callSet(PersistentVectorInterface vector, prep) => vector.set(prep['key'], prep['value']); callDelete(PersistentVectorInterface vector, prep) => prep ? vector.pop() : vector; -mutInsert(PersistentVector vector, prep) => vector.withMutations((vect) => vect.push(prep)); -mutSet(PersistentVector vector, prep) => vector.withMutations((vect) => vect.set(prep['key'], prep['value'])); -mutDelete(PersistentVector vector, prep) => prep ? vector.withMutations((vect) => vect.pop()) : vector; \ No newline at end of file +mutInsert(PersistentVector vector, prep) => vector.withMutations((vect) => vect.doPush(prep)); +mutSet(PersistentVector vector, prep) => vector.withMutations((vect) => vect.doSet(prep['key'], prep['value'])); +mutDelete(PersistentVector vector, prep) => prep ? vector.withMutations((vect) => vect.doPop()) : vector; \ No newline at end of file From 1d531c94ee07686cebc723fcf9efcf8cacad3946 Mon Sep 17 00:00:00 2001 From: Roman Hudec Date: Thu, 28 Aug 2014 16:00:18 +0200 Subject: [PATCH 024/214] fix warnings --- lib/src/vector.dart | 19 +++++++++---------- test/src/vector_model.dart | 2 +- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/lib/src/vector.dart b/lib/src/vector.dart index 91d3e7f..8ccd4f2 100644 --- a/lib/src/vector.dart +++ b/lib/src/vector.dart @@ -29,8 +29,7 @@ abstract class PersistentVectorInterface extends FirstLastInterface { PersistentVectorInterface push(E value); PersistentVectorInterface pop(); - PersistentVectorInterface from(Iterable values); - TransientVectorInterface asImmutable(); + TransientVectorInterface asMutable(); } abstract class TransientVectorInterface extends FirstLastInterface { @@ -38,7 +37,7 @@ abstract class TransientVectorInterface extends FirstLastInterface { TransientVectorInterface doSet(int index, E value); TransientVectorInterface doPush(E value); TransientVectorInterface doPop(); - PersistentVectorInterface asMutable(); + PersistentVectorInterface asImmutable(); } abstract class PersistentVectorBase extends IterableBase { @@ -47,7 +46,7 @@ abstract class PersistentVectorBase extends IterableBase { E _get(int index, [E notSetValue = null]); E get first => _get(0); - E get last => _get(this.length ? this.length - 1 : 0); + E get last => _get(this.length > 0 ? this.length - 1 : 0); int get length => _size; Iterator get iterator => new PersistentVectorIterator(this); } @@ -169,6 +168,7 @@ abstract class PersistentVectorImpl extends PersistentVectorBase { } return node; } + return null; } PersistentVectorImpl _resize(int end) { @@ -253,7 +253,6 @@ abstract class PersistentVectorImpl extends PersistentVectorBase { if (ownerID == null) { this.__ownerID = ownerID; return new PersistentVector._make(this._origin, this._size, this._level, this._root, this._tail); - return this; } return new TransientVector._make(this._origin, this._size, this._level, this._root, this._tail, ownerID); } @@ -276,7 +275,7 @@ abstract class PersistentVectorImpl extends PersistentVectorBase { class VNode { List _array; - int _ownerID; + Owner _ownerID; int get length => _array.length; @@ -298,7 +297,7 @@ class VNode { } VNode _removeAfter(Owner ownerID, int level, int index) { - if (index == level ? 1 << level : 0 || this.length == 0) { + if ((index == (level > 0 ? 1 << level : 0 ))|| this.length == 0) { return this; } var sizeIndex = ((index - 1) >> level) & _MASK; @@ -311,7 +310,7 @@ class VNode { var oldChild = this._array[sizeIndex]; if (oldChild == null) newChild = null; else { - newChild = oldChild.removeAfter(ownerID, level - SHIFT, index); + newChild = oldChild.removeAfter(ownerID, level - _SHIFT, index); } if (newChild == oldChild && removingLast) { return this; @@ -445,7 +444,7 @@ class PersistentVector extends PersistentVectorImpl implements PersistentV PersistentVector push(E value) => _push(value); PersistentVector pop() => _pop(); PersistentVector set(int index, E value) => _set(index, value); - PersistentVector get(int index) => _get(index); + E get(int index, [E notSetValue]) => _get(index); } class TransientVector extends PersistentVectorImpl implements TransientVectorInterface { @@ -479,6 +478,6 @@ class TransientVector extends PersistentVectorImpl implements TransientVec PersistentVector asImmutable() => _asImmutable(); TransientVector doPush(E value) => _push(value); TransientVector doPop() => _pop(); - TransientVector doGet(int index) => _get(index); + E doGet(int index, [E notSetValue]) => _get(index); TransientVector doSet(int index, E value) => _set(index, value); } \ No newline at end of file diff --git a/test/src/vector_model.dart b/test/src/vector_model.dart index bbb0bf3..04478ac 100644 --- a/test/src/vector_model.dart +++ b/test/src/vector_model.dart @@ -1,6 +1,6 @@ part of test_util; -class ModelVector extends IterableBase implements PersistentVectorInterface { +class ModelVector extends IterableBase implements PersistentVectorInterface { final List _vector; int get length => _vector.length; From 05af3fa63c3075da0713fd8e32e6eaf080dbe7bb Mon Sep 17 00:00:00 2001 From: Roman Hudec Date: Thu, 28 Aug 2014 16:22:37 +0200 Subject: [PATCH 025/214] rename interfaces --- lib/src/vector.dart | 125 +++++++++++++++++++------------------ test/src/vector_model.dart | 2 +- test/vector_test.dart | 10 +-- 3 files changed, 71 insertions(+), 66 deletions(-) diff --git a/lib/src/vector.dart b/lib/src/vector.dart index 8ccd4f2..09d0a04 100644 --- a/lib/src/vector.dart +++ b/lib/src/vector.dart @@ -18,26 +18,31 @@ class Bool { class Owner {} -abstract class FirstLastInterface { +abstract class PersistentVector implements Iterable { + E get(int index, [E notSetValue = null]); + PersistentVector set(int index, E value); + + PersistentVector push(E value); + PersistentVector pop(); + TransientVector asMutable(); + E get first; E get last; + + factory PersistentVector() => new PersistentVectorImpl.empty(); + factory PersistentVector.from(Iterable values) => new PersistentVectorImpl.from(values); + PersistentVector withMutations(TransientVector fn(TransientVector vect)); } -abstract class PersistentVectorInterface extends FirstLastInterface { +abstract class TransientVector implements Iterable { E get(int index, [E notSetValue = null]); - PersistentVectorInterface set(int index, E value); + TransientVector doSet(int index, E value); + TransientVector doPush(E value); + TransientVector doPop(); + PersistentVector asImmutable(); - PersistentVectorInterface push(E value); - PersistentVectorInterface pop(); - TransientVectorInterface asMutable(); -} - -abstract class TransientVectorInterface extends FirstLastInterface { - E doGet(int index, [E notSetValue = null]); - TransientVectorInterface doSet(int index, E value); - TransientVectorInterface doPush(E value); - TransientVectorInterface doPop(); - PersistentVectorInterface asImmutable(); + E get first; + E get last; } abstract class PersistentVectorBase extends IterableBase { @@ -48,15 +53,15 @@ abstract class PersistentVectorBase extends IterableBase { E get first => _get(0); E get last => _get(this.length > 0 ? this.length - 1 : 0); int get length => _size; - Iterator get iterator => new PersistentVectorIterator(this); + Iterator get iterator => new VectorIterator(this); } -class PersistentVectorIterator extends Iterator { +class VectorIterator extends Iterator { PersistentVectorBase _parentVector; int _position = -1; int _length; - PersistentVectorIterator(this._parentVector) { + VectorIterator(this._parentVector) { _length = _parentVector.length; } @@ -69,7 +74,7 @@ class PersistentVectorIterator extends Iterator { E get current => _parentVector._get(_position); } -abstract class PersistentVectorImpl extends PersistentVectorBase { +abstract class BaseVectorImpl extends PersistentVectorBase { int _origin; Owner __ownerID; VNode _root; @@ -79,7 +84,7 @@ abstract class PersistentVectorImpl extends PersistentVectorBase { int _hashCode = null; bool __altered = false; - PersistentVectorImpl._prototype() { + BaseVectorImpl._prototype() { this._origin = 0; this.__ownerID = null; this._root = new VNode([], __ownerID); @@ -99,7 +104,7 @@ abstract class PersistentVectorImpl extends PersistentVectorBase { node._array[maskedIndex] : notSetValue; } - PersistentVectorImpl _set(int index, E value) { + BaseVectorImpl _set(int index, E value) { if (index >= this.length) { if (value == getNotSet()) return this; @@ -124,15 +129,15 @@ abstract class PersistentVectorImpl extends PersistentVectorBase { vector.__altered = true; return vector; } - return new PersistentVector._make(vector._origin, vector._size, vector._level, newRoot, newTail); + return new PersistentVectorImpl._make(vector._origin, vector._size, vector._level, newRoot, newTail); } - PersistentVectorImpl _push(E value) { + BaseVectorImpl _push(E value) { var len = this.length; return this._withMutations((vect) => vect._resize(len+1)._set(len, value)); } - PersistentVectorImpl _pushAll(List values) { + BaseVectorImpl _pushAll(List values) { var t = this._resize(this.length + values.length); for (int i = 0; i < values.length; i++) { t = t.set(this.length+i, values[i]); @@ -140,7 +145,7 @@ abstract class PersistentVectorImpl extends PersistentVectorBase { return t; } - PersistentVectorImpl _pop() { + BaseVectorImpl _pop() { return this._resize(this.length-1); } @@ -171,7 +176,7 @@ abstract class PersistentVectorImpl extends PersistentVectorBase { return null; } - PersistentVectorImpl _resize(int end) { + BaseVectorImpl _resize(int end) { var owner; if (__ownerID == null) { owner = __ownerID; @@ -222,7 +227,7 @@ abstract class PersistentVectorImpl extends PersistentVectorBase { __altered = true; return this; } - return new PersistentVector._make(0, newSize, newLevel, newRoot, newTail); + return new PersistentVectorImpl._make(0, newSize, newLevel, newRoot, newTail); } @@ -236,8 +241,8 @@ abstract class PersistentVectorImpl extends PersistentVectorBase { } bool operator==(other) { - if (other is! PersistentVector) return false; - PersistentVector otherVector = other; + if (other is! PersistentVectorImpl) return false; + PersistentVectorImpl otherVector = other; if (this.hashCode != otherVector.hashCode) return false; if (this.length != otherVector.length) return false; for (int i = 0; i < this.length; i++) { @@ -246,26 +251,26 @@ abstract class PersistentVectorImpl extends PersistentVectorBase { return true; } - PersistentVectorImpl _ensureOwner(Owner ownerID) { + BaseVectorImpl _ensureOwner(Owner ownerID) { if (ownerID == this.__ownerID) { return this; } if (ownerID == null) { this.__ownerID = ownerID; - return new PersistentVector._make(this._origin, this._size, this._level, this._root, this._tail); + return new PersistentVectorImpl._make(this._origin, this._size, this._level, this._root, this._tail); } - return new TransientVector._make(this._origin, this._size, this._level, this._root, this._tail, ownerID); + return new TransientVectorImpl._make(this._origin, this._size, this._level, this._root, this._tail, ownerID); } - TransientVector _asMutable() { + TransientVectorImpl _asMutable() { return this.__ownerID != null ? this : this._ensureOwner(new Owner()); } - PersistentVector _asImmutable() { + PersistentVectorImpl _asImmutable() { return this._ensureOwner(null); } - PersistentVector _withMutations(fn) { + BaseVectorImpl _withMutations(fn) { var mutable = this._asMutable(); fn(mutable); return mutable.wasAltered() ? mutable._ensureOwner(this.__ownerID) : this; @@ -331,7 +336,7 @@ class VNode { } _ensureOwner(ownerID) { - if (ownerID && ownerID == _ownerID) + if (ownerID != null && ownerID == _ownerID) return this; return new VNode(_array.sublist(0), ownerID); } @@ -392,12 +397,12 @@ VNode _mutableVNode(VNode node, Owner ownerID) { return new VNode(node != null ? node._array.sublist(0) : [], ownerID); } -class PersistentVector extends PersistentVectorImpl implements PersistentVectorInterface { - factory PersistentVector.from(Iterable values) { +class PersistentVectorImpl extends BaseVectorImpl implements PersistentVector { + factory PersistentVectorImpl.from(Iterable values) { if (values.length == 0) { - return new PersistentVector.empty(); + return new PersistentVectorImpl.empty(); } - PersistentVector result = new PersistentVector.empty(); + PersistentVectorImpl result = new PersistentVectorImpl.empty(); result = result.withMutations((vector) { values.forEach((E value) { vector = vector.doPush(value); @@ -407,11 +412,11 @@ class PersistentVector extends PersistentVectorImpl implements PersistentV return result; } - factory PersistentVector.empty() => new PersistentVector._prototype(); - PersistentVector._prototype() : super._prototype(); + factory PersistentVectorImpl.empty() => new PersistentVectorImpl._prototype(); + PersistentVectorImpl._prototype() : super._prototype(); - factory PersistentVector._make(int origin, int size, int level, VNode root, VNode tail) { - var x = new PersistentVector._prototype(); + factory PersistentVectorImpl._make(int origin, int size, int level, VNode root, VNode tail) { + var x = new PersistentVectorImpl._prototype(); x._origin = origin; x._size = size; x._level = level; @@ -432,26 +437,26 @@ class PersistentVector extends PersistentVectorImpl implements PersistentV return this._hashCode; } - PersistentVector _clear() { + PersistentVectorImpl _clear() { if (this.length == 0) { return this; } - return new PersistentVector.empty(); + return new PersistentVectorImpl.empty(); } - TransientVector asMutable() => _asMutable(); - PersistentVector withMutations(fn) => _withMutations(fn); - PersistentVector push(E value) => _push(value); - PersistentVector pop() => _pop(); - PersistentVector set(int index, E value) => _set(index, value); + TransientVectorImpl asMutable() => _asMutable(); + PersistentVectorImpl withMutations(fn) => _withMutations(fn); + PersistentVectorImpl push(E value) => _push(value); + PersistentVectorImpl pop() => _pop(); + PersistentVectorImpl set(int index, E value) => _set(index, value); E get(int index, [E notSetValue]) => _get(index); } -class TransientVector extends PersistentVectorImpl implements TransientVectorInterface { - TransientVector._prototype() : super._prototype(); +class TransientVectorImpl extends BaseVectorImpl implements TransientVector { + TransientVectorImpl._prototype() : super._prototype(); - factory TransientVector._make(int origin, int size, int level, VNode root, VNode tail, Owner ownerID) { - var x = new TransientVector._prototype(); + factory TransientVectorImpl._make(int origin, int size, int level, VNode root, VNode tail, Owner ownerID) { + var x = new TransientVectorImpl._prototype(); x._origin = origin; x._size = size; x._level = level; @@ -465,7 +470,7 @@ class TransientVector extends PersistentVectorImpl implements TransientVec return this.__altered; } - TransientVector _clear() { + TransientVectorImpl _clear() { this._size = 0; this._origin = 0; this._level = _SHIFT; @@ -475,9 +480,9 @@ class TransientVector extends PersistentVectorImpl implements TransientVec return this; } - PersistentVector asImmutable() => _asImmutable(); - TransientVector doPush(E value) => _push(value); - TransientVector doPop() => _pop(); - E doGet(int index, [E notSetValue]) => _get(index); - TransientVector doSet(int index, E value) => _set(index, value); + PersistentVectorImpl asImmutable() => _asImmutable(); + TransientVectorImpl doPush(E value) => _push(value); + TransientVectorImpl doPop() => _pop(); + E get(int index, [E notSetValue]) => _get(index); + TransientVectorImpl doSet(int index, E value) => _set(index, value); } \ No newline at end of file diff --git a/test/src/vector_model.dart b/test/src/vector_model.dart index 04478ac..08153f7 100644 --- a/test/src/vector_model.dart +++ b/test/src/vector_model.dart @@ -1,6 +1,6 @@ part of test_util; -class ModelVector extends IterableBase implements PersistentVectorInterface { +class ModelVector extends IterableBase implements PersistentVector { final List _vector; int get length => _vector.length; diff --git a/test/vector_test.dart b/test/vector_test.dart index 7f95789..4fc8711 100644 --- a/test/vector_test.dart +++ b/test/vector_test.dart @@ -12,8 +12,8 @@ main() { test('random_test', () { Random r = new Random(47); int next = 1; - PersistentVector mut = new PersistentVector.empty(); - PersistentVector vector = new PersistentVector.empty(); + PersistentVector mut = new PersistentVector(); + PersistentVector vector = new PersistentVector(); ModelVector model = new ModelVector.empty(); List actions = []; @@ -81,9 +81,9 @@ prepDelete(Random r, PersistentVector vector) { return vector.length != 0; } -callInsert(PersistentVectorInterface vector, prep) => vector.push(prep); -callSet(PersistentVectorInterface vector, prep) => vector.set(prep['key'], prep['value']); -callDelete(PersistentVectorInterface vector, prep) => prep ? vector.pop() : vector; +callInsert(PersistentVector vector, prep) => vector.push(prep); +callSet(PersistentVector vector, prep) => vector.set(prep['key'], prep['value']); +callDelete(PersistentVector vector, prep) => prep ? vector.pop() : vector; mutInsert(PersistentVector vector, prep) => vector.withMutations((vect) => vect.doPush(prep)); mutSet(PersistentVector vector, prep) => vector.withMutations((vect) => vect.doSet(prep['key'], prep['value'])); From 7c893e3f5fa4fd949c485c27c52d2f4d05821f6f Mon Sep 17 00:00:00 2001 From: Roman Hudec Date: Thu, 28 Aug 2014 18:17:35 +0200 Subject: [PATCH 026/214] bugfix + operators [] --- lib/src/vector.dart | 89 ++++++++++++++++++++++++++++----------------- 1 file changed, 56 insertions(+), 33 deletions(-) diff --git a/lib/src/vector.dart b/lib/src/vector.dart index 09d0a04..f4338df 100644 --- a/lib/src/vector.dart +++ b/lib/src/vector.dart @@ -19,7 +19,8 @@ class Bool { class Owner {} abstract class PersistentVector implements Iterable { - E get(int index, [E notSetValue = null]); + Option get(int index); + E operator[](int index); PersistentVector set(int index, E value); PersistentVector push(E value); @@ -32,13 +33,18 @@ abstract class PersistentVector implements Iterable { factory PersistentVector() => new PersistentVectorImpl.empty(); factory PersistentVector.from(Iterable values) => new PersistentVectorImpl.from(values); PersistentVector withMutations(TransientVector fn(TransientVector vect)); + + bool operator==(other); + int get hashCode; } abstract class TransientVector implements Iterable { - E get(int index, [E notSetValue = null]); - TransientVector doSet(int index, E value); - TransientVector doPush(E value); - TransientVector doPop(); + Option get(int index); + E operator[](int index); + void operator []=(int index, E value); + void doSet(int index, E value); + void doPush(E value); + void doPop(); PersistentVector asImmutable(); E get first; @@ -48,7 +54,8 @@ abstract class TransientVector implements Iterable { abstract class PersistentVectorBase extends IterableBase { int _size; - E _get(int index, [E notSetValue = null]); + E _get(int index); + Option _getOption(int index); E get first => _get(0); E get last => _get(this.length > 0 ? this.length - 1 : 0); @@ -93,17 +100,20 @@ abstract class BaseVectorImpl extends PersistentVectorBase { this._size = 0; } - E _get(int index, [E notSetValue = null]) { - index = _checkIndex(index); - if (index >= this._size) { - return notSetValue; + E _get(int index) { + try { + index = _checkIndex(index); + } catch(e) { + return null; } + var node = _vectorNodeFor(index); var maskedIndex = index & _MASK; - return (node != null && (notSetValue == null || node._array.length > maskedIndex)) ? - node._array[maskedIndex] : notSetValue; + return (node != null && node.length > maskedIndex) ? node._get(maskedIndex) : null; } + Option _getOption(int index) => new Option.fromNullable(_get(index)); + BaseVectorImpl _set(int index, E value) { if (index >= this.length) { if (value == getNotSet()) @@ -168,7 +178,7 @@ abstract class BaseVectorImpl extends PersistentVectorBase { var node = this._root; var level = this._level; while (node != null && level > 0) { - node = node._array[(index >> level) & _MASK]; + node = node._get((index >> level) & _MASK); level -= _SHIFT; } return node; @@ -194,7 +204,7 @@ abstract class BaseVectorImpl extends PersistentVectorBase { var oldTailOffset = _getTailOffset(oldSize); var newTailOffset = _getTailOffset(newSize); while (newTailOffset >= 1 << (newLevel + _SHIFT)) { - newRoot = new VNode(newRoot != null && newRoot._array.length > 0 ? [newRoot] : [], owner); + newRoot = new VNode(newRoot != null && newRoot.length > 0 ? [newRoot] : [], owner); newLevel += _SHIFT; } @@ -208,8 +218,8 @@ abstract class BaseVectorImpl extends PersistentVectorBase { var node = newRoot; for (var level = newLevel; level > _SHIFT; level -= _SHIFT) { var idx = (oldTailOffset >> level) & _MASK; - node._array[idx] = node._array[idx] == null ? node._array[idx]._ensureOwner(owner) : new VNode([], owner); - node = node._array[idx]; + node._set(idx , node._get(idx) == null ? node._get(idx)._ensureOwner(owner) : new VNode([], owner)); + node = node._get(idx); } node._set((oldTailOffset >> _SHIFT) & _MASK, oldTail); } @@ -240,17 +250,6 @@ abstract class BaseVectorImpl extends PersistentVectorBase { print("Tail: $_tail"); } - bool operator==(other) { - if (other is! PersistentVectorImpl) return false; - PersistentVectorImpl otherVector = other; - if (this.hashCode != otherVector.hashCode) return false; - if (this.length != otherVector.length) return false; - for (int i = 0; i < this.length; i++) { - if (this._get(i) != otherVector.get(i)) return false; - } - return true; - } - BaseVectorImpl _ensureOwner(Owner ownerID) { if (ownerID == this.__ownerID) { return this; @@ -301,6 +300,8 @@ class VNode { } } + _get(int index) => (index >= 0 && index < this.length) ? this._array[index] : null; + VNode _removeAfter(Owner ownerID, int level, int index) { if ((index == (level > 0 ? 1 << level : 0 ))|| this.length == 0) { return this; @@ -405,7 +406,7 @@ class PersistentVectorImpl extends BaseVectorImpl implements PersistentVec PersistentVectorImpl result = new PersistentVectorImpl.empty(); result = result.withMutations((vector) { values.forEach((E value) { - vector = vector.doPush(value); + vector.doPush(value); }); return vector; }); @@ -437,6 +438,17 @@ class PersistentVectorImpl extends BaseVectorImpl implements PersistentVec return this._hashCode; } + bool operator==(other) { + if (other is! PersistentVectorImpl) return false; + PersistentVectorImpl otherVector = other; + if (this.hashCode != otherVector.hashCode) return false; + if (this.length != otherVector.length) return false; + for (int i = 0; i < this.length; i++) { + if (this._get(i) != otherVector.get(i)) return false; + } + return true; + } + PersistentVectorImpl _clear() { if (this.length == 0) { return this; @@ -449,7 +461,8 @@ class PersistentVectorImpl extends BaseVectorImpl implements PersistentVec PersistentVectorImpl push(E value) => _push(value); PersistentVectorImpl pop() => _pop(); PersistentVectorImpl set(int index, E value) => _set(index, value); - E get(int index, [E notSetValue]) => _get(index); + Option get(int index) => _getOption(index); + E operator[](int index) => _get(index); } class TransientVectorImpl extends BaseVectorImpl implements TransientVector { @@ -481,8 +494,18 @@ class TransientVectorImpl extends BaseVectorImpl implements TransientVecto } PersistentVectorImpl asImmutable() => _asImmutable(); - TransientVectorImpl doPush(E value) => _push(value); - TransientVectorImpl doPop() => _pop(); - E get(int index, [E notSetValue]) => _get(index); - TransientVectorImpl doSet(int index, E value) => _set(index, value); + void doPush(E value) { + _push(value); + } + void doPop() { + _pop(); + } + Option get(int index) => _getOption(index); + E operator[](int index) => _get(index); + void doSet(int index, E value) { + _set(index, value); + } + void operator []=(int index, E value) { + _set(index, value); + } } \ No newline at end of file From 0868fa6cecf8e60ef43f9470676d2afe142980e1 Mon Sep 17 00:00:00 2001 From: Roman Hudec Date: Thu, 28 Aug 2014 18:37:03 +0200 Subject: [PATCH 027/214] should throw if out of range by default, optional orElse in get --- lib/src/vector.dart | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/lib/src/vector.dart b/lib/src/vector.dart index f4338df..4c81946 100644 --- a/lib/src/vector.dart +++ b/lib/src/vector.dart @@ -19,7 +19,7 @@ class Bool { class Owner {} abstract class PersistentVector implements Iterable { - Option get(int index); + E get(int index, {Function orElse: null}); E operator[](int index); PersistentVector set(int index, E value); @@ -39,7 +39,7 @@ abstract class PersistentVector implements Iterable { } abstract class TransientVector implements Iterable { - Option get(int index); + E get(int index, {Function orElse: null}); E operator[](int index); void operator []=(int index, E value); void doSet(int index, E value); @@ -54,7 +54,7 @@ abstract class TransientVector implements Iterable { abstract class PersistentVectorBase extends IterableBase { int _size; - E _get(int index); + E _get(int index, {Function orElse: null}); Option _getOption(int index); E get first => _get(0); @@ -100,21 +100,27 @@ abstract class BaseVectorImpl extends PersistentVectorBase { this._size = 0; } - E _get(int index) { + E _get(int index, {Function orElse: null}) { try { index = _checkIndex(index); } catch(e) { - return null; + if (orElse == null) { + throw(e); + } else { + return orElse(); + } } var node = _vectorNodeFor(index); var maskedIndex = index & _MASK; - return (node != null && node.length > maskedIndex) ? node._get(maskedIndex) : null; + // if resize ever gets publicly exposed, we need to check if node != null + return node._get(maskedIndex); } Option _getOption(int index) => new Option.fromNullable(_get(index)); BaseVectorImpl _set(int index, E value) { + index = _checkIndex(index); if (index >= this.length) { if (value == getNotSet()) return this; @@ -461,7 +467,7 @@ class PersistentVectorImpl extends BaseVectorImpl implements PersistentVec PersistentVectorImpl push(E value) => _push(value); PersistentVectorImpl pop() => _pop(); PersistentVectorImpl set(int index, E value) => _set(index, value); - Option get(int index) => _getOption(index); + E get(int index, {Function orElse: null}) => _get(index, orElse: orElse); E operator[](int index) => _get(index); } @@ -500,7 +506,7 @@ class TransientVectorImpl extends BaseVectorImpl implements TransientVecto void doPop() { _pop(); } - Option get(int index) => _getOption(index); + E get(int index, {Function orElse: null}) => _get(index, orElse: orElse); E operator[](int index) => _get(index); void doSet(int index, E value) { _set(index, value); From 54858c5bcd606cf022013776c22712e3d4940c68 Mon Sep 17 00:00:00 2001 From: Jozef Brandys Date: Fri, 29 Aug 2014 11:05:29 +0200 Subject: [PATCH 028/214] Persistent class + deepPersistent --- lib/persistent.dart | 1 + lib/src/map.dart | 65 +++++++++++++++++++++++------------------ lib/src/map_impl.dart | 10 +++---- lib/src/persistent.dart | 16 ++++++++++ test/deep_map_test.dart | 4 +-- 5 files changed, 59 insertions(+), 37 deletions(-) create mode 100644 lib/src/persistent.dart diff --git a/lib/persistent.dart b/lib/persistent.dart index 58f0977..767a068 100644 --- a/lib/persistent.dart +++ b/lib/persistent.dart @@ -8,6 +8,7 @@ library persistent; import 'dart:collection'; import 'dart:math'; +part 'src/persistent.dart'; part 'src/map.dart'; part 'src/map_impl.dart'; part 'src/set.dart'; diff --git a/lib/src/map.dart b/lib/src/map.dart index e55c968..7a00af8 100644 --- a/lib/src/map.dart +++ b/lib/src/map.dart @@ -12,13 +12,13 @@ part of persistent; * values are supported but null keys are not. * * Persistent data structure is an immutable structure, that provides effective - * creation of slightly mutated copies. - * + * creation of slightly mutated copies. + * * In all the examples below `{k1: v1, k2: v2, ...}` is a shorthand for * `new PersistentMap.fromMap({k1: v1, k2: v2, ...})`. */ abstract class PersistentMap - implements Iterable> { + implements Iterable>, Persistent { /** Creates an empty [PersistentMap] using its default implementation. */ factory PersistentMap() => new PersistentMapImpl(); @@ -39,20 +39,20 @@ abstract class PersistentMap /** * The equality operator. - * + * * Two persistent maps are equal if and only if their sets of keys are equal, * and the equal keys are bound to the equal values. - * + * * Two sets of keys are equal if and only if for each key exists * an equal key in the other set. */ bool operator== (PersistentMap other); - + /* * The documentation is inherited from the Object */ int get hashCode; - + /** * Returns a new map identical to `this` except that it binds [key] to * [value]. @@ -68,7 +68,7 @@ abstract class PersistentMap */ PersistentMap insert(K key, V value, [V combine(V oldvalue, V newvalue)]); - + /** * Calls [insert] recursively using [path] elemenets as keys. */ @@ -82,7 +82,7 @@ abstract class PersistentMap * {'a': 1}.delete('b') == {'a': 1} */ PersistentMap delete(K key); - + /** * Calls [delete] recursively using [path] elemenets as keys. */ @@ -96,7 +96,7 @@ abstract class PersistentMap * {'a': 1, 'b': 2}.lookup('b') == Option.some(2) */ Option lookup(K key); - + /** * Calls [lookup] recursively using [path] elemenets as keys. */ @@ -121,7 +121,7 @@ abstract class PersistentMap * {'a': 1}.adjust('b', (x) => x + 1) == {'a': 1} */ PersistentMap adjust(K key, V update(V value)); - + /** * Calls [adjust] recursively using [path] elemenets as keys. */ @@ -182,6 +182,8 @@ abstract class PersistentMap /// The keys of `this`. Iterable get keys; + bool containsKey(K key); + /// The values of `this`. Iterable get values; @@ -202,12 +204,15 @@ abstract class PersistentMap /// The number of entries of `this`. int get length; + bool get isEmpty => length == 0; + bool get isNotEmpty => length != 0; + /** * Returns a transient copy of `this`. - * + * * This is ussualy called to do some changes and * then create a new [PersistentMap]. - * + * * var persistent1 = new PersistentMap.from({'a':1}); * var transient = persistent1.asTransient(); * transient.doInsert({'b':2}); @@ -218,7 +223,7 @@ abstract class PersistentMap /** * Creates transient copy of `this`, lets it to be modified by [change] * and returns persistent result. - * + * * var persistent1 = new PersistentMap.from({'a':1}); * var persistent2 = persistent1.withTransient((m){ * m.doInsert({'b':2}); @@ -230,7 +235,7 @@ abstract class PersistentMap /** * A transient map, binding keys of type [K] to values of type [V]. Null values * are supported but null keys are not. - * + * * Transient data structure is a mutable structure, that can be effectively * converted to the persistent data structure. It is ussualy created from * a persistent structure to apply some changes and obtain a new persistent @@ -253,7 +258,7 @@ abstract class TransientMap * to [value]. If [key] was bound to some `oldvalue` * and if [combine] is provided then [key] is bound to * `combine(oldvalue, value)`. - * + * * var map = PersistentMap.fromMap({'a': 1}).asTransient(); * map.doInsert('b', 2); // map is now {'a': 1, 'b': 2} * map.doInsert('b', 3); // map is now {'a': 1, 'b': 3} @@ -267,12 +272,12 @@ abstract class TransientMap */ TransientMap doInsertIn(List path, V value, [V combine(V oldvalue, V newvalue)]); - + /** * Unbinds [key]. - * + * * If [key] isn't bound this function has no effect. - * + * * var map = PersistentMap.fromMap({'a': 1, 'b': 2}).asTransient(); * map.doDelete('b', 2); // map is now {'a': 1} * map.doDelete('b', 2); // map is still {'a': 1} @@ -303,7 +308,7 @@ abstract class TransientMap /** * Returns the value for the given [key] or [:null:] if [key] * is not in the map. - * + * * var map = PersistentMap.fromMap({'a': 1, 'b': 2}).asTransient(); * map['b'] == null; * map.doInsert('b', 2); @@ -315,7 +320,7 @@ abstract class TransientMap * Evaluates `f(key, value)` for each (`key`, `value`) pair in `this`. */ void doForEachKeyValue(f(K key, V value)); - + /** * Adjusts the value that is possibly bound to [key] by [update]. * @@ -333,7 +338,7 @@ abstract class TransientMap /** * Updates all values by passing them to [f] and replacing them by results. - * + * * var map = PersistentMap.fromMap({'a': 1, 'b': 2}).asTransient(); * map.mapValues((x) => x + 1) // map is now {'a': 2, 'b': 3} */ @@ -348,7 +353,7 @@ abstract class TransientMap * value for a `key` present in both `this` and [other] is then * `combine(leftvalue, rightvalue)` where `leftvalue` is the value bound to * `key` in `this` and `rightvalue` is the one bound to `key` in [other]. - * + * * var mapA = PersistentMap.fromMap({'a': 1}).asTransient(); * var mapB = PersistentMap.fromMap({'b': 2}).asTransient(); * var mapAB = PersistentMap.fromMap({'a': 3, 'b': 2}).asTransient(); @@ -358,7 +363,7 @@ abstract class TransientMap */ TransientMap doUnion(TransientMap other, [V combine(V left, V right)]); - + /** * Returns a new map whose (key, value) pairs are the intersection of those of * `this` and [other]. @@ -384,22 +389,24 @@ abstract class TransientMap /// The keys of `this`. Iterable get keys; - + + bool containsKey(K key); + /// The values of `this`. Iterable get values; - + /// Randomly picks an entry of `this`. Pair doPickRandomEntry([Random random]); - + /// An iterator through the entries of `this`. Iterator> get iterator; /** * Returns a persistent copy of `this`. - * + * * This is ussualy called when changes to `this` * are finished - * + * * var persistent1 = new PersistentMap.from({'a':1}); * var transient = persistent1.asTransient(); * transient.doInsert({'b':2}); diff --git a/lib/src/map_impl.dart b/lib/src/map_impl.dart index be91a9b..d672065 100644 --- a/lib/src/map_impl.dart +++ b/lib/src/map_impl.dart @@ -28,8 +28,8 @@ class PersistentMapImpl if(other.hashCode != this.hashCode || this.length != other.length) return false; bool equals = true; - this.forEachKeyValue((key, value) { - equals = equals && other.contains(key) && other[key] == value; + this.forEachKeyValue((key, dynamic value) { + equals = equals && other.containsKey(key) && other[key] == value; }); return equals; } @@ -153,8 +153,7 @@ class PersistentMapImpl int get length => _root.length; - // Optimized version of Iterable's contains - bool contains(key) { + bool containsKey(key) { final value = this.lookup(key); return value.isDefined; } @@ -284,8 +283,7 @@ class TransientMapImpl int get length => _root.length; - // Optimized version of Iterable's contains - bool contains(key) { + bool containsKey(key) { final value = this.doLookup(key); return value.isDefined; } diff --git a/lib/src/persistent.dart b/lib/src/persistent.dart new file mode 100644 index 0000000..ca68f14 --- /dev/null +++ b/lib/src/persistent.dart @@ -0,0 +1,16 @@ +part of persistent; + +class Persistent {} + +deepPersistent(from) { + if(from is Persistent) return from; + if(from is Map) { + var map = new PersistentMap(); + return map.withTransient((TransientMap map) { + from.forEach((key,value) => map.doInsert(key, deepPersistent(value))); + }); + } + else { + return from; + } +} \ No newline at end of file diff --git a/test/deep_map_test.dart b/test/deep_map_test.dart index ba33136..0cc31e8 100644 --- a/test/deep_map_test.dart +++ b/test/deep_map_test.dart @@ -15,9 +15,9 @@ main() { map = map.insert('a', new PersistentMap()); PersistentMap map2 = map.insertIn(['a', 'b'], 'c'); - expect(map2['a'], equals(new PersistentMap.fromMap({'b': 'c'}))); + expect(map2 == deepPersistent({'a': {'b': 'c'}}), isTrue); expect(map == map2, isFalse); - expect(map, equals(new PersistentMap.fromMap({'a': new PersistentMap()}))); + expect(map, equals(deepPersistent({'a': {}}))); }); test('adjustIn', () { From a7d640416e8f2421cc0cb31aa174b0d29b72de09 Mon Sep 17 00:00:00 2001 From: Jozef Brandys Date: Fri, 29 Aug 2014 12:34:28 +0200 Subject: [PATCH 029/214] Refactor according to comments. --- benchmark/map_bench_wordcount.dart | 1 + benchmark/src/benchmark.dart | 4 +- benchmark/src/simple_map_1.dart | 6 +- benchmark/src/simple_map_2.dart | 6 +- lib/persistent.dart | 2 +- lib/src/map.dart | 66 ++++++++++----------- lib/src/map_impl.dart | 80 ++++++++++++++------------ lib/src/option.dart | 57 ------------------ lib/src/persistent.dart | 7 +++ lib/src/set_impl.dart | 2 +- test/deep_map_test.dart | 2 +- test/option_test.dart | 92 ------------------------------ test/src/map_model.dart | 6 +- test/src/test_util.dart | 2 +- 14 files changed, 100 insertions(+), 233 deletions(-) delete mode 100644 lib/src/option.dart create mode 100644 lib/src/persistent.dart delete mode 100644 test/option_test.dart diff --git a/benchmark/map_bench_wordcount.dart b/benchmark/map_bench_wordcount.dart index aaa5f33..b2aaf89 100644 --- a/benchmark/map_bench_wordcount.dart +++ b/benchmark/map_bench_wordcount.dart @@ -46,6 +46,7 @@ void bench(List doyle, List austeen) { watch.stop(); print('Persistent ${watch.elapsedMilliseconds / 10}'); + runT(doyle, austeen); watch.reset(); watch.start(); for (int i = 0; i < 10; i++) { diff --git a/benchmark/src/benchmark.dart b/benchmark/src/benchmark.dart index 0cb8ba1..d7f98a1 100644 --- a/benchmark/src/benchmark.dart +++ b/benchmark/src/benchmark.dart @@ -70,8 +70,10 @@ class Benchmark { for (int i = 0; i <= size * 2; i++) { map.doLookup("key$i"); } + PersistentMap mapP = map.asPersistent(); + TransientMap saved = mapP.asTransient(); + map = mapP.asTransient(); - TransientMap saved = map.asPersistent().asTransient(); for (int i = size * 2; i >= 0; i--) { map = map.doDelete("key$i"); } diff --git a/benchmark/src/simple_map_1.dart b/benchmark/src/simple_map_1.dart index 0365de6..641cb21 100644 --- a/benchmark/src/simple_map_1.dart +++ b/benchmark/src/simple_map_1.dart @@ -42,15 +42,15 @@ class SimplePersistentMap extends IterableBase implements PersistentMap delete(K key) => new SimplePersistentMap._internal(_list.strictWhere((p) => p.fst != key)); - Option lookup(K key) { + V lookup(K key, [orElse()]) { LinkedList> it = _list; while (it.isCons) { Cons> cons = it.asCons; Pair elem = cons.elem; - if (elem.fst == key) return new Option.some(elem.snd); + if (elem.fst == key) return elem.snd; it = cons.tail; } - return new Option.none(); + return orElse == null ? null : orElse(); } PersistentMap mapValues(f(V)) => diff --git a/benchmark/src/simple_map_2.dart b/benchmark/src/simple_map_2.dart index 730f509..4cef035 100644 --- a/benchmark/src/simple_map_2.dart +++ b/benchmark/src/simple_map_2.dart @@ -30,11 +30,11 @@ class SimplePersistentMap2 extends IterableBase implements PersistentMap lookup(K key) { + V lookup(K key, [orElse]) { if (_map.containsKey(key)) { - return new Option.some(_map[key]); + return _map[key]; } else { - return new Option.none(); + return orElse == null ? null : orElse(); } } diff --git a/lib/persistent.dart b/lib/persistent.dart index 58f0977..2dc550c 100644 --- a/lib/persistent.dart +++ b/lib/persistent.dart @@ -8,10 +8,10 @@ library persistent; import 'dart:collection'; import 'dart:math'; +part 'src/persistent.dart'; part 'src/map.dart'; part 'src/map_impl.dart'; part 'src/set.dart'; part 'src/set_impl.dart'; part 'src/linked_list.dart'; -part 'src/option.dart'; part 'src/pair.dart'; diff --git a/lib/src/map.dart b/lib/src/map.dart index e55c968..b44a67d 100644 --- a/lib/src/map.dart +++ b/lib/src/map.dart @@ -12,8 +12,8 @@ part of persistent; * values are supported but null keys are not. * * Persistent data structure is an immutable structure, that provides effective - * creation of slightly mutated copies. - * + * creation of slightly mutated copies. + * * In all the examples below `{k1: v1, k2: v2, ...}` is a shorthand for * `new PersistentMap.fromMap({k1: v1, k2: v2, ...})`. */ @@ -39,20 +39,20 @@ abstract class PersistentMap /** * The equality operator. - * + * * Two persistent maps are equal if and only if their sets of keys are equal, * and the equal keys are bound to the equal values. - * + * * Two sets of keys are equal if and only if for each key exists * an equal key in the other set. */ bool operator== (PersistentMap other); - + /* * The documentation is inherited from the Object */ int get hashCode; - + /** * Returns a new map identical to `this` except that it binds [key] to * [value]. @@ -68,7 +68,7 @@ abstract class PersistentMap */ PersistentMap insert(K key, V value, [V combine(V oldvalue, V newvalue)]); - + /** * Calls [insert] recursively using [path] elemenets as keys. */ @@ -82,7 +82,7 @@ abstract class PersistentMap * {'a': 1}.delete('b') == {'a': 1} */ PersistentMap delete(K key); - + /** * Calls [delete] recursively using [path] elemenets as keys. */ @@ -95,15 +95,15 @@ abstract class PersistentMap * {'a': 1}.lookup('b') == Option.none() * {'a': 1, 'b': 2}.lookup('b') == Option.some(2) */ - Option lookup(K key); - + V lookup(K key, [dynamic orElse()]); + /** * Calls [lookup] recursively using [path] elemenets as keys. */ - Option lookupIn(List path); + lookupIn(List path, [dynamic orElse()]); /** - * Returns the value for the given [key] or [:null:] if [key] + * Returns the value for the given [key] or throws if [key] * is not in the map. */ V operator [](K key); @@ -121,7 +121,7 @@ abstract class PersistentMap * {'a': 1}.adjust('b', (x) => x + 1) == {'a': 1} */ PersistentMap adjust(K key, V update(V value)); - + /** * Calls [adjust] recursively using [path] elemenets as keys. */ @@ -204,10 +204,10 @@ abstract class PersistentMap /** * Returns a transient copy of `this`. - * + * * This is ussualy called to do some changes and * then create a new [PersistentMap]. - * + * * var persistent1 = new PersistentMap.from({'a':1}); * var transient = persistent1.asTransient(); * transient.doInsert({'b':2}); @@ -218,7 +218,7 @@ abstract class PersistentMap /** * Creates transient copy of `this`, lets it to be modified by [change] * and returns persistent result. - * + * * var persistent1 = new PersistentMap.from({'a':1}); * var persistent2 = persistent1.withTransient((m){ * m.doInsert({'b':2}); @@ -230,7 +230,7 @@ abstract class PersistentMap /** * A transient map, binding keys of type [K] to values of type [V]. Null values * are supported but null keys are not. - * + * * Transient data structure is a mutable structure, that can be effectively * converted to the persistent data structure. It is ussualy created from * a persistent structure to apply some changes and obtain a new persistent @@ -253,7 +253,7 @@ abstract class TransientMap * to [value]. If [key] was bound to some `oldvalue` * and if [combine] is provided then [key] is bound to * `combine(oldvalue, value)`. - * + * * var map = PersistentMap.fromMap({'a': 1}).asTransient(); * map.doInsert('b', 2); // map is now {'a': 1, 'b': 2} * map.doInsert('b', 3); // map is now {'a': 1, 'b': 3} @@ -267,12 +267,12 @@ abstract class TransientMap */ TransientMap doInsertIn(List path, V value, [V combine(V oldvalue, V newvalue)]); - + /** * Unbinds [key]. - * + * * If [key] isn't bound this function has no effect. - * + * * var map = PersistentMap.fromMap({'a': 1, 'b': 2}).asTransient(); * map.doDelete('b', 2); // map is now {'a': 1} * map.doDelete('b', 2); // map is still {'a': 1} @@ -293,17 +293,17 @@ abstract class TransientMap * map.doInsert('b', 2); * map.doLookup('b') == Option.some(2); */ - Option doLookup(K key); + V doLookup(K key, [dynamic orElse()]); /** * Calls [doLookup] recursively using [path] elemenets as keys. */ - Option doLookupIn(List path); + doLookupIn(List path, [dynamic orElse()]); /** * Returns the value for the given [key] or [:null:] if [key] * is not in the map. - * + * * var map = PersistentMap.fromMap({'a': 1, 'b': 2}).asTransient(); * map['b'] == null; * map.doInsert('b', 2); @@ -315,7 +315,7 @@ abstract class TransientMap * Evaluates `f(key, value)` for each (`key`, `value`) pair in `this`. */ void doForEachKeyValue(f(K key, V value)); - + /** * Adjusts the value that is possibly bound to [key] by [update]. * @@ -333,7 +333,7 @@ abstract class TransientMap /** * Updates all values by passing them to [f] and replacing them by results. - * + * * var map = PersistentMap.fromMap({'a': 1, 'b': 2}).asTransient(); * map.mapValues((x) => x + 1) // map is now {'a': 2, 'b': 3} */ @@ -348,7 +348,7 @@ abstract class TransientMap * value for a `key` present in both `this` and [other] is then * `combine(leftvalue, rightvalue)` where `leftvalue` is the value bound to * `key` in `this` and `rightvalue` is the one bound to `key` in [other]. - * + * * var mapA = PersistentMap.fromMap({'a': 1}).asTransient(); * var mapB = PersistentMap.fromMap({'b': 2}).asTransient(); * var mapAB = PersistentMap.fromMap({'a': 3, 'b': 2}).asTransient(); @@ -358,7 +358,7 @@ abstract class TransientMap */ TransientMap doUnion(TransientMap other, [V combine(V left, V right)]); - + /** * Returns a new map whose (key, value) pairs are the intersection of those of * `this` and [other]. @@ -384,22 +384,22 @@ abstract class TransientMap /// The keys of `this`. Iterable get keys; - + /// The values of `this`. Iterable get values; - + /// Randomly picks an entry of `this`. Pair doPickRandomEntry([Random random]); - + /// An iterator through the entries of `this`. Iterator> get iterator; /** * Returns a persistent copy of `this`. - * + * * This is ussualy called when changes to `this` * are finished - * + * * var persistent1 = new PersistentMap.from({'a':1}); * var transient = persistent1.asTransient(); * transient.doInsert({'b':2}); diff --git a/lib/src/map_impl.dart b/lib/src/map_impl.dart index be91a9b..124e1f8 100644 --- a/lib/src/map_impl.dart +++ b/lib/src/map_impl.dart @@ -88,21 +88,26 @@ class PersistentMapImpl (e) => e.deleteIn(path, offset+1))); } - Option lookup(K key) { - return _root.lookup(key); + V lookup(K key, [dynamic orElse()]) { + var val = _root.lookup(key); + if(isNone(val)) return (orElse == null ? null : orElse()); + return val; } - Option lookupIn(List path, [offset = 0]) { - Option e = lookup(path[offset]); + lookupIn(List path, [offset = 0, dynamic orElse()]) { + dynamic e = lookup(path[offset]); offset++; if(path.length == offset) return e; - else return e.isDefined ? e.value.lookupIn(path, offset) : null; + else return !isNone(e) ? + e.lookupIn(path, offset, orElse) + : + (orElse == null ? null : orElse()); } - V operator [](K key) { - return _root.lookup(key).asNullable; - } + V operator [](K key) => + lookup(key, () => throw new Exception('Key is not defined')); + void forEachKeyValue(f(K key, V value)) => _root.forEachKeyValue(f); @@ -156,7 +161,7 @@ class PersistentMapImpl // Optimized version of Iterable's contains bool contains(key) { final value = this.lookup(key); - return value.isDefined; + return !isNone(value); } TransientMap asTransient() { @@ -225,21 +230,25 @@ class TransientMapImpl (e) => e.deleteIn(path, offset+1))); } - Option doLookup(K key) { - return _root.lookup(key); + V doLookup(K key, [dynamic orElse()]) { + var val = _root.lookup(key); + if(isNone(val)) return (orElse == null ? null : orElse()); + return val; } - Option doLookupIn(List path, [offset = 0]) { - Option e = doLookup(path[offset]); + doLookupIn(List path, [offset = 0, dynamic orElse()]) { + dynamic e = doLookup(path[offset]); offset++; if(path.length == offset) return e; - else return e.isDefined ? e.value.lookupIn(path, offset) : null; + else return !isNone(e) ? + e.lookupIn(path, offset, orElse) + : + (orElse == null ? null : orElse()); } - V operator [](K key) { - return _root.lookup(key).asNullable; - } + V operator [](K key) => + doLookup(key, () => throw new Exception('Key is not defined')); void doForEachKeyValue(f(K key, V value)) => _root.forEachKeyValue(f); @@ -287,7 +296,7 @@ class TransientMapImpl // Optimized version of Iterable's contains bool contains(key) { final value = this.doLookup(key); - return value.isDefined; + return !isNone(value); } PersistentMap asPersistent() { @@ -338,14 +347,12 @@ abstract class NodeBase Pair pickRandomEntry([Random random]) => elementAt((random != null ? random : _random).nextInt(this.length)); - V operator [](K key) => this.lookup(key).asNullable; - NodeBase insert(Owner owner, K key, V value, [V combine(V oldvalue, V newvalue)]); NodeBase delete(Owner owner, K key); - Option lookup(K key); + V lookup(K key); void forEachKeyValue(f(K key, V value)); @@ -367,12 +374,11 @@ abstract class NodeBase abstract class _ANodeBase extends NodeBase { Owner _owner; - _ANodeBase(this._owner, length, this.isEmpty, this._isLeaf) : super(length); + _ANodeBase(this._owner, length, this._isLeaf) : super(length); - final bool isEmpty; final bool _isLeaf; - Option _lookup(K key, int hash, int depth); + V _lookup(K key, int hash, int depth); NodeBase _insertWith(Owner owner, LinkedList> keyValues, int size, V combine(V x, V y), int hash, int depth); NodeBase _intersectWith(Owner owner, LinkedList> keyValues, int size, @@ -405,7 +411,7 @@ abstract class _ANodeBase extends NodeBase { LinkedList> _onePair(K key, V value) => new Cons>(new Pair(key, value), new Nil>()); - Option lookup(K key) => + V lookup(K key) => _lookup(key, key.hashCode & 0x3fffffff, 0); NodeBase insert(Owner owner, K key, V value, [V combine(V x, V y)]) => @@ -443,9 +449,9 @@ class _EmptyMapIterator implements Iterator> { } class _EmptyMap extends _ANodeBase { - _EmptyMap(Owner owner) : super(owner, 0, true, false); + _EmptyMap(Owner owner) : super(owner, 0, false); - Option _lookup(K key, int hash, int depth) => new Option.none(); + V _lookup(K key, int hash, int depth) => none(); NodeBase _insertWith(Owner owner, LinkedList> keyValues, int size, V combine(V x, V y), int hash, @@ -517,7 +523,7 @@ class _Leaf extends _ANodeBase { int _hash; LinkedList> _pairs; - _Leaf.abc(Owner owner, this._hash, pairs, int size) : super(owner, size, false, true) { + _Leaf.abc(Owner owner, this._hash, pairs, int size) : super(owner, size, true) { this._pairs = pairs; assert(size == pairs.length); } @@ -679,17 +685,17 @@ class _Leaf extends _ANodeBase { V combine(V x, V y), int depth) => m._intersectWith(owner, _pairs, length, combine, _hash, depth); - Option _lookup(K key, int hash, int depth) { + V _lookup(K key, int hash, int depth) { if (hash != _hash) - return new Option.none(); + return none(); LinkedList> it = _pairs; while (it.isCons) { Cons> cons = it.asCons; Pair elem = cons.elem; - if (elem.fst == key) return new Option.some(elem.snd); + if (elem.fst == key) return elem.snd; it = cons.tail; } - return new Option.none(); + return none(); } NodeBase mapValues(Owner owner, f(V)) => @@ -774,7 +780,7 @@ class _SubMap extends _ANodeBase { int _bitmap; List<_ANodeBase> _array; - _SubMap.abc(Owner owner, this._bitmap, this._array, int size) : super(owner, size, false, false); + _SubMap.abc(Owner owner, this._bitmap, this._array, int size) : super(owner, size, false); factory _SubMap.ensureOwner(_SubMap old, Owner owner, bitmap, array, int size) { if(ownerEquals(owner, old._owner)) { @@ -794,7 +800,7 @@ class _SubMap extends _ANodeBase { return n & 0x0000003F; } - Option _lookup(K key, int hash, int depth) { + V _lookup(K key, int hash, int depth) { int branch = (hash >> (depth * 5)) & 0x1f; int mask = 1 << branch; if ((_bitmap & mask) != 0) { @@ -802,7 +808,7 @@ class _SubMap extends _ANodeBase { _ANodeBase map = _array[index]; return map._lookup(key, hash, depth + 1); } else { - return new Option.none(); + return none(); } } @@ -820,7 +826,7 @@ class _SubMap extends _ANodeBase { _ANodeBase newM = m._insertWith(owner, keyValues, size, combine, hash, depth + 1); if(identical(m, newM)) { - if(oldSize != newM.length) this._length += m.length - oldSize; + if(oldSize != m.length) this._length += m.length - oldSize; return this; } List<_ANodeBase> newarray = makeCopyIfNeeded(owner, this._owner, _array); @@ -869,7 +875,7 @@ class _SubMap extends _ANodeBase { this._length += delta; return this; } - if (newm.isEmpty) { + if (newm is _EmptyMap) { if (_array.length > 2) { int newsize = _array.length - 1; List<_ANodeBase> newarray = diff --git a/lib/src/option.dart b/lib/src/option.dart deleted file mode 100644 index 245b019..0000000 --- a/lib/src/option.dart +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code -// is governed by a BSD-style license that can be found in the LICENSE file. - -// Author: Paul Brauner (polux@google.com) - -part of persistent; - -class Option { - static final _none = new Option._internal(false, null); - - final T _value; - final bool isDefined; - - Option._internal(this.isDefined, this._value); - - factory Option.none() => _none; - - factory Option.some(T value) => new Option._internal(true, value); - - factory Option.fromNullable(T nullableValue) => - nullableValue == null ? _none : new Option.some(nullableValue); - - T get value { - if (isDefined) return _value; - throw new StateError('Option.none() has no value'); - } - - T get asNullable => isDefined ? _value : null; - - T orElse(T defaultValue) => isDefined ? _value : defaultValue; - - T orElseCompute(T defaultValue()) => isDefined ? _value : defaultValue(); - - /// [:forall U, Option map(U f(T value)):] - Option map(f(T value)) => - isDefined ? new Option.some(f(_value)) : this; - - /// [:forall U, Option map(Option f(T value)):] - Option expand(Option f(T value)) => - isDefined ? f(_value) : this; - - /// Precondition: [:this is Option