├── test ├── .gitignore ├── utils.dart ├── all.dart ├── reference_test.dart ├── cursor_test.dart ├── linked_list_test.dart ├── map_test.dart ├── set_test.dart ├── vector_test.dart ├── randomized_vector_test.dart ├── functions_test.dart └── randomized_map_test.dart ├── benchmark ├── .gitignore ├── mori_speed │ ├── .gitignore │ ├── package.json │ ├── makefile │ └── mori_speed.js ├── preamble │ ├── README │ └── d8.js ├── map_speed │ ├── simple.dart │ ├── makefile │ ├── interface.dart │ ├── benchmarks.dart │ ├── map_speed.dart │ └── interface_impl.dart ├── vector_speed │ ├── makefile │ ├── interface.dart │ ├── vector_speed.dart │ ├── interface_impl.dart │ └── benchmarks.dart ├── mori │ ├── makefile │ └── memory.js ├── vector_memory │ ├── vector_memory.dart │ └── makefile └── map_memory │ ├── map_memory.dart │ └── makefile ├── .gitignore ├── CHANGELOG.md ├── AUTHORS ├── pubspec.yaml ├── changes_2_0.md ├── lib ├── src │ ├── cursor.dart │ ├── pair.dart │ ├── reference.dart │ ├── set_impl.dart │ ├── set.dart │ ├── linked_list.dart │ ├── persistent.dart │ ├── vector.dart │ ├── map.dart │ ├── vector_impl.dart │ ├── map_impl.dart │ └── functions.dart └── persistent.dart ├── example ├── persistent_set_example.dart ├── example.dart └── overall_example.dart ├── LICENSE ├── transients.md ├── technical.md └── README.md /test/.gitignore: -------------------------------------------------------------------------------- 1 | local_*.dart 2 | -------------------------------------------------------------------------------- /benchmark/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | *.js.map 3 | *.js.deps 4 | temp.out 5 | -------------------------------------------------------------------------------- /benchmark/mori_speed/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | compiled.js 3 | !mori_speed.js -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | packages 4 | .project 5 | local_pubspec_yaml 6 | pubspec.lock 7 | .idea 8 | *.swp 9 | build 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Persistent Changelog 2 | 3 | ## 0.8.1 4 | 5 | - Added `CHANGELOG.md` 6 | - Moved benchmarks to the standard `benchmark` directory -------------------------------------------------------------------------------- /benchmark/preamble/README: -------------------------------------------------------------------------------- 1 | The d8.js is polyfill some of the functionality that browsers provide. For more info: 2 | http://dart.googlecode.com/svn/branches/1.8/dart/sdk/lib/_internal/compiler/js_lib/preambles/README 3 | -------------------------------------------------------------------------------- /benchmark/mori_speed/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mori_speed", 3 | "main": "mori_speed.js", 4 | "dependencies": { 5 | "benchmark": "^1.0.0", 6 | "closurecompiler": "^1.5.1", 7 | "mori": "0.3.0" 8 | }, 9 | "private": true 10 | } 11 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Paul Brauner 2 | Rafael Brandão 3 | Jozef Brandys 4 | Roman Hudec 5 | Marian Hornak 6 | Tomas Kulich 7 | Matus Fedak 8 | -------------------------------------------------------------------------------- /benchmark/mori_speed/makefile: -------------------------------------------------------------------------------- 1 | all: clean run 2 | 3 | init: 4 | npm up 5 | 6 | compiled.js: mori_speed.js 7 | node_modules/closurecompiler/bin/ccjs mori_speed.js > compiled.js 8 | 9 | run: compiled.js 10 | node compiled.js 11 | 12 | clean: 13 | rm -f compiled.js 14 | 15 | 16 | -------------------------------------------------------------------------------- /benchmark/map_speed/simple.dart: -------------------------------------------------------------------------------- 1 | import 'package:vacuum_persistent/persistent.dart'; 2 | 3 | main(){ 4 | Stopwatch timer = new Stopwatch()..start(); 5 | var map; 6 | for(var j=0; j<100;j++){ 7 | map = new PMap(); 8 | for(var i=0; i<3000; i++){ 9 | map = map.assoc(i*i, 'val'); 10 | } 11 | } 12 | print(timer.elapsedMilliseconds); 13 | } 14 | -------------------------------------------------------------------------------- /test/utils.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, VaccumLabs. 2 | // Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code 3 | // is governed by a BSD-style license that can be found in the LICENSE file. 4 | 5 | // Authors are listed in the AUTHORS file 6 | 7 | import 'dart:math'; 8 | 9 | Random r = new Random(); 10 | bool probability(num what) => r.nextDouble() < what; 11 | random_elem(List list) => list[r.nextInt(list.length)]; -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: vacuum_persistent 2 | version: 2.0.0 3 | author: see AUTHORS 4 | description: Efficient persistent data structures for Dart 5 | homepage: https://github.com/vacuumlabs/persistent 6 | environment: 7 | sdk: '>=0.8.10+6 <2.0.0' 8 | dependencies: 9 | quiver: '>=0.18.0 <1.0.0' 10 | dev_dependencies: 11 | args: '>=0.9.0 <0.10.0' 12 | benchmark_harness: any 13 | enumerators: '>=0.5.0 <0.6.0' 14 | http: '>=0.9.0 <0.10.0' 15 | unittest: '>=0.9.0 <0.10.0' 16 | -------------------------------------------------------------------------------- /changes_2_0.md: -------------------------------------------------------------------------------- 1 | - memory footprint reduced with a factor of 15 (wait what? Was the old implementation so 2 | ineffective? Or the new one is so cool? The truth is: both. Check out benchmarks) 3 | 4 | - changes in API, most notably PersistentMap -> PMap, PersistentVector -> PVec 5 | 6 | - more effective == and != on PMap 7 | 8 | - deleted several classes, the whole class/interface hierarchy becomes much simpler (although little bit dirtier; some performance-motivated compromises were introduced) 9 | 10 | -------------------------------------------------------------------------------- /benchmark/map_speed/makefile: -------------------------------------------------------------------------------- 1 | all: clean run 2 | 3 | 4 | build/map_speed.js: map_speed.dart 5 | mkdir -p build 6 | dart2js -o build/tmp.js map_speed.dart 7 | cat ../preamble/d8.js >> build/map_speed.js 8 | cat build/tmp.js >> build/map_speed.js 9 | 10 | clean: 11 | rm -rf build 12 | 13 | 14 | run: build/map_speed.js 15 | 16 | @echo "Performing several insert, lookup and delete operations on flat maps." 17 | @echo "The number of operations depends on the map size." 18 | 19 | @echo "--------" 20 | @echo "DartVM:" 21 | @dart map_speed.dart 22 | 23 | @echo "--------" 24 | @echo "NodeJS:" 25 | @node build/map_speed.js 26 | 27 | -------------------------------------------------------------------------------- /benchmark/vector_speed/makefile: -------------------------------------------------------------------------------- 1 | all: clean run 2 | 3 | 4 | build/vector_speed.js: vector_speed.dart 5 | mkdir -p build 6 | dart2js -o build/tmp.js vector_speed.dart 7 | cat ../preamble/d8.js >> build/vector_speed.js 8 | cat build/tmp.js >> build/vector_speed.js 9 | 10 | dart2js -o build/vector_speed.js vector_speed.dart 11 | 12 | 13 | clean: 14 | rm -rf build 15 | 16 | 17 | run: build/vector_speed.js 18 | 19 | @echo "Performing several get, set, push and pop operations on flat vectors." 20 | @echo "The number of operations depends on the map size." 21 | 22 | @echo "--------" 23 | @echo "DartVM:" 24 | @dart vector_speed.dart 25 | 26 | @echo "--------" 27 | @echo "NodeJS:" 28 | @node build/vector_speed.js 29 | 30 | -------------------------------------------------------------------------------- /lib/src/cursor.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, VacuumLabs. 2 | // Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code 3 | // is governed by a BSD-style license that can be found in the LICENSE file. 4 | 5 | // Authors are listed in the AUTHORS file 6 | 7 | part of persistent; 8 | 9 | class Cursor { 10 | final Reference _ref; 11 | final PVec _path; 12 | 13 | Cursor(Reference this._ref, Iterable path) : 14 | _path = (path is PVec)? path: new PVec.from(path); 15 | 16 | Cursor operator[](val) => new Cursor(_ref, conj(_path, val) as PVec); 17 | 18 | deref([notFound = _none]) => getIn(_ref.deref(), _path, notFound); 19 | 20 | update(f) => _ref.update((_) => updateIn(_ref.deref(), _path, f)); 21 | } 22 | -------------------------------------------------------------------------------- /benchmark/mori/makefile: -------------------------------------------------------------------------------- 1 | template_size = 1000 2 | 3 | all: run 4 | 5 | run: 6 | 7 | @echo -n "NodeJS Mori persistent map: " 8 | @ (! node --max-old-space-size=1024 memory.js ${template_size} persistent_map > temp.out) 2>/dev/null 9 | @tail -1 temp.out 10 | 11 | @echo -n "NodeJS vanilla JS map: " 12 | @ (! node --max-old-space-size=1024 memory.js ${template_size} map > temp.out) 2>/dev/null 13 | @tail -1 temp.out 14 | 15 | @echo -n "NodeJS Mori persistent vector: " 16 | @ (! node --max-old-space-size=1024 memory.js ${template_size} persistent_vector > temp.out) 2>/dev/null 17 | @tail -1 temp.out 18 | 19 | @echo -n "NodeJS vanilla JS list: " 20 | @ (! node --max-old-space-size=1024 memory.js ${template_size} list > temp.out) 2>/dev/null 21 | @tail -1 temp.out 22 | 23 | rm -f temp.out 24 | 25 | -------------------------------------------------------------------------------- /benchmark/map_speed/interface.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, VacuumLabs. 2 | // Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code 3 | // is governed by a BSD-style license that can be found in the LICENSE file. 4 | 5 | // Authors are listed in the AUTHORS file 6 | 7 | part of map_bench; 8 | 9 | abstract class BenchmarkInterface{ 10 | 11 | void assoc(K key, V value); 12 | void get(K key); 13 | void delete(K key); 14 | 15 | void save(); 16 | void restore(); 17 | } 18 | 19 | 20 | abstract class EncapsulatingInterface 21 | extends BenchmarkInterface{ 22 | 23 | T object = null; 24 | T object_copy = null; 25 | 26 | T _copy(); 27 | 28 | save(){ 29 | object_copy = _copy(); 30 | } 31 | 32 | restore(){ 33 | return object = object_copy; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/persistent.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, VacuumLabs. 2 | // Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code 3 | // is governed by a BSD-style license that can be found in the LICENSE file. 4 | 5 | // Authors are listed in the AUTHORS file 6 | 7 | library persistent; 8 | 9 | import 'dart:collection'; 10 | import 'dart:math'; 11 | import 'package:quiver/core.dart'; 12 | import 'package:quiver/iterables.dart' as quiver; 13 | import 'dart:async'; 14 | 15 | part 'src/persistent.dart'; 16 | part 'src/map.dart'; 17 | part 'src/map_impl.dart'; 18 | part 'src/set.dart'; 19 | part 'src/set_impl.dart'; 20 | part 'src/linked_list.dart'; 21 | part 'src/pair.dart'; 22 | part 'src/vector.dart'; 23 | part 'src/vector_impl.dart'; 24 | part 'src/functions.dart'; 25 | part 'src/cursor.dart'; 26 | part 'src/reference.dart'; 27 | -------------------------------------------------------------------------------- /benchmark/vector_speed/interface.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, VacuumLabs. 2 | // Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code 3 | // is governed by a BSD-style license that can be found in the LICENSE file. 4 | 5 | // Authors are listed in the AUTHORS file 6 | 7 | part of vector_speed; 8 | 9 | abstract class BenchmarkInterface{ 10 | 11 | void create(); 12 | void push(E value); 13 | void set(int index, E value); 14 | void get(int index); 15 | void pop(); 16 | 17 | void save(); 18 | void restore(); 19 | } 20 | 21 | 22 | abstract class EncapsulatingInterface 23 | extends BenchmarkInterface{ 24 | 25 | T object = null; 26 | T object_copy = null; 27 | 28 | T _copy(); 29 | 30 | save(){ 31 | object_copy = _copy(); 32 | } 33 | 34 | restore(){ 35 | object = object_copy; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/all.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, VaccumLabs. 2 | // Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code 3 | // is governed by a BSD-style license that can be found in the LICENSE file. 4 | 5 | // Authors are listed in the AUTHORS file 6 | 7 | import 'linked_list_test.dart' as linked_list_test; 8 | import 'map_test.dart' as map_test; 9 | import 'set_test.dart' as set_test; 10 | import 'vector_test.dart' as vector_test; 11 | import 'randomized_map_test.dart' as randomized_map_test; 12 | import 'randomized_vector_test.dart' as randomized_vector_test; 13 | import 'cursor_test.dart' as cursor_test; 14 | import 'reference_test.dart' as reference_test; 15 | 16 | main() { 17 | linked_list_test.run(); 18 | map_test.run(); 19 | set_test.run(); 20 | vector_test.run(); 21 | reference_test.run(); 22 | cursor_test.run(); 23 | randomized_map_test.run(100); 24 | randomized_vector_test.run(100); 25 | } 26 | -------------------------------------------------------------------------------- /benchmark/vector_speed/vector_speed.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, VacuumLabs. 2 | // Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code 3 | // is governed by a BSD-style license that can be found in the LICENSE file. 4 | 5 | // Authors are listed in the AUTHORS file 6 | 7 | library vector_speed; 8 | 9 | import 'package:vacuum_persistent/persistent.dart'; 10 | import 'package:benchmark_harness/benchmark_harness.dart'; 11 | 12 | part 'benchmarks.dart'; 13 | part 'interface.dart'; 14 | part 'interface_impl.dart'; 15 | 16 | var interfaces = { 17 | "PersistentVector": () => new PersistentVectorInterface(), 18 | "TransientVector": () => new TransientVectorInterface(), 19 | "List": () => new ListInterface(), 20 | }; 21 | 22 | void main() { 23 | 24 | for (int n in [1,10,100,1000,10000]) { 25 | for (String name in interfaces.keys){ 26 | new ReadBenchmark(n, interfaces[name](), name).report(); 27 | } 28 | } 29 | 30 | for (int n in [1,10,100,1000,10000]) { 31 | for (String name in interfaces.keys){ 32 | new WriteBenchmark(n, interfaces[name](), name).report(); 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /benchmark/vector_memory/vector_memory.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, VacuumLabs. 2 | // Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code 3 | // is governed by a BSD-style license that can be found in the LICENSE file. 4 | 5 | // Authors are listed in the AUTHORS file 6 | 7 | library vector_memory; 8 | 9 | import 'package:vacuum_persistent/persistent.dart'; 10 | 11 | List template = []; 12 | 13 | // here we will store the data to prevent it from garbage collection 14 | List data = []; 15 | 16 | var creators = { 17 | 18 | "persistent": () => new PVec.from(template), 19 | 20 | "list": () => new List.from(template), 21 | }; 22 | 23 | void run(int template_size, String mode) { 24 | 25 | template.addAll(new List.generate(template_size, (i)=>"$i".padLeft(8))); 26 | 27 | int allocated = 0; 28 | for(bool go = true; go; allocated++){ 29 | try{ 30 | go = false; 31 | var a = creators[mode](); 32 | data.add(a); 33 | go = true; 34 | print(1073741824.0 / allocated / template_size); 35 | } catch(e) { 36 | data = null; 37 | } 38 | } 39 | } 40 | 41 | main(List args){ 42 | 43 | run(int.parse(args[0]), args[1]); 44 | } 45 | -------------------------------------------------------------------------------- /example/persistent_set_example.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code 2 | // is governed by a BSD-style license that can be found in the LICENSE file. 3 | 4 | // Author: Paul Brauner (polux@google.com) 5 | 6 | library map_example; 7 | 8 | import 'package:vacuum_persistent/persistent.dart'; 9 | 10 | main() { 11 | final emptySet = new PersistentSet(); 12 | final s1 = emptySet.insert('a').insert('b'); 13 | final s2 = new PersistentSet.from(['a', 'c']); 14 | 15 | print(s1); // {a, b} 16 | print(s2); // {a, c} 17 | print(s1.contains('a')); // true 18 | print(s1.contains('c')); // false 19 | 20 | final s3 = s1.delete('a'); 21 | print(s1); // {a, b} 22 | print(s3); // {b} 23 | 24 | final s4 = s1 + s2; 25 | print(s4); // {a, b, c} 26 | 27 | final s5 = s1.map((s) => 'prefix_$s'); 28 | print(s5); // {prefix_a, prefix_b} 29 | 30 | final s6 = s1 - s2; 31 | print(s6); // {b} 32 | 33 | final s7 = s1 * s2; 34 | print(s7); // {Pair(a, c), Pair(a, a), Pair(b, c), Pair(b, a)} 35 | 36 | final s8 = s1.intersection(s2); 37 | print(s8); // {a} 38 | 39 | for (final e in s4) { 40 | print(e); // a, b, c 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /example/example.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code 2 | // is governed by a BSD-style license that can be found in the LICENSE file. 3 | 4 | // Author: Paul Brauner (polux@google.com) 5 | 6 | library map_example; 7 | 8 | import 'package:vacuum_persistent/persistent.dart'; 9 | 10 | main() { 11 | 12 | PMap map = new PMap.from({"a":1, "b":2}); 13 | 14 | print(map["a"]); // 1 15 | print(map.get("b")); // 2 16 | print(map.get("c", ":(")); // :( 17 | 18 | print(map.assoc("c", 3)); // {a: 1, b: 2, c: 3} 19 | print(map.assoc("d", 4)); // {a: 1, b: 2, d: 4} 20 | 21 | print(map.update("a", (x) => x+1)); // {a: 2, b: 2} 22 | print(map.delete("b")); // {a: 1} 23 | print(map.delete("not-a-key", missingOk: true)); // does not throw 24 | 25 | print(map); // {a: 1, b: 2} (i.e. the map stays unchanged) 26 | 27 | // Transiency: 28 | 29 | final vec = new PVec.from(["a", "b"]); 30 | 31 | print(vec.push("c")); // (a, b, c) 32 | print(vec.push("d")); // (a, b, d) 33 | 34 | var temp = vec.asTransient(); 35 | temp.doPush("c"); 36 | temp.doPush("d"); 37 | temp[0] = "A"; 38 | final vec2 = temp.asPersistent(); 39 | print(vec2); // (A, b, c, d) 40 | 41 | } 42 | -------------------------------------------------------------------------------- /lib/src/pair.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, VacuumLabs. 2 | // Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code 3 | // is governed by a BSD-style license that can be found in the LICENSE file. 4 | 5 | // Authors are listed in the AUTHORS file 6 | 7 | part of persistent; 8 | 9 | 10 | /** 11 | * A pair of two values encapsulated to the single object. 12 | * 13 | * Mostly used to represent key-value pairs in dictionaries. 14 | */ 15 | class Pair { 16 | 17 | /// fst value 18 | final A fst; 19 | 20 | /// Second value 21 | final B snd; 22 | 23 | /** 24 | * Creates a new pair of given values 25 | */ 26 | Pair(this.fst, this.snd); 27 | 28 | /** 29 | * The equality operator. 30 | * 31 | * Two pairs are equal if and only if both their fst and second 32 | * values are equal. 33 | */ 34 | bool operator ==(other) { 35 | return (other is Pair) 36 | && fst == other.fst 37 | && snd == other.snd; 38 | } 39 | 40 | operator [](int pos) { 41 | switch (pos) { 42 | case 0: return fst; 43 | case 1: return snd; 44 | default: throw new RangeError("Pair does not contain value on position $pos"); 45 | } 46 | } 47 | 48 | A get key => fst; 49 | 50 | B get value => snd; 51 | 52 | int get hashCode => fst.hashCode + 31 * snd.hashCode; 53 | 54 | String toString() => "Pair($fst, $snd)"; 55 | } 56 | -------------------------------------------------------------------------------- /benchmark/map_memory/map_memory.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, VacuumLabs. 2 | // Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code 3 | // is governed by a BSD-style license that can be found in the LICENSE file. 4 | 5 | // Authors are listed in the AUTHORS file 6 | 7 | library map_memory; 8 | 9 | import 'package:vacuum_persistent/persistent.dart'; 10 | import 'dart:collection'; 11 | 12 | Map template = {}; 13 | 14 | // here we will store the data to prevent it from garbage collection 15 | List data = []; 16 | 17 | var creators = { 18 | 19 | "persistent": () => new PMap.fromMap(template), 20 | 21 | "transient": (){ 22 | var res = new TMap(); 23 | template.forEach((k, v) => res.doAssoc(k, v)); 24 | return res; 25 | }, 26 | 27 | "map": () => new Map.from(template), 28 | "hashmap": () => new HashMap.from(template) 29 | }; 30 | 31 | void run(int template_size, String mode) { 32 | 33 | for (int i = 0; i < template_size; i++) { 34 | template["$i".padLeft(8)] = "$i".padRight(8); 35 | } 36 | 37 | int allocated = 0; 38 | for(bool go = true; go; allocated++){ 39 | try{ 40 | go = false; 41 | var a = creators[mode](); 42 | data.add(a); 43 | go = true; 44 | print(1073741824.0 / allocated / template_size); 45 | } catch(e) { 46 | data = null; 47 | } 48 | } 49 | } 50 | 51 | main(List args){ 52 | 53 | run(int.parse(args[0]), args[1]); 54 | } 55 | -------------------------------------------------------------------------------- /test/reference_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, VaccumLabs. 2 | // Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code 3 | // is governed by a BSD-style license that can be found in the LICENSE file. 4 | 5 | // Authors are listed in the AUTHORS file 6 | 7 | library reference_test; 8 | 9 | import 'package:vacuum_persistent/persistent.dart'; 10 | import 'package:unittest/unittest.dart'; 11 | import 'dart:async'; 12 | 13 | main() { 14 | run(); 15 | } 16 | 17 | run() { 18 | test('Creation of Reference', () { 19 | var r = new Reference(); 20 | expect(r.deref(), null); 21 | r = new Reference(10); 22 | expect(r.deref(), 10); 23 | }); 24 | 25 | test('Changing value of ref', () { 26 | var r = new Reference(15); 27 | r.update((_) => 20); 28 | expect(r.deref(), 20); 29 | r.update((_) => 30); 30 | expect(r.deref(), 30); 31 | }); 32 | 33 | test('After change of value notification come.', () { 34 | var r = new Reference(10); 35 | expect(r.onChange.first, completion(per({'oldVal': 10, 'newVal': 15}))); 36 | r.update((_)=> 15); 37 | }); 38 | 39 | test('After change of value notification come.', () { 40 | var r = new Reference(10); 41 | expect(r.onChangeSync.first, completion(per({'oldVal': 10, 'newVal': 15}))); 42 | r.update((_) => 15); 43 | expect(r.onChangeSync.first, completion(per({'oldVal': 15, 'newVal': 20}))); 44 | r.update((_) => 20); 45 | }); 46 | } 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2014, VacuumLabs. 2 | Copyright 2012, Google Inc. All rights reserved. Redistribution and 3 | use in source and binary forms, with or without modification, are 4 | permitted provided that the following conditions are met: 5 | * Redistributions of source code must retain the above copyright 6 | notice, this list of conditions and the following disclaimer. 7 | * Redistributions in binary form must reproduce the above 8 | copyright notice, this list of conditions and the following 9 | disclaimer in the documentation and/or other materials provided 10 | with the distribution. 11 | * Neither the name of Google Inc. nor the names of its 12 | contributors may be used to endorse or promote products derived 13 | from this software without specific prior written permission. 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 15 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 16 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 17 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 18 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 19 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 20 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 21 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 22 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /benchmark/vector_speed/interface_impl.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, VacuumLabs. 2 | // Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code 3 | // is governed by a BSD-style license that can be found in the LICENSE file. 4 | 5 | // Authors are listed in the AUTHORS file 6 | 7 | part of vector_speed; 8 | 9 | class PersistentVectorInterface 10 | extends EncapsulatingInterface>{ 11 | 12 | create() => object = new PVec(); 13 | 14 | push(E value) => 15 | object = object.push(value); 16 | 17 | get(int index) => object.get(index); 18 | 19 | set(int index, E value) => object = object.set(index, value); 20 | 21 | pop() => object = object.pop(); 22 | 23 | _copy() => object; 24 | } 25 | 26 | 27 | class TransientVectorInterface 28 | extends EncapsulatingInterface>{ 29 | 30 | create() => object = new PVec().asTransient(); 31 | 32 | push(E value) => 33 | object.doPush(value); 34 | 35 | get(int index) => object.get(index); 36 | 37 | set(int index, E value) => object.doSet(index, value); 38 | 39 | pop() => object.doPop(); 40 | 41 | _copy(){ 42 | return new PVec.from(object).asTransient(); 43 | } 44 | } 45 | 46 | 47 | class ListInterface 48 | extends EncapsulatingInterface>{ 49 | 50 | create() => object = []; 51 | 52 | push(E value) => 53 | object.add(value); 54 | 55 | get(int index) => object[index]; 56 | 57 | set(int index, E value) => object[index] = value; 58 | 59 | pop() => object.removeLast(); 60 | 61 | _copy(){ 62 | return new List.from(object); 63 | } 64 | } 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /lib/src/reference.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, VacuumLabs. 2 | // Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code 3 | // is governed by a BSD-style license that can be found in the LICENSE file. 4 | 5 | // Authors are listed in the AUTHORS file 6 | 7 | part of persistent; 8 | 9 | class Reference { 10 | V _value; 11 | StreamController _onChangeController, _onChangeSyncController; 12 | 13 | Reference([defVal = null]) : _value = defVal; 14 | 15 | Stream get onChange { 16 | if (_onChangeController == null) _onChangeController = new StreamController.broadcast(sync: false); 17 | return _onChangeController.stream; 18 | } 19 | 20 | Stream get onChangeSync { 21 | if (_onChangeSyncController == null) _onChangeSyncController = new StreamController.broadcast(sync: true); 22 | return _onChangeSyncController.stream; 23 | } 24 | 25 | deref() => _value; 26 | 27 | /** Change value of [Reference] accept [f] as [Function] or [f] as value. 28 | */ 29 | update(f) => _setValue(f(deref())); 30 | 31 | 32 | _setValue(val) { 33 | var change = per({'oldVal': _value, 'newVal': val}); 34 | _value = val; 35 | if (_onChangeSyncController != null) _onChangeSyncController.add(change); 36 | if (_onChangeController != null) _onChangeController.add(change); 37 | } 38 | 39 | Cursor get cursor => new Cursor(this, []); 40 | 41 | dispose() { 42 | if (_onChangeSyncController != null) _onChangeSyncController.close(); 43 | if (_onChangeController != null) _onChangeSyncController.close(); 44 | } 45 | 46 | String toString() => 'Ref(${_value.toString()})'; 47 | } 48 | -------------------------------------------------------------------------------- /benchmark/mori/memory.js: -------------------------------------------------------------------------------- 1 | var m = require('mori'); 2 | 3 | var template_map = {}; 4 | var template_vector = []; 5 | 6 | // here we will store the data to prevent it from garbage collection 7 | data = []; 8 | 9 | // JSON.stringify & parse is not good way of cloning since it creates copies of the values; we write 10 | // custom (shallow) clone functions instead 11 | 12 | function clone_list(template){ 13 | var res = []; 14 | for (var i=0; i> build/vector_memory.js 28 | cat build/tmp.js >> build/vector_memory.js 29 | echo "$$RUNNER" >> build/vector_memory.js 30 | 31 | 32 | clean: 33 | rm -rf build 34 | 35 | 36 | run: build/vector_memory.js 37 | 38 | @echo "$$INTRO" 39 | 40 | @echo -n "NodeJS list: " 41 | @ (! node --max-old-space-size=1024 build/vector_memory.js ${template_size} list > build/temp.out) 2>/dev/null 42 | @tail -1 build/temp.out 43 | 44 | @echo -n "DartVM persistent: " 45 | @dart --old_gen_heap_size=1024 vector_memory.dart ${template_size} persistent > build/temp.out 2>/dev/null 46 | @tail -1 build/temp.out 47 | 48 | @echo -n "DartVM list: " 49 | @dart --old_gen_heap_size=1024 vector_memory.dart ${template_size} list > build/temp.out 2>/dev/null 50 | @tail -1 build/temp.out 51 | 52 | @echo -n "NodeJS persistent: " 53 | @ (! node --max-old-space-size=1024 build/vector_memory.js ${template_size} persistent > build/temp.out) 2>/dev/null 54 | @tail -1 build/temp.out 55 | 56 | 57 | -------------------------------------------------------------------------------- /benchmark/vector_speed/benchmarks.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, VacuumLabs. 2 | // Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code 3 | // is governed by a BSD-style license that can be found in the LICENSE file. 4 | 5 | // Authors are listed in the AUTHORS file 6 | 7 | part of vector_speed; 8 | 9 | class WriteBenchmark extends BenchmarkBase{ 10 | 11 | final int size; 12 | final BenchmarkInterface object; 13 | 14 | 15 | WriteBenchmark(size, object, name): 16 | size = size, 17 | object = object, 18 | super("Writing $name($size)"); 19 | 20 | 21 | void run(){ 22 | 23 | object.create(); 24 | 25 | for (int i = 0; i < size; i++) { 26 | object.push("${i}".padLeft(8,"0")); 27 | } 28 | 29 | for (int i = size-1; i >= 0; i--) { 30 | object.set(i, "${i}".padLeft(8,"_")); 31 | } 32 | 33 | for (int i = 0; i < size; i++) { 34 | object.set(i, "${i}".padLeft(8,"x")); 35 | } 36 | 37 | object.save(); 38 | for (int i = size-1 ; i >= 0; i--) { 39 | object.pop(); 40 | } 41 | 42 | object.restore(); 43 | for (int i = 0; i < size; i++) { 44 | object.pop(); 45 | } 46 | } 47 | } 48 | 49 | class ReadBenchmark extends BenchmarkBase{ 50 | 51 | final int size; 52 | final BenchmarkInterface object; 53 | 54 | 55 | ReadBenchmark(size, object, name): 56 | size = size, 57 | object = object, 58 | super("Reading $name($size)"); 59 | 60 | void setup(){ 61 | 62 | object.create(); 63 | 64 | for (int i = 0; i < size; i++) { 65 | object.push("${i}".padLeft(8,"0")); 66 | } 67 | } 68 | 69 | void run(){ 70 | 71 | for (int i = size-1; i >= 0; i--) { 72 | object.get(i); 73 | } 74 | 75 | for (int i = 0; i < size; i++) { 76 | object.get(i); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /benchmark/map_memory/makefile: -------------------------------------------------------------------------------- 1 | template_size = 1000 2 | 3 | define RUNNER 4 | function dartMainRunner(main, args){ 5 | main(process.argv.slice(2)); 6 | } 7 | endef 8 | 9 | export RUNNER 10 | 11 | define INTRO 12 | - - - - - - - - 13 | Using (flat) map of size ${template_size}. Benchmark internally counts number of objects that fit 14 | into 1GB heap space. Output is in bytes per one key->value storage place. Instances of keys and 15 | values are shared between multiple copies of the structure we create, therefore their size do not 16 | affect the result (much). 17 | - - - - - - - - 18 | endef 19 | 20 | export INTRO 21 | 22 | 23 | all: clean run 24 | 25 | 26 | build/map_memory.js: map_memory.dart 27 | mkdir -p build 28 | dart2js -o build/tmp.js map_memory.dart 29 | cat ../preamble/d8.js >> build/map_memory.js 30 | cat build/tmp.js >> build/map_memory.js 31 | echo "$$RUNNER" >> build/map_memory.js 32 | 33 | clean: 34 | rm -rf build 35 | 36 | 37 | run: build/map_memory.js 38 | 39 | @echo "$$INTRO" 40 | 41 | # @echo -n "DartVM persistent: " 42 | # @dart --old_gen_heap_size=1024 map_memory.dart ${template_size} persistent > build/temp.out 2>/dev/null 43 | # @tail -1 build/temp.out 44 | # 45 | # @echo -n "DartVM map: " 46 | # @dart --old_gen_heap_size=1024 map_memory.dart ${template_size} map > build/temp.out 2>/dev/null 47 | # @tail -1 build/temp.out 48 | 49 | @echo -n "NodeJS persistent: " 50 | @ (! node --max-old-space-size=1024 build/map_memory.js ${template_size} persistent > build/temp.out) 2>/dev/null 51 | @tail -1 build/temp.out 52 | 53 | @echo -n "NodeJS map: " 54 | @ (! node --max-old-space-size=1024 build/map_memory.js ${template_size} map > build/temp.out) 2>/dev/null 55 | @tail -1 build/temp.out 56 | 57 | @echo -n "NodeJS hashmap: " 58 | @ (! node --max-old-space-size=1024 build/map_memory.js ${template_size} hashmap > build/temp.out) 2>/dev/null 59 | @tail -1 build/temp.out 60 | 61 | rm -f build/temp.out 62 | 63 | -------------------------------------------------------------------------------- /benchmark/map_speed/benchmarks.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, VacuumLabs. 2 | // Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code 3 | // is governed by a BSD-style license that can be found in the LICENSE file. 4 | 5 | // Authors are listed in the AUTHORS file 6 | 7 | part of map_bench; 8 | 9 | class WriteBenchmark extends BenchmarkBase{ 10 | 11 | final Map sample; 12 | BenchmarkInterface object; 13 | final dynamic factory; 14 | 15 | void setup(){ 16 | object = factory(); 17 | } 18 | 19 | WriteBenchmark(this.sample, this.factory):super('Writing'); 20 | 21 | void run(){ 22 | for (var size in this.sample.keys) { 23 | for (var j=0; j sample; 42 | Map> objects = new Map(); 43 | final dynamic factory; 44 | 45 | ReadBenchmark(this.sample, this.factory):super('Reading'); 46 | 47 | void setup(){ 48 | this.sample.forEach((size, count){ 49 | objects[size] = []; 50 | for (int j=0; j= 0; i--) { 65 | object.get(i*i); 66 | } 67 | } 68 | } 69 | } 70 | } 71 | 72 | 73 | -------------------------------------------------------------------------------- /test/cursor_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, VaccumLabs. 2 | // Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code 3 | // is governed by a BSD-style license that can be found in the LICENSE file. 4 | 5 | // Authors are listed in the AUTHORS file 6 | 7 | library cursor_test; 8 | 9 | import 'package:vacuum_persistent/persistent.dart'; 10 | import 'package:unittest/unittest.dart'; 11 | 12 | main() { 13 | run(); 14 | } 15 | 16 | eqPer(x) => equals(persist(x)); 17 | 18 | run() { 19 | Reference r; 20 | Cursor c; 21 | setUp(() { 22 | r = new Reference(per({'a': {'b' : 10}})); 23 | c = r.cursor; 24 | }); 25 | 26 | test('Create top cursor from reference', () { 27 | expect(c.deref(), equals(r.deref())); 28 | }); 29 | 30 | test('Create cursor with path from reference', () { 31 | var c = new Cursor(r, ['a']); 32 | expect(c.deref(), eqPer({'b' : 10})); 33 | }); 34 | 35 | test('Create cursor from existing cursor one level', () { 36 | var c1 = c['a']; 37 | expect(c1.deref(), eqPer({'b': 10})); 38 | }); 39 | 40 | test('Create cursor from existing cursor more level', () { 41 | var c1 = c['a']['b']; 42 | expect(c1.deref(), eqPer(10)); 43 | }); 44 | 45 | test('Derref cursor works when ref value changed', () { 46 | var c1 = c['a']; 47 | expect(c.deref(), eqPer({'a': {'b' : 10}})); 48 | expect(c1.deref(), eqPer({'b' : 10})); 49 | 50 | r.update((_)=> per({'c': 10, 'a': 15})); 51 | expect(c.deref(), eqPer({'c': 10, 'a': 15})); 52 | expect(c1.deref(), eqPer(15)); 53 | }); 54 | 55 | test('Cursor with invalid path work', () { 56 | var c1 = c['c']['d']; 57 | expect(() => c1.deref(), throws); 58 | expect(c1.deref(null), null); 59 | 60 | r.update((_) => per({'c': {'d': {'e': 15}}})); 61 | expect(c1.deref(), per({'e': 15})); 62 | }); 63 | 64 | test('Update on cursor work', () { 65 | var c1 = c['a']; 66 | c1.update((x) => assoc(x, 'g', 17)); 67 | expect(r.deref(), eqPer({'a': {'b' : 10, 'g': 17}})); 68 | }); 69 | 70 | test('Update on invalid cursor work', () { 71 | var c2 = c['a']['h']; 72 | c2.update(([x]) => 15); 73 | expect(r.deref(), eqPer({'a': {'b' : 10, 'h': 15}})); 74 | 75 | var c3 = c['a']['j']['k']; 76 | expect(() => c3.update(([x]) => 15), throws); 77 | }); 78 | } 79 | -------------------------------------------------------------------------------- /benchmark/map_speed/map_speed.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, VacuumLabs. 2 | // Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code 3 | // is governed by a BSD-style license that can be found in the LICENSE file. 4 | 5 | // Authors are listed in the AUTHORS file 6 | 7 | library map_bench; 8 | 9 | import 'package:vacuum_persistent/persistent.dart'; 10 | import 'package:benchmark_harness/benchmark_harness.dart'; 11 | import 'dart:math'; 12 | 13 | part 'benchmarks.dart'; 14 | part 'interface.dart'; 15 | part 'interface_impl.dart'; 16 | 17 | Map interfaces = { 18 | "PersistentMap": () => new PersistentMapInterface(), 19 | "TransientMap": () => new TransientMapInterface(), 20 | "Map": () => new StandardMapInterface(), 21 | }; 22 | 23 | int times = 4; 24 | 25 | void main() { 26 | 27 | var config = [ 28 | {'name': 'Write', 29 | 'creator': ((sample, factory) => (new WriteBenchmark(sample, factory))), 30 | // 'sizes': [{10000: 1}, ], 31 | 'sizes': [{500:60, 1000: 30, 1500: 20, 3000: 10}], 32 | }, 33 | { 34 | 'name': 'Read', 35 | 'creator': ((sample, factory) => (new ReadBenchmark(sample, factory))), 36 | // 'sizes': [{10000: 1}, ], 37 | 'sizes': [{500:60, 1000: 30, 1500: 20, 3000: 10}], 38 | } 39 | ]; 40 | var result = {}; 41 | config.forEach((conf){ 42 | String mode = conf['name']; 43 | var creator = conf['creator']; 44 | for (Map sample in conf['sizes']) { 45 | var res = {}; 46 | var dev = {}; 47 | interfaces.forEach((k,v){ 48 | res[k] = 0; 49 | dev[k] = 0; 50 | }); 51 | for (int i=0; i x+3); 42 | print(map3.delete("b")); // {c: 7} 43 | print(map3.delete("a", missingOk: true)); // {b: 3, c: 7} 44 | 45 | print(map1); // {a: 1, b: 2} 46 | print(map2); // {b: 3, c: 4} 47 | print(map3); // {b: 3, c: 7} 48 | 49 | // Transiency: 50 | 51 | final vector1 = new PVec.from(["x", "y"]); 52 | 53 | print(vector1.push("z")); // (x, y, z) 54 | print(vector1.push("q")); // (x, y, q) 55 | 56 | var temp = vector1.asTransient(); 57 | temp.doPush("z"); 58 | temp.doPush("q"); 59 | temp[1] = "Y"; 60 | final vector2 = temp.asPersistent(); 61 | 62 | final vector3 = vector2.withTransient((TransientVector v){ 63 | v.doSet(2, "Z"); 64 | v.doPop(); 65 | v[0] = "X"; 66 | }); 67 | 68 | print(vector1); // (x, y) 69 | print(vector2); // (x, Y, z, q) 70 | print(vector3); // (X, Y, Z) 71 | 72 | // Features 73 | 74 | print(map1.toList()); // [Pair(a, 1), Pair(b, 2)] 75 | 76 | final set1 = new PersistentSet.from(["a", "b"]); 77 | final set2 = new PersistentSet.from([1, 2, 3]); 78 | print((set1 * set2).toList()); 79 | // [Pair(a, 1), Pair(a, 2), Pair(a, 3), Pair(b, 1), Pair(b, 2), Pair(b, 3)] 80 | 81 | } 82 | -------------------------------------------------------------------------------- /test/linked_list_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, VaccumLabs. 2 | // Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code 3 | // is governed by a BSD-style license that can be found in the LICENSE file. 4 | 5 | // Authors are listed in the AUTHORS file 6 | 7 | library linked_list_test; 8 | 9 | import 'package:vacuum_persistent/persistent.dart'; 10 | import 'package:unittest/unittest.dart'; 11 | 12 | main() { 13 | run(); 14 | } 15 | 16 | N() => new Nil(); 17 | C(e,t) => new Cons(e,t); 18 | 19 | run() { 20 | 21 | group("LinkedList", (){ 22 | test("isNil", (){ 23 | expect( N().isNil, isTrue); 24 | expect( C(0,N()).isNil, isFalse); 25 | }); 26 | test("isCons", (){ 27 | expect( N().isCons, isFalse); 28 | expect( C(0,N()).isCons, isTrue); 29 | }); 30 | test("asNil", (){ 31 | expect( N().asNil, equals(N())); 32 | expect( C(0,N()).asNil, equals(null)); 33 | }); 34 | test("asCons", (){ 35 | expect( N().asCons, equals(null)); 36 | expect( C(0,N()).asCons, equals(C(0,N()))); 37 | }); 38 | test("length", (){ 39 | expect( N().length, equals(0)); 40 | expect( C(0,N()).length, equals(1)); 41 | expect( C(1,C(0,N())).length, equals(2)); 42 | expect( C(2,C(1,C(0,N()))).length, equals(3)); 43 | expect( C(3,C(2,C(1,C(0,N())))).length, equals(4)); 44 | 45 | }); 46 | test("iterator", (){ 47 | // Indirectly using `toList`. 48 | expect( N().toList(), equals([])); 49 | expect( C(0,N()).toList(), equals([0])); 50 | expect( C(1,C(0,N())).toList(), equals([1,0])); 51 | expect( C(2,C(1,C(0,N()))).toList(), equals([2,1,0])); 52 | expect( C(3,C(2,C(1,C(0,N())))).toList(), equals([3,2,1,0])); 53 | }); 54 | test("==", (){ 55 | expect( N() == N(), isTrue); 56 | expect( C(0,N()) == C(0,N()), isTrue); 57 | expect( C(1,C(0,N())) == C(1,C(0,N())), isTrue); 58 | 59 | expect( N() == C(0,N()), isFalse); 60 | expect( C(0,N()) == N(), isFalse); 61 | expect( C(0,N()) == C(0,C(1,N())), isFalse); 62 | expect( C(1,N()) == C(0,N()), isFalse); 63 | }); 64 | }); 65 | 66 | group("Cons", (){ 67 | test("elem", (){ 68 | expect( C(0,N()).elem, equals(0)); 69 | }); 70 | test("tail", (){ 71 | expect( C(0,N()).tail, equals(N())); 72 | }); 73 | }); 74 | 75 | group("LinkedListBuilder", (){ 76 | test("basic", (){ 77 | LinkedListBuilder b = new LinkedListBuilder(); 78 | b.add(0); 79 | b.addAll([1,2]); 80 | b.add(3); 81 | b.addAll([]); 82 | expect(b.build(), equals(C(0,C(1,C(2,C(3,N())))))); 83 | expect(b.build(C(4,N())), equals(C(0,C(1,C(2,C(3,C(4,N()))))))); 84 | }); 85 | }); 86 | } 87 | -------------------------------------------------------------------------------- /benchmark/mori_speed/mori_speed.js: -------------------------------------------------------------------------------- 1 | var Benchmark = require('benchmark'); 2 | var m = require('mori'); 3 | 4 | var samples = [ 5 | {"500":60, "1000":30, "1500":20, "3000":10} 6 | ] 7 | 8 | var times = 10; 9 | 10 | // Read Map 11 | 12 | var read_map_suite = new Benchmark.Suite; 13 | 14 | for (var sample_id = 0; sample_id < samples.length; sample_id++){ 15 | 16 | var sample = samples[sample_id]; 17 | var objects = {} 18 | 19 | for(var size_name in sample){ 20 | var size = Number(size_name) 21 | var count = sample[size_name] 22 | 23 | var prototype = [] 24 | for (var i = 0; i < size; i++){ 25 | prototype.push(String(i*i)) 26 | prototype.push("foo"); 27 | } 28 | 29 | subobjects = []; 30 | for (var i = 0; i < count; i++){ 31 | subobjects.push(m.hashMap.apply(null, prototype)); 32 | } 33 | 34 | objects[size_name] = subobjects; 35 | } 36 | 37 | read_map_suite.add('Map Read on '+JSON.stringify(sample), function() { 38 | for(var size_name in sample){ 39 | var size = Number(size_name) 40 | 41 | objects[size_name].forEach(function(map){ 42 | for (var i = size; i >= 0; i--) { 43 | // somefn.f2, .f1, .f3, etc were added in mori 0.3 to speed-up method dispatching. 44 | // .f2 means version of function with arity 2, etc 45 | m.get.f2(map, i*i); 46 | } 47 | for (var i = 0; i <= size; i++) { 48 | m.get.f2(map, i*i); 49 | } 50 | }); 51 | 52 | } 53 | }, {"minSamples": times}); 54 | 55 | } 56 | 57 | 58 | 59 | // Write Map 60 | 61 | var write_map_suite = new Benchmark.Suite; 62 | 63 | for (var sample_id = 0; sample_id < samples.length; sample_id++){ 64 | 65 | var sample = samples[sample_id]; 66 | 67 | write_map_suite.add('Map Write on '+JSON.stringify(sample), function() { 68 | for(var size_name in sample){ 69 | var size = Number(size_name); 70 | var count = sample[size_name]; 71 | for (var j = 0; j < count; j++){ 72 | var map = m.hashMap(); 73 | vals = ["foo", "bar", "baz", "woo", "hoo", "goo", "wat"]; 74 | for(var ival in vals){ 75 | var val = vals[ival]; 76 | for (var i = 0; i < size; i++) { 77 | map = m.assoc(map, i*i, val); 78 | } 79 | } 80 | for (var i = 0; i < size; i++) { 81 | map = m.dissoc(map, i*i); 82 | } 83 | } 84 | } 85 | }, {"minSamples": times}); 86 | 87 | } 88 | 89 | 90 | 91 | 92 | // Run 93 | 94 | var logger = function(event) { 95 | var error = event.target.error, 96 | time = (1/event.target.hz * 1000000); 97 | stats = event.target.stats, 98 | size = stats.sample.length, 99 | result = event.target.name; 100 | 101 | if (error) { 102 | result += ': ' + join(error); 103 | } else { 104 | result += ': ' + time.toFixed(0) + ' us +/-' + 105 | stats.rme.toFixed(2) + '% (' + size + ' runs sampled)'; 106 | } 107 | console.log(result); 108 | } 109 | 110 | read_map_suite.on('cycle', logger).run(); 111 | write_map_suite.on('cycle', logger).run(); 112 | -------------------------------------------------------------------------------- /benchmark/map_speed/interface_impl.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, VacuumLabs. 2 | // Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code 3 | // is governed by a BSD-style license that can be found in the LICENSE file. 4 | 5 | // Authors are listed in the AUTHORS file 6 | 7 | part of map_bench; 8 | 9 | class PersistentMapInterface 10 | extends EncapsulatingInterface>{ 11 | 12 | PersistentMapInterface(){ 13 | this.object = new PMap(); 14 | } 15 | 16 | assoc(K key, V value) => 17 | object = object.assoc(key, value); 18 | 19 | get(K key) => object.get(key, null); 20 | 21 | delete(K key) => object = object.delete(key); 22 | 23 | _copy() => object; 24 | } 25 | 26 | 27 | class TransientMapInterface 28 | extends EncapsulatingInterface>{ 29 | 30 | TransientMapInterface(){ 31 | this.object = new PMap().asTransient(); 32 | } 33 | 34 | assoc(K key, V value) => 35 | object.doAssoc(key, value); 36 | 37 | get(K key) => object.get(key, null); 38 | 39 | delete(K key) => object.doDelete(key); 40 | 41 | _copy(){ 42 | var copy = object.asPersistent(); 43 | object = copy.asTransient(); 44 | return copy.asTransient(); 45 | } 46 | } 47 | 48 | 49 | class StandardMapInterface 50 | extends EncapsulatingInterface>{ 51 | 52 | StandardMapInterface(){ 53 | object = new Map(); 54 | } 55 | 56 | assoc(K key, V value) => 57 | object[key] = value; 58 | 59 | get(K key) => object[key]; 60 | 61 | delete(K key) => object.remove(key); 62 | 63 | _copy() => new Map.from(object); 64 | } 65 | 66 | 67 | class CopyMapInterface 68 | extends EncapsulatingInterface>{ 69 | 70 | create() => object = new Map(); 71 | 72 | assoc(K key, V value){ 73 | object = new Map.from(object); 74 | object[key] = value; 75 | } 76 | 77 | get(K key) => object[key]; 78 | 79 | delete(K key){ 80 | object = new Map.from(object); 81 | object.remove(key); 82 | } 83 | 84 | _copy() => object; 85 | } 86 | 87 | 88 | class LinkedListInterface 89 | extends EncapsulatingInterface>>{ 90 | 91 | create() => object = new Nil>(); 92 | 93 | assoc(K key, V value){ 94 | LinkedListBuilder> builder = 95 | new LinkedListBuilder>(); 96 | LinkedList> it = object; 97 | while (it.isCons) { 98 | Cons> cons = it.asCons; 99 | Pair elem = cons.elem; 100 | if (elem.fst == key) { 101 | builder.add(new Pair(key, value)); 102 | return builder.build(cons.tail); 103 | } 104 | builder.add(elem); 105 | it = cons.tail; 106 | } 107 | builder.add(new Pair(key, value)); 108 | object = builder.build(); 109 | } 110 | 111 | get(K key){ 112 | LinkedList> it = object; 113 | while (it.isCons) { 114 | Cons> cons = it.asCons; 115 | Pair elem = cons.elem; 116 | if (elem.fst == key) return; 117 | it = cons.tail; 118 | } 119 | } 120 | 121 | delete(K key){ 122 | object = object.strictWhere((p) => p.fst != key); 123 | } 124 | 125 | _copy() => object; 126 | } 127 | 128 | -------------------------------------------------------------------------------- /transients.md: -------------------------------------------------------------------------------- 1 | ## Working with Transients 2 | 3 | Persistent data structure can 'unfreeze' into *Transient* structure (TMap, TVec, ..), which is mutable. The purpose of this is to gain some speedup while still working with Persistents. The typical workflow is as follows: 4 | 5 | 1. TMap trans = pers.asTransient(); 6 | 2. do a lot of mutations on trans 7 | 3. Persistent result = trans.asPersistent(); 8 | 9 | There are several notable things here: 10 | - When working with transients, methods like `assoc` or `delete` are no longer there. Instead of these, use `doAssoc`, `doDelete`, etc. These methods return void and mutate the structure inplace. 11 | - once `.asPersistent` is called, you cannot modify transient anymore. If you try doing it, you'll get an exception. 12 | - conversion Persistent -> Transient and vice-versa is O(1), which means fast, in this case, really fast. 13 | - everything is safe, i.e. basic contract 'there is no way, how to modify Persistent data structure' still holds 14 | - since there is some repeating pattern in steps 1-3, `PersistentStructure.withTransient(modifier)` helper method exists. 15 | 16 | ### Equality and hash 17 | 18 | Two persistent structures are equal if they carry the equal data. 19 | This allows them to be used as map keys - the key is the data in the context, 20 | not the object itself. 21 | 22 | Two transient structures are equal in the standard meaning of a word - if they are the same object. 23 | 24 | The hash code is consistent with the equality operator. 25 | 26 | ## Example 27 | 28 | import 'package:persistent/persistent.dart'; 29 | 30 | main() { 31 | 32 | // Persistency: 33 | 34 | PMap map1 = new PMap.from({"a":1, "b":2}); 35 | PMap map2 = new PMap.from({"b":3, "c":4}); 36 | 37 | print(map1["a"]); // 1 38 | print(map1.lookup("b")); // 2 39 | print(map1.lookup("c", orElse: ()=>":(")); // :( 40 | 41 | print(map1.insert("c", 3)); // {a: 1, b: 2, c: 3} 42 | print(map1.insert("d", 4)); // {a: 1, b: 2, d: 4} 43 | 44 | final map3 = map2.insert("c", 3, (x,y) => x+y); 45 | print(map3.delete("b")); // {c: 7} 46 | print(map3.delete("a", safe: true)); // {b: 3, c: 7} 47 | 48 | print(map1); // {a: 1, b: 2} 49 | print(map2); // {b: 3, c: 4} 50 | print(map3); // {b: 3, c: 7} 51 | 52 | // Transiency: 53 | 54 | final vector1 = new PersistentVector.from(["x", "y"]); 55 | 56 | print(vector1.push("z")); // (x, y, z) 57 | print(vector1.push("q")); // (x, y, q) 58 | 59 | var temp = vector1.asTransient(); 60 | temp.doPush("z"); 61 | temp.doPush("q"); 62 | temp[1] = "Y"; 63 | final vector2 = temp.asPersistent(); 64 | 65 | final vector3 = vector2.withTransient((TransientVector v){ 66 | v.doSet(2, "Z"); 67 | v.doPop(); 68 | v[0] = "X"; 69 | }); 70 | 71 | print(vector1); // (x, y) 72 | print(vector2); // (x, Y, z, q) 73 | print(vector3); // (X, Y, Z) 74 | 75 | // Features 76 | 77 | print(map1.toList()); // [Pair(a, 1), Pair(b, 2)] 78 | 79 | final set1 = new PersistentSet.from(["a", "b"]); 80 | final set2 = new PersistentSet.from([1, 2, 3]); 81 | print((set1 * set2).toList()); 82 | // [Pair(a, 2), Pair(a, 1), Pair(b, 3), Pair(b, 2), Pair(b, 1), Pair(a, 3)] 83 | 84 | } 85 | -------------------------------------------------------------------------------- /technical.md: -------------------------------------------------------------------------------- 1 | # Technical overview 2 | 3 | The implementation of Persistent Vector is very similar to the one found in 4 | Facebook's [immutable.js] (https://github.com/facebook/immutable-js). We show almost no invention 5 | here. The rest of the document describes our design of Persistent Map which is more unusual and needs 6 | more explanation. 7 | 8 | 9 | ## PMap technical overview 10 | 11 | The implementation is a version of HAMT, be sure you understand the basic concepts before reading 12 | further. Good places to start are [wikipedia] (http://en.wikipedia.org/wiki/Hash_array_mapped_trie) 13 | or this [blog post] 14 | (http://blog.higher-order.net/2009/09/08/understanding-clojures-persistenthashmap-deftwice.html). 15 | The following text explains issues, that are specific for our implementation. 16 | 17 | The whole HAMT consists of two types of nodes: Node and Leaf (they are called _Node and _Leaf in the 18 | code). 19 | 20 | Node is typical HAMT inner node. It's branching factor is set to 16 (this may change) currently this 21 | gets us best results in the benchmarks. Note that Node implements PMap interface. 22 | 23 | Leaf can hold several key-value pairs. These are stored in a simple List such as: 24 | [hash1, ke1, value1, hash2, key2, value2, etc..] 25 | if the leaf grows big (currently, > 48 such h,k,v triplets), it is split up to several Nodes. Similarly, 26 | if Node stores only few k,v pairs (in all its nodes) it is compacted to one single Leaf (threshold 27 | for this is currently set to < 32 triplets) 28 | 29 | Few things to note here: 30 | 31 | - In the tree, h,k,v triplets are stored in a way to guarantee the following property: if iterating 32 | through one Node by inorder (i.e. you are recursively visiting its children from the 0th to the 33 | 15-th), you enumerate h,k,v triplets sorted by hash value. This may look unimportant on the first 34 | glance, but it simplifies several things; for example comparing Leaf with Node on equality, or doing intersection 35 | with Leaf and Node gets easier. For this purpose, we do the following: 36 | 37 | - In a single Leaf, h,k,v triplets are sorted by the hash. This allows us to binsearch for the 38 | correct value, when doing lookup. 39 | 40 | - In the put / lookup process, we consume the key hash from the first digits (not from the last, 41 | as usual). Note that hashes of small objects (especially, small ints) tend to have just zeros 42 | in the leading places. To overcome this problem, we work with mangled hash, which has enough 43 | entropy also in the first digits (check out _mangeHash function). 44 | 45 | - In the Node implementation we're not compacting the array of children. Typically, to save memory, HAMT 46 | implementation stores only not-null children. Such implementations then use bitmask to correctly 47 | determine, what the proper indexes of individual (not-null) children would be (if the nulls were 48 | there). Such trick is neat, but it costs time, and moreover, we don't need it. Why? Because we 49 | store up to 48 values in a single Leaf. This means, when the Leaf gets expanded to a proper Node, 50 | most of its children will be not null. (Exercise: you randomly pick 48 numbers from 51 | interval 0,15 inclusive. What is the expectation for the count of numbers not picked at least once?) 52 | 53 | - Node is a strange class. It serves for two purposes (which is probably not the cleanest design): 54 | it implements all PMap methods (in fact, when you construct new PMap, what you got is Node) and it 55 | implements low-level method for HAMT manipulation. Moreover, PMap methods (such as assoc) can be 56 | called only on the root Node - on every other Node, such call will lead to inconsistent result. 57 | Why such bad design? 58 | 59 | - The main purpose is to save time and memory by creating an additional object that would encapsulate the 60 | root Node (yes, it matters). 61 | 62 | - All "bad things" happen only internally and there is no possibility for the end-user to get the 63 | structure to the inconsistent state. So, it's not such a bad design after all. 64 | 65 | -------------------------------------------------------------------------------- /test/map_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, VaccumLabs. 2 | // Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code 3 | // is governed by a BSD-style license that can be found in the LICENSE file. 4 | 5 | // Authors are listed in the AUTHORS file 6 | 7 | library map_test; 8 | 9 | import 'package:vacuum_persistent/persistent.dart'; 10 | import 'package:unittest/unittest.dart'; 11 | 12 | 13 | class Element { 14 | var value, hash; 15 | Element(this.value, this.hash); 16 | get hashCode => hash; 17 | operator ==(other){ 18 | if (other is! Element) { 19 | return false; 20 | } 21 | return value == other.value; 22 | } 23 | toString() => "Element(${value},${hash})"; 24 | } 25 | 26 | main() { 27 | run(); 28 | } 29 | 30 | run() { 31 | group('Persistent map', () { 32 | test('assoc', () { 33 | PMap pm = new PMap(); 34 | pm = pm.assoc('a', 'b'); 35 | expect(pm.toMap(), equals({'a': 'b'})); 36 | pm = pm.assoc('a', 'c'); 37 | expect(pm.toMap(), equals({'a': 'c'})); 38 | 39 | }); 40 | 41 | test('get', () { 42 | PMap pm = new PMap(); 43 | pm = pm.assoc('a', 'b'); 44 | pm = pm.assoc('b', 'c'); 45 | 46 | expect(pm.get('a'), equals('b')); 47 | expect(() => pm.get('c'), throws); 48 | expect(pm.get('c','none'), equals('none')); 49 | }); 50 | 51 | test('delete', () { 52 | PMap pm = new PMap(); 53 | pm = pm.assoc('a', 'b'); 54 | 55 | expect(pm.delete('a').toMap(), equals({})); 56 | expect(() => pm.delete('b'), throws); 57 | expect(pm.delete('b', missingOk: true).toMap(), equals({'a': 'b'})); 58 | }); 59 | 60 | test('update', () { 61 | PMap pm = persist({'a':'b', 'c': 'd'}); 62 | expect(pm.update('c', (v) => 'updated $v'), equals(persist({'a': 'b', 'c': 'updated d'}))); 63 | }); 64 | 65 | test('equality speed', (){ 66 | PMap pm; 67 | Map m = {}; 68 | for(int i=0; i<100000; i++) { 69 | m['hello${i}'] = 'world${i}'; 70 | } 71 | pm = persist(m); 72 | // more important than the 'expect' is that this test takes reasonable time to finish 73 | for (int i=0; i<100000; i++) { 74 | expect(pm == pm.assoc('hello${i}', 'different'), isFalse); 75 | } 76 | }); 77 | 78 | test('transient basics', (){ 79 | TMap m = new TMap(); 80 | m.doAssoc('a', 'b'); 81 | m.doAssoc('c', 'd'); 82 | expect(m.toMap(), equals({'a':'b', 'c':'d'})); 83 | m.doAssoc('a', 'bb'); 84 | expect(m.toMap(), equals({'a':'bb', 'c':'d'})); 85 | m.doUpdate('a', (v) => "b${v}"); 86 | expect(m.toMap(), equals({'a':'bbb', 'c':'d'})); 87 | m.doDelete('a'); 88 | expect(m.toMap(), equals({'c':'d'})); 89 | }); 90 | 91 | test('equality on hash colision', (){ 92 | var e1 = new Element(0, 0); 93 | var e2 = new Element(1, 0); 94 | var m1 = persist({e1: 0, e2: 0}); 95 | var m2 = persist({e2: 0, e1: 0}); 96 | expect(m1, equals(m2)); 97 | }); 98 | 99 | test('union', (){ 100 | var dm1 = {}; 101 | var dm2 = {}; 102 | for(int i = 1; i <= 1000; i++){ 103 | if(i%4 != 3) dm1[i] = i; 104 | if(i%4 != 1) dm2[i] = -i; 105 | } 106 | PMap m1 = persist(dm1); 107 | PMap m2 = persist(dm2); 108 | PMap m3 = m1.union(m2); 109 | PMap m4 = m1.union(m2, (a,b)=>a+b); 110 | 111 | expect(m3.length, equals(1000)); 112 | expect(m4.length, equals(1000)); 113 | for(int i = 1; i <= 1000; i++){ 114 | if(i%4 == 1){ 115 | expect(m3[i], equals(i)); 116 | expect(m4[i], equals(i)); 117 | } else if(i%4 == 3){ 118 | expect(m3[i], equals(-i)); 119 | expect(m4[i], equals(-i)); 120 | } else { 121 | expect(m3[i], equals(-i)); 122 | expect(m4[i], equals(0)); 123 | } 124 | } 125 | }); 126 | 127 | test('intersection', (){ 128 | var dm1 = {}; 129 | var dm2 = {}; 130 | for(int i = 1; i <= 1000; i++){ 131 | if(i%4 != 3) dm1[i] = i; 132 | if(i%4 != 1) dm2[i] = -i; 133 | } 134 | PMap m1 = persist(dm1); 135 | PMap m2 = persist(dm2); 136 | PMap m3 = m1.intersection(m2); 137 | PMap m4 = m1.intersection(m2, (a,b)=>a+b); 138 | 139 | expect(m3.length, equals(500)); 140 | expect(m4.length, equals(500)); 141 | for(int i = 2; i <= 1000; i+=2){ 142 | expect(m3[i], equals(-i)); 143 | expect(m4[i], equals(0)); 144 | } 145 | }); 146 | 147 | }); 148 | 149 | } 150 | -------------------------------------------------------------------------------- /lib/src/set_impl.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, VacuumLabs. 2 | // Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code 3 | // is governed by a BSD-style license that can be found in the LICENSE file. 4 | 5 | // Authors are listed in the AUTHORS file 6 | 7 | part of persistent; 8 | 9 | /** 10 | * A base class for implementations of [ReadSet]. 11 | */ 12 | abstract class _ReadSetBase 13 | extends IterableBase 14 | implements ReadSet { 15 | 16 | String toString() { 17 | StringBuffer buffer = new StringBuffer('{'); 18 | bool comma = false; 19 | this.forEach((E e) { 20 | if (comma) buffer.write(', '); 21 | buffer.write(e.toString()); 22 | comma = true; 23 | }); 24 | buffer.write('}'); 25 | return buffer.toString(); 26 | } 27 | } 28 | 29 | abstract class _PSetMixim 30 | implements PSet { 31 | 32 | PSet union(PSet PSet) => 33 | this.withTransient((set)=> 34 | PSet.where((e) => !this.contains(e)).forEach((e)=> 35 | set.doInsert(e) 36 | ) 37 | ); 38 | 39 | PSet operator +(PSet pset) => 40 | union(pset); 41 | 42 | PSet difference(PSet pset) => 43 | new PSet.from(this.where((e) => !pset.contains(e))); 44 | 45 | PSet operator -(PSet pset) => 46 | difference(pset); 47 | 48 | Iterable cartesianProduct(PSet PSet) => 49 | this.expand((a) => PSet.map((b) => new Pair(a,b))); 50 | 51 | Iterable operator *(PSet PSet) => 52 | cartesianProduct(PSet); 53 | 54 | PSet intersect(PSet pset) => 55 | new PSet.from(this.where((e) => pset.contains(e))); 56 | 57 | PSet strictMap(f(E element)) => 58 | new PSet.from(this.map(f)); 59 | 60 | PSet strictWhere(bool f(E element)) => 61 | new PSet.from(this.where(f)); 62 | } 63 | 64 | abstract class _SetImplBase extends _ReadSetBase { 65 | ReadMap get _map; 66 | 67 | bool contains(E element) => _map.containsKey(element); 68 | 69 | bool hasKey(E key) => contains(key); 70 | 71 | E get(E element, [E notFound = _none]) => 72 | contains(element) ? 73 | element 74 | : 75 | notFound == _none ? 76 | _ThrowKeyError(element) 77 | : 78 | notFound; 79 | 80 | void forEach(f(E element)) => _map.forEachKeyValue((E k, v) => f(k)); 81 | 82 | Iterable map(f(E element)) { 83 | return _map.map((pair)=>f(pair.fst)); 84 | } 85 | 86 | int get length => _map.length; 87 | 88 | bool operator ==(other) => 89 | other is _SetImplBase ? _map == other._map : false; 90 | 91 | Iterator get iterator => 92 | _map.map((Pair pair) => pair.fst).iterator; 93 | 94 | E get last => _map.last.fst; 95 | 96 | E elementAt(int index) => _map.elementAt(index).fst; 97 | } 98 | 99 | 100 | class _PSetImpl 101 | extends _SetImplBase 102 | with _PSetMixim { 103 | 104 | final PMap _map; 105 | 106 | _PSetImpl._internal(this._map); 107 | 108 | factory _PSetImpl() => 109 | new _PSetImpl._internal(new PMap()); 110 | 111 | _PSetImpl insert(E element) => 112 | new _PSetImpl._internal(_map.assoc(element, null)); 113 | 114 | _PSetImpl delete(E element, {bool missingOk:false}) => 115 | new _PSetImpl._internal(_map.delete(element, missingOk:missingOk)); 116 | 117 | TSet asTransient() { 118 | return new _TSetImpl._internal(_map.asTransient()); 119 | } 120 | 121 | 122 | PSet union(PSet PSet){ 123 | if(PSet is _PSetImpl){ 124 | return new _PSetImpl._internal( 125 | _map.union(PSet._map)); 126 | } else { 127 | return super.union(PSet); 128 | } 129 | } 130 | 131 | PSet intersection(PSet PSet){ 132 | if(PSet is _PSetImpl){ 133 | return new _PSetImpl._internal( 134 | _map.intersection(PSet._map)); 135 | } else { 136 | return super.intersection(PSet); 137 | } 138 | } 139 | 140 | PSet withTransient(void change(TSet set)) { 141 | TSet result = this.asTransient(); 142 | change(result); 143 | return result.asPersistent(); 144 | } 145 | 146 | bool operator==(other) => other is PSet ? super == other : false; 147 | 148 | int get hashCode => this._map.hashCode; 149 | } 150 | 151 | class _TSetImpl extends _SetImplBase implements TSet { 152 | final TMap _map; 153 | 154 | _TSetImpl._internal(this._map); 155 | 156 | factory _TSetImpl() => 157 | new _TSetImpl._internal(new TMap()); 158 | 159 | void doInsert(E element){ 160 | _map.doAssoc(element, null); 161 | } 162 | 163 | void doDelete(E element, {bool missingOk:false}){ 164 | _map.doDelete(element, missingOk:missingOk); 165 | } 166 | 167 | PSet asPersistent() { 168 | return new _PSetImpl._internal(_map.asPersistent()); 169 | } 170 | 171 | } 172 | 173 | 174 | -------------------------------------------------------------------------------- /lib/src/set.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, VacuumLabs. 2 | // Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code 3 | // is governed by a BSD-style license that can be found in the LICENSE file. 4 | 5 | // Authors are listed in the AUTHORS file 6 | 7 | part of persistent; 8 | 9 | /** 10 | * A read-only set, unordered collection of distinct elements of type [E]. 11 | * 12 | * There is no default implementation of [ReadVector], since it just 13 | * specifies the common interface of [PVec] and [TVec]. 14 | */ 15 | abstract class ReadSet implements Iterable { 16 | 17 | bool hasKey(E key); 18 | 19 | /** 20 | * If it contains given [element], it is returned. Otherwise returns [notFound] 21 | */ 22 | E get(E element, [E notFound]); 23 | } 24 | 25 | /** 26 | * A persistent set, unordered collection of distinct elements of type [E]. 27 | * 28 | * Persistent data structure is an immutable structure, that provides effective 29 | * creation of slightly mutated copies. 30 | */ 31 | abstract class PSet implements ReadSet, PersistentCollection { 32 | 33 | /// Creates an empty [PSet] using its default implementation. 34 | factory PSet() => new _PSetImpl(); 35 | 36 | /** 37 | * Creates an immutable copy of [elements] using the default implementation 38 | * of [PSet]. 39 | */ 40 | factory PSet.from(Iterable elements) { 41 | PSet result = new _PSetImpl(); 42 | for (E element in elements) { 43 | result = result.insert(element); 44 | } 45 | return result; 46 | } 47 | 48 | /** 49 | * Returns a set identical to `this` except that it contains [element]. 50 | * 51 | * If [element] is already in `this`, the same set is returned. 52 | */ 53 | PSet insert(E element); 54 | 55 | /** 56 | * Returns a set identical to `this` except that it does not contain [element]. 57 | * 58 | * If `this` does not contain [element], `this` is returned or Exception is thrown 59 | * dependent on what is value of [missingOk] flag. 60 | */ 61 | PSet delete(E element, {bool missingOk: false}); 62 | 63 | /** 64 | * Creates transient copy of `this`, lets it to be modified by [change] 65 | * and returns persistent result. 66 | */ 67 | PSet withTransient(void change(TSet set)); 68 | 69 | /** 70 | * Returns a new set of all the elements that are included 71 | * in either `this` or [other] 72 | */ 73 | PSet union(PSet other); 74 | 75 | /// Alias for [union]. 76 | PSet operator +(PSet other); 77 | 78 | /** 79 | * Returns a new set of all the elements that are included in `this` but 80 | * not in [other] 81 | */ 82 | PSet difference(PSet other); 83 | 84 | /// Alias for [difference]. 85 | PSet operator -(PSet other); 86 | 87 | /** 88 | * Returns a lazy iterable with all the pairs `Pair(first, second)` 89 | * such that `first` is included in `this` and 90 | * `second` is included in [other] 91 | */ 92 | Iterable> cartesianProduct(PSet other); 93 | 94 | /// Alias for [cartesianProduct]. 95 | Iterable> operator *(PSet other); 96 | 97 | /** 98 | * Returns a new set of all the elements that are included 99 | * in both `this` and [other] 100 | */ 101 | PSet intersection(PSet PSet); 102 | 103 | /// A strict (non-lazy) version of [map]. 104 | PSet strictMap(f(E element)); 105 | 106 | /// A strict (non-lazy) version of [where]. 107 | PSet strictWhere(bool f(E element)); 108 | 109 | /** 110 | * The equality operator. 111 | * 112 | * Two persistent sets are equal if and only if for each element 113 | * in any of them exists an equal element in the other one. 114 | */ 115 | bool operator==(other); 116 | 117 | /* 118 | * The documentation is inherited from the Object 119 | */ 120 | int get hashCode; 121 | 122 | TSet asTransient(); 123 | } 124 | 125 | /** 126 | * A transient set, unordered collection of distinct elements of type [E]. 127 | * 128 | * Transient data structure is a mutable structure, that can be effectively 129 | * converted to the persistent data structure. It is usually created from 130 | * a persistent structure to apply some changes and obtain a new persistent 131 | * structure. 132 | */ 133 | abstract class TSet implements ReadSet { 134 | 135 | /** 136 | * Adds [element] to `this`. 137 | * 138 | * If `this` contains [element], nothing happens. 139 | */ 140 | void doInsert(E element); 141 | 142 | /** 143 | * Removes [element] from `this`. 144 | * 145 | * If `this` does not contain [element] and [missingOk] 146 | * is not specified or false, the error is thrown. 147 | * If `this` does not contain [element] and [missingOk] 148 | * is `true`, nothing happens. 149 | */ 150 | void doDelete(E element, {bool missingOk: false}); 151 | 152 | PSet asPersistent(); 153 | } 154 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Efficient Persistent Data Structures 2 | 3 | [![Build Status](https://drone.io/github.com/vacuumlabs/persistent/status.png)](https://drone.io/github.com/vacuumlabs/persistent/latest) 4 | 5 | Check out [changes in 2.0 version!] (changes_2_0.md) 6 | 7 | Learn how you can use [transients] (transients.md) 8 | 9 | Want to understand the code? Want to contribute? See [technical overview] (technical.md) 10 | 11 | 14 | 15 | ## What are persistent data structures 16 | *Persistent* data structure is an immutable structure; the main difference with standard data structures is how you 'write' to them: instead of mutating 17 | the old structure, you create the new, independent, (slightly) modified copy of it. Typical examples of commonly used Persistent structures are String (in Java, Javascript, Python, Ruby) or Python's Tuple or Java's BigDecimal. [(Not only)](http://www.infoq.com/presentations/Value-Identity-State-Rich-Hickey) we believe such concept could be beneficial also for other data structures such as Maps, Lists/Vectors, Sets. 18 | 19 | var couple = new PMap.fromMap({'father': 'Homer', 'mother': 'Marge'}); 20 | // do not (and can not) modify couple anymore 21 | var withChild = couple.assoc('boy', 'Bart'); 22 | print(couple); // {mother: Marge, father: Homer} 23 | print(withChild); // {boy: Bart, mother: Marge, father: Homer} 24 | 25 | ## Got it. And it is cool because...? 26 | 27 | ### It disallows unwanted side effects 28 | You know the story. Late in the evening, exhausted and frustrated you find out that some guy that implemented 29 | 30 | int ComputeResponseLength(Map responseMap) 31 | 32 | got a 'great' idea, that instead of just computing response length he also mutates responseMap in some tricky way (say, he does some kind of sanitization of responseMap). Even if this was mentioned in the documentation and even if the methods name was different: this is a spaghetti code. 33 | 34 | ### Equality and hashCode done right 35 | Finally, they work as you'd expect. How cool is this: 36 | 37 | // deeply persist the structure of Maps and Lists 38 | PMap a = persist({[1,2]: 'tower', [1,3]: 'water'}); 39 | PMap b = persist({[1,2]: 'tower', [1,3]: 'water'}); 40 | assert(a==b); 41 | // kids, don't try this with standard List, it ain't going to work 42 | print(a[persist([1, 2])]); // prints 'tower' 43 | 44 | ### Instant 'deep copy' 45 | Just copy/pass the reference. It's like doing deep copy in O(1). 46 | 47 | ### Caching made easier 48 | Caching can speed things up significantly. But how do you cache results of a function 49 | 50 | List findSuspiciousEntries(List entries) 51 | 52 | One possible workaround would be to JSONize entries to string and use such string as a hashing key. However, it's much more elegant, safe (what about ordering of keys within maps?), performant and memory-wise with Persistent structures. Also, until you restrict to well-behaving functions, there's no need to invalidate cache; you can cache anything, for as long as you want. 53 | 54 | ### Simplicity matters 55 | Fast copying or equality done right are nice features, but this is not the only selling point here. Having different ways how to copy (shallow, deep) objects or how to compare them (== vs. equals in Java) introduces new complexity. Even if you get used to it, it still takes some part of your mental capabilities and can lead to errors. 56 | 57 | ### Structure sharing 58 | PMap map1 = persist({'a': 'something', 'b': bigMap}); 59 | PMap map2 = a.assoc('a', 'something completely different'); 60 | Suppose you are interested, whether map1['b'] == map2['b']. Thanks to structure sharing, this is O(1) operation, which means it is amazingly fast - no need to traverse through the bigMap. Although it may sound unimportant at the first glance, it is what really enables fast caching of complex functions. Also, this is the reason, why [Om](https://github.com/swannodette/om/) framework is MUCH faster than Facebooks [React](http://facebook.github.io/react/). 61 | 62 | ## And what is the prize for this all 63 | In the 2.0 release, we optimized memory consumption such that the only penalty for using Persistent 64 | comes at lower speed. Although structure sharing makes the whole thing much more effective than naive 65 | copy-it-all approach, Persistents are still slower than their mutable counterparts (note however, that on 66 | the other hand, some operations runs significantly faster, so its hard to say something conclusive 67 | here). Following numbers illustrate, how much slow are Persistent data structures when benchmarking either on DartVM 68 | or Dart2JS on Node (the numbers are quite independent of the structure size): 69 | 70 | * DartVM read speed: 2 71 | * DartVM write speed: 12 (5 by using Transients) 72 | * Dart2JS read speed: 3 73 | * Dart2JS write speed: 14 (6 by using Transients) 74 | 75 | Although the factors are quite big, the whole operation is still very fast and it probably won't be THE bottleneck which would slow down your app. 76 | 77 | -------------------------------------------------------------------------------- /test/set_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, VaccumLabs. 2 | // Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code 3 | // is governed by a BSD-style license that can be found in the LICENSE file. 4 | 5 | // Authors are listed in the AUTHORS file 6 | 7 | library set_test; 8 | 9 | import 'package:vacuum_persistent/persistent.dart'; 10 | import 'package:unittest/unittest.dart'; 11 | 12 | main() { 13 | run(); 14 | } 15 | 16 | run() { 17 | 18 | group('Persistent set', () { 19 | 20 | test('length', () { 21 | var set = new PSet.from(["a","b","c"]); 22 | expect(set.length, equals(3)); 23 | }); 24 | 25 | test('contains', () { 26 | var set = new PSet.from(["a","b","c"]); 27 | expect(set.contains("a"), isTrue); 28 | expect(set.contains("b"), isTrue); 29 | expect(set.contains("c"), isTrue); 30 | expect(!set.contains("d"), isTrue); 31 | }); 32 | 33 | test('==', () { 34 | var set1 = new PSet.from(["a","b"]); 35 | var set2 = new PSet.from(["b","a"]); 36 | var set3 = new PSet.from(["b"]); 37 | expect(set1==set2, isTrue); 38 | expect(set2!=set3, isTrue); 39 | expect(set1!=set3, isTrue); 40 | }); 41 | 42 | test('union', () { 43 | var set1 = new PSet.from(["a","b","c"]); 44 | var set2 = new PSet.from(["d","c"]); 45 | var set3 = new PSet(); 46 | expect(set1.union(set3) == set1, isTrue); 47 | expect(set3.union(set2) == set2, isTrue); 48 | expect(set1.union(set2) == new PSet.from(["a","b","c","d"]), isTrue); 49 | }); 50 | 51 | test('difference', () { 52 | var set1 = new PSet.from(["a","b","c"]); 53 | var set2 = new PSet.from(["d","c"]); 54 | var set3 = new PSet(); 55 | expect(set1.difference(set3) == set1, isTrue); 56 | expect(set3.difference(set2) == set3, isTrue); 57 | expect( 58 | set1.difference(set2) == new PSet.from(["a","b"]), 59 | isTrue 60 | ); 61 | }); 62 | 63 | test('intersection', () { 64 | var set1 = new PSet.from(["a","b","c"]); 65 | var set2 = new PSet.from(["d","c"]); 66 | var set3 = new PSet(); 67 | expect(set1.intersection(set3) == set3, isTrue); 68 | expect(set3.intersection(set2) == set3, isTrue); 69 | expect(set1.intersection(set2) == new PSet.from(["c"]), isTrue); 70 | }); 71 | 72 | test('cartesianProduct', () { 73 | var set1 = new PSet.from(["a","b","c"]); 74 | var set2 = new PSet.from(["d","c"]); 75 | var set3 = new PSet(); 76 | expect(new PSet.from(set1 * set3) == set3, isTrue); 77 | expect(new PSet.from(set3 * set2) == set3, isTrue); 78 | expect(new PSet.from(set1 * set2) == new PSet.from([ 79 | new Pair("a","d"), new Pair("b","d"), new Pair("c","d"), 80 | new Pair("a","c"), new Pair("b","c"), new Pair("c","c") 81 | ]), isTrue); 82 | }); 83 | 84 | test('insert', () { 85 | var set = new PSet.from(["a","b","c"]); 86 | set = set.insert("c"); 87 | expect(set, equals(new PSet.from(["a","b","c"]))); 88 | expect(set.contains("c"), isTrue); 89 | set = set.insert("d"); 90 | expect(set, equals(new PSet.from(["a","b","c","d"]))); 91 | expect(set.contains("d"), isTrue); 92 | }); 93 | 94 | test('delete', () { 95 | var set = new PSet.from(["a","b","c"]); 96 | set = set.delete("c"); 97 | expect(set, equals(new PSet.from(["a","b"]))); 98 | expect(set.contains("c"), isFalse); 99 | set = set.delete("d", missingOk:true); 100 | expect(set, equals(new PSet.from(["a","b"]))); 101 | expect(set.contains("d"), isFalse); 102 | }); 103 | }); 104 | 105 | group('Transient set', () { 106 | 107 | test('length', () { 108 | var set = new PSet.from(["a","b","c"]).asTransient(); 109 | expect(set.length, equals(3)); 110 | }); 111 | 112 | test('contains', () { 113 | var set = new PSet.from(["a","b","c"]).asTransient(); 114 | expect(set.contains("a"), isTrue); 115 | expect(set.contains("b"), isTrue); 116 | expect(set.contains("c"), isTrue); 117 | expect(!set.contains("d"), isTrue); 118 | }); 119 | 120 | test('doInsert', () { 121 | var set = new PSet.from(["a","b","c"]).asTransient(); 122 | set.doInsert("c"); 123 | expect(set.asPersistent(), 124 | equals(new PSet.from(["a","b","c"]))); 125 | expect(set.contains("c"), isTrue); 126 | set = set.asPersistent().asTransient(); 127 | set.doInsert("d"); 128 | expect(set.asPersistent(), 129 | equals(new PSet.from(["a","b","c","d"]))); 130 | expect(set.contains("d"), isTrue); 131 | }); 132 | 133 | test('doDelete', () { 134 | var set = new PSet.from(["a","b","c"]).asTransient(); 135 | set.doDelete("c"); 136 | expect(set.asPersistent(), equals(new PSet.from(["a","b"]))); 137 | expect(set.contains("c"), isFalse); 138 | set = set.asPersistent().asTransient(); 139 | set.doDelete("d", missingOk:true); 140 | expect(set.asPersistent(), equals(new PSet.from(["a","b"]))); 141 | expect(set.contains("d"), isFalse); 142 | }); 143 | }); 144 | } -------------------------------------------------------------------------------- /test/vector_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, VaccumLabs. 2 | // Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code 3 | // is governed by a BSD-style license that can be found in the LICENSE file. 4 | 5 | // Authors are listed in the AUTHORS file 6 | 7 | library vector_test; 8 | 9 | import 'package:vacuum_persistent/persistent.dart'; 10 | import 'package:unittest/unittest.dart'; 11 | 12 | main() { 13 | run(); 14 | } 15 | 16 | run() { 17 | 18 | group('PersistentVector', () { 19 | 20 | PV(list) => new PVec.from(list); 21 | 22 | test('get', () { 23 | PVec v = PV([0, 1, 2]); 24 | expect(v.get(0), equals(0)); 25 | expect(v[1], equals(1)); 26 | expect(v.get(2, 47), equals(2)); 27 | expect(() => v.get(3), throws); 28 | expect(() => v[4], throws); 29 | expect(v.get(5, 47), equals(47)); 30 | }); 31 | 32 | test('first', () { 33 | expect(PV([0, 1, 2]).first, equals(0)); 34 | expect(PV([0]).first, equals(0)); 35 | expect(() => PV([]).first, throws); 36 | }); 37 | 38 | test('last', () { 39 | expect(PV([0, 1, 2]).last, equals(2)); 40 | expect(PV([0]).last, equals(0)); 41 | expect(() => PV([]).last, throws); 42 | }); 43 | 44 | test('iterator', () { 45 | // Indirectly, using `toList` 46 | expect(PV([0, 1, 2]).toList(), equals([0, 1, 2])); 47 | expect(PV([0, 1]).toList(), equals([0, 1])); 48 | expect(PV([0]).toList(), equals([0])); 49 | expect(PV([]).toList(), equals([])); 50 | }); 51 | 52 | test('length', () { 53 | expect(PV([0, 1, 2]).length, equals(3)); 54 | expect(PV([0, 1]).length, equals(2)); 55 | expect(PV([0]).length, equals(1)); 56 | expect(PV([]).length, equals(0)); 57 | }); 58 | 59 | test('push', () { 60 | expect(PV([0]).push(1).toList(), equals([0, 1])); 61 | expect(PV([]).push(0).push(1).toList(), equals([0, 1])); 62 | }); 63 | 64 | test('pop', () { 65 | expect(PV([0, 1]).pop().toList(), equals([0])); 66 | expect(PV([0, 1]).pop().pop().toList(), equals([])); 67 | expect(() => PV([0, 1]).pop().pop().pop(), throws); 68 | }); 69 | 70 | test('set', () { 71 | expect(PV([0, 1]).set(0, 1).toList(), equals([1, 1])); 72 | expect(() => PV([0, 1]).set(2, 1), throws); 73 | }); 74 | 75 | test('asTransient', () { 76 | expect(PV([0, 1]).asTransient().toList(), equals([0, 1])); 77 | expect(PV([0, 1]).asTransient() is TVec, isTrue); 78 | }); 79 | 80 | test('withTransient', () { 81 | expect(PV([0, 1]).withTransient((v){ 82 | v[0]=2; 83 | }).toList(), equals([2, 1])); 84 | }); 85 | 86 | 87 | test('pushing nulls', () { 88 | PVec v = PV([]); 89 | v = v.push(null); 90 | v = v.push(47); 91 | expect(v, orderedEquals([null, 47])); 92 | }); 93 | 94 | test('created from array of nulls', () { 95 | PVec v = PV([null, null]); 96 | v = v.push(null); 97 | v = v.push(47); 98 | expect(v, orderedEquals([null, null, null, 47])); 99 | }); 100 | }); 101 | 102 | 103 | group('TransientVector', () { 104 | 105 | TV(list) => new PVec.from(list).asTransient(); 106 | 107 | test('get', () { 108 | TVec v = TV([0, 1, 2]); 109 | expect(v.get(0), equals(0)); 110 | expect(v[1], equals(1)); 111 | expect(v.get(2, 47), equals(2)); 112 | expect(() => v.get(3), throws); 113 | expect(() => v[4], throws); 114 | expect(v.get(5, 47), equals(47)); 115 | }); 116 | 117 | test('first', () { 118 | expect(TV([0, 1, 2]).first, equals(0)); 119 | expect(TV([0]).first, equals(0)); 120 | expect(() => TV([]).first, throws); 121 | }); 122 | 123 | test('last', () { 124 | expect(TV([0, 1, 2]).last, equals(2)); 125 | expect(TV([0]).last, equals(0)); 126 | expect(() => TV([]).last, throws); 127 | }); 128 | 129 | test('iterator', () { 130 | // Indirectly, using `toList` 131 | expect(TV([0, 1, 2]).toList(), equals([0, 1, 2])); 132 | expect(TV([0, 1]).toList(), equals([0, 1])); 133 | expect(TV([0]).toList(), equals([0])); 134 | expect(TV([]).toList(), equals([])); 135 | }); 136 | 137 | test('length', () { 138 | expect(TV([0, 1, 2]).length, equals(3)); 139 | expect(TV([0, 1]).length, equals(2)); 140 | expect(TV([0]).length, equals(1)); 141 | expect(TV([]).length, equals(0)); 142 | }); 143 | 144 | test('push', () { 145 | expect(TV([0])..doPush(1)..toList(), equals([0, 1])); 146 | expect(TV([])..doPush(0)..doPush(1)..toList(), equals([0, 1])); 147 | }); 148 | 149 | test('pop', () { 150 | expect(TV([0, 1])..doPop()..toList(), equals([0])); 151 | expect(TV([0, 1])..doPop()..doPop()..toList(), equals([])); 152 | expect(() => TV([0, 1])..doPop()..doPop()..doPop(), throws); 153 | }); 154 | 155 | test('set', () { 156 | TVec v = TV([0, 1]); 157 | v[0] = 1; 158 | expect(v, equals([1, 1])); 159 | expect(TV([0, 1])..doSet(0, 1)..toList(), equals([1, 1])); 160 | expect(() => TV([0, 1])..doSet(2, 1), throws); 161 | expect(() => TV([0, 1])[2] = 1, throws); 162 | }); 163 | 164 | test('asPersistent', () { 165 | expect(TV([0, 1]).asPersistent().toList(), equals([0, 1])); 166 | expect(TV([0, 1]).asPersistent() is PVec, isTrue); 167 | }); 168 | 169 | 170 | }); 171 | } -------------------------------------------------------------------------------- /lib/src/linked_list.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code 2 | // is governed by a BSD-style license that can be found in the LICENSE file. 3 | 4 | // Author: Paul Brauner (polux@google.com) 5 | 6 | part of persistent; 7 | 8 | /** 9 | * Immutable list of elements of type E. All its predecessors must 10 | * be accessed before accessing an element. 11 | * 12 | * Can be either [Cons] or [Nil]. 13 | * [Nil] is an empty list, 14 | * while [Cons] contains the first element (`elem`) 15 | * and the rest of the list (`tail`). 16 | */ 17 | abstract class LinkedList implements Iterable { 18 | bool get isNil; 19 | bool get isCons; 20 | 21 | /// Converts this to [Nil] or returns `null` 22 | Nil get asNil; 23 | 24 | /// Converts this to [Cons] or returns `null` 25 | Cons get asCons; 26 | 27 | /// Passes all the elements of this to [f]. 28 | void foreach(f(A)); 29 | 30 | /// A strict (non-lazy) version of [:map:]. 31 | LinkedList strictMap(f(A)); 32 | 33 | /// A strict (non-lazy) version of [:where:]. 34 | LinkedList strictWhere(bool f(A)); 35 | 36 | /** 37 | * The equality operator. 38 | * 39 | * Two linked lists are equal if and only if they have same lengths, 40 | * and for each possition, the elements at it are equal. 41 | */ 42 | bool operator==(other); 43 | 44 | // Documentation inherited from Object 45 | int get hashCode; 46 | } 47 | 48 | /** 49 | * [LinkedList] builder. 50 | * 51 | * Elements are added from the first. 52 | */ 53 | class LinkedListBuilder { 54 | List _data = []; 55 | 56 | /// Adds the next element to the list. 57 | void add(E x) { 58 | _data.add(x); 59 | } 60 | 61 | /// Adds all the elements of [iterable] to the list. 62 | void addAll(Iterable iterable) { 63 | _data.addAll(iterable); 64 | } 65 | 66 | /** 67 | * Creates a new list prepending so far added elements to the 68 | * optional [tail]. 69 | */ 70 | LinkedList build([tail = null]) { 71 | if (tail == null) 72 | tail = new Nil(); 73 | for (E x in _data.reversed){ 74 | tail = new Cons(x, tail); 75 | } 76 | return tail; 77 | } 78 | } 79 | 80 | abstract class _LinkedListBase extends IterableBase 81 | implements LinkedList { 82 | 83 | const _LinkedListBase(); 84 | 85 | void foreach(f(A)) { 86 | LinkedList it = this; 87 | while (!it.isNil) { 88 | Cons cons = it.asCons; 89 | f(cons.elem); 90 | it = cons.tail; 91 | } 92 | } 93 | 94 | LinkedList strictMap(f(A)) { 95 | LinkedListBuilder builder = new LinkedListBuilder(); 96 | LinkedList it = this; 97 | while (it.isCons) { 98 | Cons cons = it.asCons; 99 | E elem = cons.elem; 100 | builder.add(f(elem)); 101 | it = cons.tail; 102 | } 103 | return builder.build(); 104 | } 105 | 106 | LinkedList strictWhere(bool f(A)) { 107 | LinkedListBuilder builder = new LinkedListBuilder(); 108 | LinkedList it = this; 109 | while (it.isCons) { 110 | Cons cons = it.asCons; 111 | E elem = cons.elem; 112 | if (f(elem)) builder.add(elem); 113 | it = cons.tail; 114 | } 115 | return builder.build(); 116 | } 117 | } 118 | 119 | class _NilIterator implements Iterator { 120 | const _NilIterator(); 121 | E get current => null; 122 | bool moveNext() => false; 123 | } 124 | 125 | /** 126 | * Empty [LinkedList] 127 | */ 128 | class Nil extends _LinkedListBase { 129 | bool get isNil => true; 130 | bool get isCons => false; 131 | Nil get asNil => this; 132 | Cons get asCons => null; 133 | 134 | const Nil(); 135 | 136 | toString() => "nil()"; 137 | 138 | int get length => 0; 139 | 140 | Iterator get iterator => const _NilIterator(); 141 | 142 | bool operator==(other) => other is LinkedList ? other.isNil : false; 143 | 144 | int get hashCode => 0; 145 | } 146 | 147 | class _ConsIterator implements Iterator { 148 | final LinkedList _head; 149 | LinkedList _current = null; 150 | 151 | _ConsIterator(this._head); 152 | 153 | E get current => _current.isCons ? _current.asCons.elem : null; 154 | 155 | bool moveNext() { 156 | if (_current == null) { 157 | _current = _head; 158 | return _current.isCons; 159 | } 160 | if (_current.isCons) { 161 | _current = _current.asCons.tail; 162 | return _current.isCons; 163 | } 164 | return false; 165 | } 166 | } 167 | 168 | /** 169 | * Nonempty [LinkedList] 170 | */ 171 | class Cons extends _LinkedListBase { 172 | final int length; 173 | final int hashCode; 174 | 175 | /// The first element of this 176 | final E elem; 177 | 178 | /// The rest of this - without the first element 179 | final LinkedList tail; 180 | 181 | Cons(elem, tail): 182 | elem = elem, 183 | tail = tail, 184 | length = tail.length + 1, 185 | hashCode = hash2(elem.hashCode, tail.hashCode); 186 | 187 | bool get isNil => false; 188 | bool get isCons => true; 189 | Nil get asNil => null; 190 | Cons get asCons => this; 191 | 192 | toString() => "cons($elem, $tail)"; 193 | 194 | Iterator get iterator => new _ConsIterator(this); 195 | 196 | bool operator==(other){ 197 | if (other is! LinkedList) return false; 198 | if ( !other.isCons 199 | || this.hashCode != other.hashCode 200 | || this.length != other.length 201 | ) return false; 202 | var x = this; 203 | var y = other; 204 | while(x.isCons){ 205 | if(x.elem != y.elem) return false; 206 | x = x.tail; 207 | y = y.tail; 208 | } 209 | return true; 210 | } 211 | 212 | } 213 | -------------------------------------------------------------------------------- /lib/src/persistent.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, VacuumLabs. 2 | // Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code 3 | // is governed by a BSD-style license that can be found in the LICENSE file. 4 | 5 | // Authors are listed in the AUTHORS file 6 | 7 | part of persistent; 8 | 9 | /** 10 | * All the persistent structures implements this. 11 | */ 12 | class PersistentCollection {} 13 | 14 | /** 15 | * PersistentCollection that have index access implements this. 16 | */ 17 | class PersistentIndexedCollection extends PersistentCollection {} 18 | 19 | class _Owner {} 20 | 21 | /** 22 | * Generates a hash code for two objects. 23 | */ 24 | int hash2(a, b) => _finish(_combine(_combine(0, a), b)); 25 | 26 | // Jenkins hash functions 27 | int _combine(int hash, int value) { 28 | hash = 0x1fffffff & (hash + value); 29 | hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); 30 | return hash ^ (hash >> 6); 31 | } 32 | 33 | int _finish(int hash) { 34 | hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); 35 | hash = hash ^ (hash >> 11); 36 | return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); 37 | } 38 | 39 | /** 40 | * Converts structure of [List]s and [Map]s to the equivalent 41 | * persistent structure. 42 | * 43 | * Works recursively. 44 | */ 45 | persist(from) { 46 | if(from is PersistentCollection) return from; 47 | if(from is Map) { 48 | var map = new PMap(); 49 | return map.withTransient((TMap map) { 50 | from.forEach((key,value) => map.doAssoc(per(key), per(value))); 51 | }); 52 | } 53 | else if (from is Set) { 54 | from = from.map((e) => persist(e)); 55 | return new PSet.from(from); 56 | } 57 | else if(from is Iterable) { 58 | from = from.map((e) => persist(e)); 59 | return new PVec.from(from); 60 | } 61 | else { 62 | return from; 63 | } 64 | } 65 | 66 | /// Alias for [persist] 67 | per(from) => persist(from); 68 | 69 | class None{ 70 | const None(); 71 | } 72 | 73 | const _none = const None(); 74 | final _getNone = () => _none; 75 | bool _isNone(val) => val == _none; 76 | 77 | /** 78 | * Looks up the element given by the [path] of keys and indices 79 | * in the [structure] of Maps and Vectors. 80 | * 81 | * If the [path] does not exist, [orElse] is called to obtain the 82 | * return value. Default [orElse] throws exception. 83 | */ 84 | lookupIn(PersistentIndexedCollection structure, List path, {notFound}) => 85 | _lookupIn(structure, path.iterator, notFound: notFound); 86 | 87 | _lookupIn(dynamic s, Iterator path, {notFound}) { 88 | if(!path.moveNext()) return s; 89 | if(s is PMap) { 90 | return _lookupIn(s.get(path.current, notFound), path, notFound: notFound); 91 | } 92 | else if(s is PVec) { 93 | return _lookupIn(s.get(path.current, notFound), path, notFound: notFound); 94 | } 95 | else if(s is TMap) { 96 | return _lookupIn(s.get(path.current, notFound), path, notFound: notFound); 97 | } 98 | else if(s is TVec) { 99 | return _lookupIn(s.get(path.current, notFound), path, notFound: notFound); 100 | } 101 | else { 102 | throw new Exception('This should not happen'); 103 | } 104 | } 105 | 106 | /** 107 | * Inserts the [value] to the position given by the [path] of keys and indices 108 | * in the [structure] of Maps and Vectors. 109 | * 110 | * This will not create any middleway structures. 111 | */ 112 | PersistentCollection insertIn(PersistentIndexedCollection structure, Iterable path, dynamic value) => 113 | _insertIn(structure, path.iterator..moveNext(), value); 114 | 115 | PersistentCollection _insertIn(s, Iterator path, dynamic value) { 116 | var current = path.current; 117 | if(path.moveNext()) { //path continues 118 | if(s is PMap) { 119 | return s.assoc(current, _insertIn(s.get(current), path, value)); 120 | } 121 | else if(s is PVec) { 122 | return s.set(current, _insertIn(s.get(current), path, value)); 123 | } 124 | else if(s is TMap) { 125 | return s.doAssoc(current, _insertIn(s.get(current), path, value)); 126 | } 127 | else if(s is TVec) { 128 | return s.doSet(current, _insertIn(s.get(current), path, value)); 129 | } 130 | else { 131 | throw new Exception('This should not happen'); 132 | } 133 | } 134 | else { 135 | if(s is PMap) { 136 | return s.assoc(current, value); 137 | } 138 | else if(s is PVec) { 139 | if(current == s.length) { 140 | return s.push(value); 141 | } 142 | return s.set(current, value); 143 | } 144 | else if(s is TMap) { 145 | return s.doAssoc(current, value); 146 | } 147 | else if(s is TVec) { 148 | if(current == s.length) { 149 | return s.doPush(value); 150 | } 151 | return s.doSet(current, value); 152 | } 153 | else { 154 | throw new Exception('This should not happen'); 155 | } 156 | } 157 | } 158 | 159 | /** 160 | * Removes the element given by the [path] of keys and indices 161 | * in the [structure] of Maps and Vectors. 162 | * 163 | * If the [path] does not exist and [safe] is not `true`, exception is thrown. 164 | * If the [path] does not exist and [safe] is specified as `true`, 165 | * the same map is returned. 166 | */ 167 | PersistentCollection deleteIn(PersistentIndexedCollection structure, List path, {bool safe: false}) => 168 | _deleteIn(structure, path.iterator..moveNext(), safe: safe); 169 | 170 | PersistentCollection _deleteIn(s, Iterator path, {bool safe: false}) { 171 | var current = path.current; 172 | if(path.moveNext()) { //path continues 173 | if(s is PMap) { 174 | var deleted = _deleteIn(s.get(current), path, safe: safe); 175 | return s.assoc(current, deleted); 176 | } 177 | else if(s is PVec) { 178 | var deleted = _deleteIn(s.get(current), path, safe: safe); 179 | return s.set(current, deleted); 180 | } 181 | else if(s is TMap) { 182 | var deleted = _deleteIn(s.get(current), path, safe: safe); 183 | return s.doAssoc(current, deleted); 184 | } 185 | else if(s is TVec) { 186 | var deleted = _deleteIn(s.get(current), path, safe: safe); 187 | return s.doSet(current, deleted); } 188 | else { 189 | throw new Exception('This should not happen'); 190 | } 191 | } 192 | else { 193 | if(s is PMap) { 194 | return s.delete(current); 195 | } 196 | else if(s is PVec) { 197 | if(s.length - 1 == current) return s.pop(); 198 | else throw new Exception('Cannot delete non last element in PersistentVector'); 199 | } 200 | else if(s is TMap) { 201 | return s.doDelete(current); 202 | } 203 | else if(s is TVec) { 204 | if(s.length - 1 == current) return s.doPop(); 205 | else throw new Exception('Cannot delete non last element in TransientVector'); 206 | } 207 | else { 208 | throw new Exception('This should not happen'); 209 | } 210 | } 211 | return throw 'It cant get here...'; 212 | } 213 | -------------------------------------------------------------------------------- /lib/src/vector.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, VacuumLabs. 2 | // Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code 3 | // is governed by a BSD-style license that can be found in the LICENSE file. 4 | 5 | // Authors are listed in the AUTHORS file 6 | 7 | part of persistent; 8 | 9 | /** 10 | * A read-only vector, ordered collection of elements of type [E]. 11 | * 12 | * There is no default implementation of [ReadVector], since it just 13 | * specifies the common interface of [PVec] and [TVec]. 14 | */ 15 | abstract class ReadVector implements Iterable { 16 | 17 | /** 18 | * Returns element at given [index]. 19 | * 20 | * If the [index] is outside the array, [notFound] is returned instead. 21 | * If [notFound] is not set, [RangeError] is thrown. 22 | * 23 | * var v = new PersistentVector.from(["Hello","world"]); 24 | * v.get(0); // returns "Hello" 25 | * v.get(2, null); // returns null 26 | * v.get(2); // throws RangeError 27 | */ 28 | E get(int index, [E notFound]); 29 | 30 | /** 31 | * Returns element at given [index]. 32 | * 33 | * Throws [RangeError] if the [index] is outside the array. 34 | * 35 | * var v = new PersistentVector.from(["Hello","world"]); 36 | * print(v[0]); // prints "Hello" 37 | * print(v[2]); // throws RangeError 38 | */ 39 | E operator[](int index); 40 | 41 | /// The first element of `this` 42 | E get first; 43 | 44 | /// The last element of `this` 45 | E get last; 46 | 47 | /// Checks if it contains key [key]. 48 | bool hasKey(int key); 49 | } 50 | 51 | 52 | /** 53 | * A persistent vector, resizable ordered collection of elements of type [E]. 54 | * 55 | * Persistent data structure is an immutable structure, that provides effective 56 | * creation of slightly mutated copies. 57 | */ 58 | abstract class PVec implements ReadVector, PersistentIndexedCollection { 59 | 60 | /** 61 | * Returns a new vector identical to `this` except that 62 | * element at [index] is [value]. 63 | * 64 | * Throws [RangeError] if the [index] is outside the array. 65 | * 66 | * var v = new PersistentVector.from(["A","B"]); 67 | * v.set(1,":)"); // returns ["A",":)"] 68 | * v.set(0,":("); // returns [":(","B"] 69 | * v.set(2,":D"); // throws RangeError 70 | */ 71 | PVec set(int index, E value); 72 | 73 | /** 74 | * Returns a new vector identical to `this` except that 75 | * the [value] is appended to its end. 76 | * 77 | * var v = new PersistentVector.from(["one","two"]); 78 | * v.push("three"); // returns ["one","two","three"] 79 | * v.push("four"); // returns ["one","two","four"] 80 | */ 81 | PVec push(E value); 82 | 83 | /** 84 | * Returns a new vector identical to `this` except that 85 | * the last element is removed. 86 | * 87 | * Throws [RangeError] if `this` is empty 88 | * 89 | * var v = new PersistentVector.from(["one","two"]); 90 | * v.pop(); // returns ["one"] 91 | * v.pop(); // still returns ["one"] 92 | * new PersistentVector.from([]).pop(); // throws RangeError 93 | */ 94 | PVec pop(); 95 | 96 | /** 97 | * Returns a transient copy of `this`. 98 | * 99 | * This is ussualy called to do some changes and 100 | * then create a new [PVec]. 101 | * 102 | * var persistent1 = new PersistentVector.from([1]); 103 | * var transient = persistent1.asTransient(); 104 | * transient.doPush(2); 105 | * var persistent2 = new transient.asPersistent(); // persistent2 is now [1,2] 106 | */ 107 | TVec asTransient(); 108 | 109 | /** 110 | * Creates an empty [PVec] using its default implementation. 111 | */ 112 | factory PVec() => new _PersistentVectorImpl.empty(); 113 | 114 | /** 115 | * Creates an [PVec] filled by [values] 116 | * using its default implementation. 117 | */ 118 | factory PVec.from(Iterable values) => new _PersistentVectorImpl.from(values); 119 | 120 | /** 121 | * Creates transient copy of `this`, lets it to be modified by [change] 122 | * and returns persistent result. 123 | * 124 | * var persistent1 = new PersistentVector.from([1,2]); 125 | * var persistent2 = persistent1.withTransient((v){ 126 | * v.doPush(3); 127 | * }); 128 | */ 129 | PVec withTransient(void change(TVec vect)); 130 | 131 | 132 | /** 133 | * The equality operator. 134 | * 135 | * Two persistent vectors are equal if and only if they have same lengths, 136 | * and for each index, the values at it are equal. 137 | */ 138 | bool operator==(other); 139 | 140 | /* 141 | * The documentation is inherited from the Object 142 | */ 143 | int get hashCode; 144 | } 145 | 146 | 147 | /** 148 | * A transient vector, resizable ordered collection of elements of type [E]. 149 | * 150 | * Transient data structure is a mutable structure, which can be efficiently 151 | * converted to the persistent data structure. It is usually created from 152 | * a persistent structure to apply some changes and obtain a new persistent 153 | * structure. 154 | */ 155 | abstract class TVec implements ReadVector { 156 | 157 | /** 158 | * Sets the element at [index] to be [value]. 159 | * 160 | * Throws [RangeError] if the [index] is outside the array 161 | * 162 | * var v = new PersistentVector.from(["A","B"]).asTransient(); 163 | * v.set[1] = ":)"; // v is now ["A",":)"] 164 | * v.set[0] = ":("; // v is now [":(",":)"] 165 | * v.set[2] = ":D"; // throws RangeError 166 | * 167 | */ 168 | void operator []=(int index, E value); 169 | 170 | /** 171 | * Sets the element at [index] to be [value]. 172 | * 173 | * Throws [RangeError] if the [index] is outside the array 174 | * 175 | * var v = new PersistentVector.from(["A","B"]).asTransient(); 176 | * v.doSet(1,":)"); // v is now ["A",":)"] 177 | * v.doSet(0,":("); // v is now [":(",":)"] 178 | * v.doSet(2,":D"); // throws RangeError 179 | */ 180 | void doSet(int index, E value); 181 | 182 | /** 183 | * Appends [value] to the end of `this`. 184 | * 185 | * var v = new PersistentVector.from(["one","two"]).asTransient(); 186 | * v.doPush("three"); // v is now ["one","two","three"] 187 | * v.doPush("four"); // v is now ["one","two","three","four"] 188 | */ 189 | void doPush(E value); 190 | 191 | /** 192 | * Removes the last element of `this`. 193 | * 194 | * Throws [RangeError] if `this` is empty 195 | * 196 | * var v = new PersistentVector.from(["one","two"]).asTransient(); 197 | * v.doPop(); // v is now ["one"] 198 | * v.doPop(); // v is now [] 199 | * v.doPop(); // throws RangeError 200 | */ 201 | void doPop(); 202 | 203 | /** 204 | * Returns a persistent copy of `this`. 205 | * 206 | * This is ussualy called when changes to `this` 207 | * are finished 208 | * 209 | * var persistent1 = new PersistentVector.from([1]); 210 | * var transient = persistent1.asTransient(); 211 | * transient.doPush(2); 212 | * var persistent2 = new transient.asPersistent(); 213 | */ 214 | PVec asPersistent(); 215 | } 216 | -------------------------------------------------------------------------------- /test/randomized_vector_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, VaccumLabs. 2 | // Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code 3 | // is governed by a BSD-style license that can be found in the LICENSE file. 4 | 5 | // Authors are listed in the AUTHORS file 6 | 7 | library randomized_vector_test; 8 | 9 | import 'package:unittest/unittest.dart'; 10 | import 'package:vacuum_persistent/persistent.dart'; 11 | import 'dart:core'; 12 | import 'utils.dart'; 13 | 14 | main() { 15 | run(10000, print_fn: (message) => print(message)); 16 | print('Test successfully finished'); 17 | } 18 | 19 | run(n, {print_fn}) { 20 | if (print_fn == null){ 21 | print_fn = (msg) => null; 22 | } 23 | doTest(n, print_fn); 24 | } 25 | 26 | doTest(operationsCnt, print_fn){ 27 | 28 | assertDeeplyEquals(Iterable a, Iterable b) { 29 | var listA = new List.from(a); 30 | var listB = new List.from(b); 31 | expect(listA, orderedEquals(listB)); 32 | } 33 | 34 | assertInstancesAreSame(Map impls) { 35 | Map prevInst; 36 | for (String name in impls.keys) { 37 | Map inst = impls[name]; 38 | if (prevInst != null) { 39 | assertDeeplyEquals(prevInst['instance'], inst['instance']); 40 | } 41 | prevInst = inst; 42 | } 43 | } 44 | 45 | Map impls = { 46 | 'persistent': { 47 | 'create': () => new PVec(), 48 | 'bulkInsert': (PVec ve, List updateWith) => 49 | updateWith.fold(ve, (ve, e) => ve.push(e)), 50 | 'bulkPop': (PVec ve, int count) => 51 | new List.filled(count, null).fold(ve, (ve, e) => ve.pop()), 52 | 'bulkChange': (PVec ve, Map changes) => 53 | changes.keys.fold(ve, (ve, key) => ve.set(key, changes[key])), 54 | 'deepCopy': (PVec ve) => ve, 55 | }, 56 | 'model': { 57 | 'create': () => [], 58 | 'bulkInsert': (List ve, List updateWith) => 59 | updateWith.fold(ve, (ve, e) => ve.sublist(0)..add(e)), 60 | 'bulkPop': (List ve, int count) => 61 | new List.filled(count, null).fold(ve, (ve, e) => ve.sublist(0, ve.length-1)), 62 | 'bulkChange': (List ve, Map changes) => 63 | changes.keys.fold(ve.sublist(0), (List ve, key) => ve..removeAt(key)..insert(key, changes[key])), 64 | 'deepCopy': (List ve) => ve.sublist(0), 65 | }, 66 | 'transient': { 67 | 'create': () => new PVec().asTransient(), 68 | 'bulkInsert': (TVec ve, List updateWith) { 69 | updateWith.forEach((e) => ve.doPush(e)); 70 | return ve; 71 | }, 72 | 'bulkPop': (TVec ve, int count) { 73 | for (int i = 0; i < count; i++) ve.doPop(); 74 | return ve; 75 | }, 76 | 'bulkChange': (TVec ve, Map changes) { 77 | changes.forEach((k, v) => ve.doSet(k, v)); 78 | return ve; 79 | }, 80 | 'deepCopy': (TVec ve) => new PVec.from(ve).asTransient(), 81 | }, 82 | 'withTransient': { 83 | 'create': () => new PVec(), 84 | 'bulkInsert': (PVec ve, List updateWith) => 85 | ve.withTransient((tv) { 86 | updateWith.forEach((e) => tv.doPush(e)); 87 | }), 88 | 'bulkPop': (PVec ve, int count) => 89 | ve.withTransient((tv) { 90 | for (int i = 0; i < count; i++) tv.doPop(); 91 | }), 92 | 'bulkChange': (PVec ve, Map changes) => 93 | ve.withTransient((tv) { 94 | changes.forEach((k, v) => tv.doSet(k, v)); 95 | }), 96 | 'deepCopy': (PVec ve) => ve, 97 | }, 98 | }; 99 | 100 | randomlyChangeImpl(m) { 101 | if (probability(0.1)) { 102 | if (m is PVec) { 103 | return m.asTransient(); 104 | } else { 105 | return m.asPersistent(); 106 | } 107 | } else { 108 | return m; 109 | } 110 | } 111 | 112 | impl_for(ve) { 113 | if (ve is TVec) { 114 | return impls['transient']; 115 | } else { 116 | return impls['persistent']; 117 | } 118 | } 119 | 120 | impls.addAll({ 121 | 'randomlyChangingPersistentTransient': { 122 | 'create': () => new PVec(), 123 | 'bulkInsert': (ve, List updateWith) => 124 | randomlyChangeImpl(impl_for(ve)['bulkInsert'](ve, updateWith)), 125 | 'bulkPop': (ve, int count) => 126 | randomlyChangeImpl(impl_for(ve)['bulkPop'](ve, count)), 127 | 'bulkChange': (ve, Map changes) => 128 | randomlyChangeImpl(impl_for(ve)['bulkChange'](ve, changes)), 129 | 'deepCopy': (ve) => impl_for(ve)['deepCopy'](ve), 130 | }, 131 | }); 132 | 133 | test('Random Vector Test', () { 134 | Map oldImpls = {}; 135 | 136 | impls.forEach((name, impl) { 137 | oldImpls[name] = {}; 138 | impl['instance'] = impl['create'](); 139 | }); 140 | 141 | for (int i = 0; i < operationsCnt; i++) { 142 | PVec vec = impls['persistent']['instance']; 143 | 144 | if (probability(0.01) || oldImpls['persistent'].isEmpty) { 145 | print_fn('saving old instances'); 146 | impls.forEach((name, impl) { 147 | oldImpls[name]['instance'] = impl['deepCopy'](impl['instance']); 148 | }); 149 | } 150 | print_fn('$i/$operationsCnt: current length: ${vec.length}'); 151 | 152 | assertInstancesAreSame(impls); 153 | assertInstancesAreSame(oldImpls); 154 | 155 | if (probability(1/3)) { 156 | // 33% Insert 157 | int bulkCount = r.nextInt(1000); 158 | List updateWith = []; 159 | for (int i = 0; i < bulkCount; i++) { 160 | updateWith.add(r.nextInt(47474747)); 161 | } 162 | impls.forEach((name, impl) { 163 | impls[name]['instance'] = impl['bulkInsert'](impl['instance'], updateWith); 164 | }); 165 | } else if (probability(1/2)) { 166 | // 33% Delete 167 | int maxIndex = impls['persistent']['instance'].length; 168 | if (maxIndex == 0) continue; 169 | int bulkCount; 170 | // sometimes, delete the whole list 171 | if(probability(0.05)){ 172 | bulkCount = vec.length; 173 | } else { 174 | bulkCount = r.nextInt(vec.length); 175 | } 176 | impls.forEach((name, impl) { 177 | impls[name]['instance'] = impl['bulkPop'](impl['instance'], bulkCount); 178 | }); 179 | } else { 180 | // 33% Change 181 | Map updateWith = {}; 182 | int maxIndex = impls['persistent']['instance'].length; 183 | if (maxIndex == 0) continue; 184 | int bulkCount = r.nextInt(maxIndex); 185 | for (int i = 0; i < bulkCount; i++) { 186 | updateWith[r.nextInt(maxIndex)] = r.nextInt(47474747); 187 | } 188 | impls.forEach((name, impl) { 189 | impls[name]['instance'] = impl['bulkChange'](impl['instance'], updateWith); 190 | }); 191 | } 192 | 193 | // test iterating, equality and hashCode 194 | PVec copy = new PVec(); 195 | PVec pv = impls['persistent']['instance']; 196 | for(var item in pv) { 197 | copy = copy.push(item); 198 | } 199 | expect(pv == copy, isTrue); 200 | expect(pv.hashCode == copy.hashCode, isTrue); 201 | PVec not_copy = copy.push('something completely different'); 202 | expect(pv == not_copy, isFalse); 203 | expect(pv.hashCode == not_copy.hashCode, isFalse); 204 | 205 | // test 'empty' 206 | num sum = 0; 207 | for (var impl in impls.keys){ 208 | sum += impls[impl]['instance'].isEmpty?0:1; 209 | } 210 | // all impementations must add the same 0 or 1 value to the sum 211 | expect(sum % impls.length, equals(0)); 212 | 213 | } 214 | }); 215 | } -------------------------------------------------------------------------------- /benchmark/preamble/d8.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | // Javascript preamble, that lets the output of dart2js run on V8's d8 shell. 6 | 7 | // Node wraps files and provides them with a different `this`. The global 8 | // `this` can be accessed through `global`. 9 | 10 | var self = this; 11 | if (typeof global != "undefined") self = global; // Node.js. 12 | 13 | (function(self) { 14 | // Using strict mode to avoid accidentally defining global variables. 15 | "use strict"; // Should be first statement of this function. 16 | 17 | // Location (Uri.base) 18 | 19 | var workingDirectory; 20 | // TODO(sgjesse): This does not work on Windows. 21 | if (typeof os == "object" && "system" in os) { 22 | // V8. 23 | workingDirectory = os.system("pwd"); 24 | var length = workingDirectory.length; 25 | if (workingDirectory[length - 1] == '\n') { 26 | workingDirectory = workingDirectory.substring(0, length - 1); 27 | } 28 | } else if (typeof process != "undefined" && 29 | typeof process.cwd == "function") { 30 | // Node.js. 31 | workingDirectory = process.cwd(); 32 | } 33 | self.location = { href: "file://" + workingDirectory + "/" }; 34 | 35 | // Event loop. 36 | 37 | // Task queue as cyclic list queue. 38 | var taskQueue = new Array(8); // Length is power of 2. 39 | var head = 0; 40 | var tail = 0; 41 | var mask = taskQueue.length - 1; 42 | function addTask(elem) { 43 | taskQueue[head] = elem; 44 | head = (head + 1) & mask; 45 | if (head == tail) _growTaskQueue(); 46 | } 47 | function removeTask() { 48 | if (head == tail) return; 49 | var result = taskQueue[tail]; 50 | taskQueue[tail] = undefined; 51 | tail = (tail + 1) & mask; 52 | return result; 53 | } 54 | function _growTaskQueue() { 55 | // head == tail. 56 | var length = taskQueue.length; 57 | var split = head; 58 | taskQueue.length = length * 2; 59 | if (split * 2 < length) { // split < length / 2 60 | for (var i = 0; i < split; i++) { 61 | taskQueue[length + i] = taskQueue[i]; 62 | taskQueue[i] = undefined; 63 | } 64 | head += length; 65 | } else { 66 | for (var i = split; i < length; i++) { 67 | taskQueue[length + i] = taskQueue[i]; 68 | taskQueue[i] = undefined; 69 | } 70 | tail += length; 71 | } 72 | mask = taskQueue.length - 1; 73 | } 74 | 75 | // Mapping from timer id to timer function. 76 | // The timer id is written on the function as .$timerId. 77 | // That field is cleared when the timer is cancelled, but it is not returned 78 | // from the queue until its time comes. 79 | var timerIds = {}; 80 | var timerIdCounter = 1; // Counter used to assing ids. 81 | 82 | // Zero-timer queue as simple array queue using push/shift. 83 | var zeroTimerQueue = []; 84 | 85 | function addTimer(f, ms) { 86 | var id = timerIdCounter++; 87 | f.$timerId = id; 88 | timerIds[id] = f; 89 | if (ms == 0) { 90 | zeroTimerQueue.push(f); 91 | } else { 92 | addDelayedTimer(f, ms); 93 | } 94 | return id; 95 | } 96 | 97 | function nextZeroTimer() { 98 | while (zeroTimerQueue.length > 0) { 99 | var action = zeroTimerQueue.shift(); 100 | if (action.$timerId !== undefined) return action; 101 | } 102 | } 103 | 104 | function nextEvent() { 105 | var action = removeTask(); 106 | if (action) { 107 | return action; 108 | } 109 | do { 110 | action = nextZeroTimer(); 111 | if (action) break; 112 | var nextList = nextDelayedTimerQueue(); 113 | if (!nextList) { 114 | return; 115 | } 116 | var newTime = nextList.shift(); 117 | advanceTimeTo(newTime); 118 | zeroTimerQueue = nextList; 119 | } while (true) 120 | var id = action.$timerId; 121 | clearTimerId(action, id); 122 | return action; 123 | } 124 | 125 | // Mocking time. 126 | var timeOffset = 0; 127 | var now = function() { 128 | // Install the mock Date object only once. 129 | // Following calls to "now" will just use the new (mocked) Date.now 130 | // method directly. 131 | installMockDate(); 132 | now = Date.now; 133 | return Date.now(); 134 | }; 135 | var originalDate = Date; 136 | var originalNow = originalDate.now; 137 | function advanceTimeTo(time) { 138 | timeOffset = time - originalNow(); 139 | } 140 | function installMockDate() { 141 | var NewDate = function Date(Y, M, D, h, m, s, ms) { 142 | if (this instanceof Date) { 143 | // Assume a construct call. 144 | switch (arguments.length) { 145 | case 0: return new originalDate(originalNow() + timeOffset); 146 | case 1: return new originalDate(Y); 147 | case 2: return new originalDate(Y, M); 148 | case 3: return new originalDate(Y, M, D); 149 | case 4: return new originalDate(Y, M, D, h); 150 | case 5: return new originalDate(Y, M, D, h, m); 151 | case 6: return new originalDate(Y, M, D, h, m, s); 152 | default: return new originalDate(Y, M, D, h, m, s, ms); 153 | } 154 | } 155 | return new originalDate(originalNow() + timeOffset).toString(); 156 | }; 157 | NewDate.UTC = originalDate.UTC; 158 | NewDate.parse = originalDate.parse; 159 | NewDate.now = function now() { return originalNow() + timeOffset; }; 160 | NewDate.prototype = originalDate.prototype; 161 | originalDate.prototype.constructor = NewDate; 162 | Date = NewDate; 163 | } 164 | 165 | // Heap priority queue with key index. 166 | // Each entry is list of [timeout, callback1 ... callbackn]. 167 | var timerHeap = []; 168 | var timerIndex = {}; 169 | function addDelayedTimer(f, ms) { 170 | var timeout = now() + ms; 171 | var timerList = timerIndex[timeout]; 172 | if (timerList == null) { 173 | timerList = [timeout, f]; 174 | timerIndex[timeout] = timerList; 175 | var index = timerHeap.length; 176 | timerHeap.length += 1; 177 | bubbleUp(index, timeout, timerList); 178 | } else { 179 | timerList.push(f); 180 | } 181 | } 182 | 183 | function nextDelayedTimerQueue() { 184 | if (timerHeap.length == 0) return null; 185 | var result = timerHeap[0]; 186 | var last = timerHeap.pop(); 187 | if (timerHeap.length > 0) { 188 | bubbleDown(0, last[0], last); 189 | } 190 | return result; 191 | } 192 | 193 | function bubbleUp(index, key, value) { 194 | while (index != 0) { 195 | var parentIndex = (index - 1) >> 1; 196 | var parent = timerHeap[parentIndex]; 197 | var parentKey = parent[0]; 198 | if (key > parentKey) break; 199 | timerHeap[index] = parent; 200 | index = parentIndex; 201 | } 202 | timerHeap[index] = value; 203 | } 204 | 205 | function bubbleDown(index, key, value) { 206 | while (true) { 207 | var leftChildIndex = index * 2 + 1; 208 | if (leftChildIndex >= timerHeap.length) break; 209 | var minChildIndex = leftChildIndex; 210 | var minChild = timerHeap[leftChildIndex]; 211 | var minChildKey = minChild[0]; 212 | var rightChildIndex = leftChildIndex + 1; 213 | if (rightChildIndex < timerHeap.length) { 214 | var rightChild = timerHeap[rightChildIndex]; 215 | var rightKey = rightChild[0]; 216 | if (rightKey < minChildKey) { 217 | minChildIndex = rightChildIndex; 218 | minChild = rightChild; 219 | minChildKey = rightKey; 220 | } 221 | } 222 | if (minChildKey > key) break; 223 | timerHeap[index] = minChild; 224 | index = minChildIndex; 225 | } 226 | timerHeap[index] = value; 227 | } 228 | 229 | function addInterval(f, ms) { 230 | var id = timerIdCounter++; 231 | function repeat() { 232 | // Reactivate with the same id. 233 | repeat.$timerId = id; 234 | timerIds[id] = repeat; 235 | addDelayedTimer(repeat, ms); 236 | f(); 237 | } 238 | repeat.$timerId = id; 239 | timerIds[id] = repeat; 240 | addDelayedTimer(repeat, ms); 241 | return id; 242 | } 243 | 244 | function cancelTimer(id) { 245 | var f = timerIds[id]; 246 | if (f == null) return; 247 | clearTimerId(f, id); 248 | } 249 | 250 | function clearTimerId(f, id) { 251 | f.$timerId = undefined; 252 | delete timerIds[id]; 253 | } 254 | 255 | function eventLoop(action) { 256 | while (action) { 257 | try { 258 | action(); 259 | } catch (e) { 260 | if (typeof onerror == "function") { 261 | onerror(e, null, -1); 262 | } else { 263 | throw e; 264 | } 265 | } 266 | action = nextEvent(); 267 | } 268 | } 269 | 270 | // Global properties. "self" refers to the global object, so adding a 271 | // property to "self" defines a global variable. 272 | self.dartMainRunner = function(main, args) { 273 | // Initialize. 274 | var action = function() { main(args); } 275 | eventLoop(action); 276 | }; 277 | self.setTimeout = addTimer; 278 | self.clearTimeout = cancelTimer; 279 | self.setInterval = addInterval; 280 | self.clearInterval = cancelTimer; 281 | self.scheduleImmediate = addTask; 282 | self.self = self; 283 | })(self); 284 | -------------------------------------------------------------------------------- /test/functions_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, VaccumLabs. 2 | // Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code 3 | // is governed by a BSD-style license that can be found in the LICENSE file. 4 | 5 | // Authors are listed in the AUTHORS file 6 | 7 | library functions_list_test; 8 | 9 | import 'package:vacuum_persistent/persistent.dart'; 10 | import 'package:unittest/unittest.dart'; 11 | 12 | main() { 13 | run(); 14 | } 15 | 16 | eqPer(x) => equals(persist(x)); 17 | 18 | run() { 19 | group("conj", (){ 20 | test("- PersistentVector", (){ 21 | PVec s = persist([1, 2, 3]); 22 | expect(conj(s, 5), equals(s.push(5))); 23 | expect(conj(s, 5, 6), equals(s.push(5).push(6))); 24 | expect(conj(s, null), equals(s.push(null))); 25 | }); 26 | 27 | test("- PersistentMap", (){ 28 | PMap s = persist({'a': 5}); 29 | expect(conj(s, new Pair('b', 6)), equals(s.assoc('b', 6))); 30 | expect(conj(s, ['b', 6]), equals(s.assoc('b', 6))); 31 | expect(() => conj(s, 6), throws); 32 | }); 33 | 34 | test("- PSet", (){ 35 | PSet s = persist(new Set.from(['a', 'c'])); 36 | expect(conj(s, "b"), equals(s.insert('b'))); 37 | }); 38 | 39 | test("- List Map Set throws", () { 40 | expect(() => conj([], 5), throws); 41 | expect(() => conj({}, [1, 2]), throws); 42 | expect(() => conj(new Set.from({}), [1, 2]), throws); 43 | }); 44 | }); 45 | 46 | group("into", (){ 47 | test("- PersistentVector", (){ 48 | PVec s = persist([1, 2, 3]); 49 | expect(into(s, [5, 5]), equals(conj(s, 5, 5))); 50 | }); 51 | test("- PersistentMap", (){ 52 | PMap s = persist({'a': 5}); 53 | expect(into(s, [new Pair('b', 6), new Pair('a', 8)]), equals(s.assoc('b', 6).assoc('a', 8))); 54 | }); 55 | 56 | test("- PSet", (){ 57 | PSet s = persist(new Set.from(['a', 'c'])); 58 | expect(into(s, ["b"]), equals(s.insert('b'))); 59 | }); 60 | }); 61 | 62 | group("assoc", () { 63 | test("- PersistentVector", (){ 64 | PVec s = persist([1, 2, 3]); 65 | expect(assoc(s, 1, 7), equals(s.set(1, 7))); 66 | expect(assoc(s, 1, 7, 1, 8), equals(s.set(1, 8))); 67 | expect(assoc(s, 1, 7, 0, 8), equals(s.set(1, 7).set(0, 8))); 68 | }); 69 | test("- PersistentMap", (){ 70 | PMap s = persist({'a': 5}); 71 | expect(assoc(s, 'b', 8, 'c', 10), equals(s.assoc('b', 8).assoc('c', 10))); 72 | }); 73 | 74 | test("- PSet", (){ 75 | PSet s = persist(new Set.from(['a', 'c'])); 76 | expect(() => assoc(s, "b", 5), throws); 77 | }); 78 | 79 | test('- throw when key is specified but value not', () { 80 | var s = persist({'a' : 5}); 81 | //key is provided but value is not 82 | expect(() => assoc(s, '0', 1, '5'), throws); 83 | }); 84 | }); 85 | 86 | group("dissoc", () { 87 | test("- PersistentMap", (){ 88 | PMap s = persist({'a': 5, 'b': 6, 'c': 7}); 89 | expect(dissoc(s, 'b', 'c'), eqPer({'a': 5})); 90 | expect(dissoc(s, 'b', 'd'), eqPer({'a': 5, 'c': 7})); 91 | }); 92 | 93 | test("- PSet", (){ 94 | PSet s = persist(new Set.from(['a', 'b', 'c', 'd'])); 95 | expect(dissoc(s, 'b', 'c'), unorderedEquals(['a', 'd'])); 96 | expect(dissoc(s, 'b', 'd'), unorderedEquals(['a', 'c'])); 97 | }); 98 | 99 | test("- PersistentVector", (){ 100 | PVec s = persist(['a', 5, 'b', 6, 'c', 7]); 101 | expect(dissoc(s, 0, 3), eqPer([5, 'b', 'c', 7])); 102 | expect(dissoc(s, 3, 0), eqPer([5, 'b', 'c', 7])); 103 | expect(dissoc(s, -1), equals(s)); 104 | expect(dissoc(s, 50), equals(s)); 105 | }); 106 | }); 107 | 108 | group("distinc", () { 109 | test("- PersistentVector", (){ 110 | PVec s = persist([1, 3, 2, 4, 3, 3]); 111 | expect(distinct(s), eqPer([1,3,2,4])); 112 | s = persist([1, null, 1, null]); 113 | expect(distinct(s), eqPer([1, null])); 114 | }); 115 | }); 116 | 117 | group("empty", () { 118 | test("- PersistentVector", (){ 119 | PVec s = persist([1, 2, 3]); 120 | expect(empty(s), eqPer([])); 121 | }); 122 | test("- PersistentMap", (){ 123 | PMap s = persist({'a': 5}); 124 | expect(empty(s), eqPer({})); 125 | }); 126 | 127 | test("- PSet", (){ 128 | PSet s = persist(new Set.from(['a', 'c'])); 129 | expect(empty(s), eqPer(new Set())); 130 | }); 131 | }); 132 | 133 | group("hasKey", () { 134 | test("- PersistentVector", (){ 135 | PVec s = persist([1, 2, 3]); 136 | expect(hasKey(s, -1), equals(false)); 137 | expect(hasKey(s, 0), equals(true)); 138 | expect(hasKey(s, 2), equals(true)); 139 | expect(hasKey(s, 3), equals(false)); 140 | }); 141 | test("- PersistentMap", (){ 142 | PMap s = persist({'a': 5}); 143 | expect(hasKey(s, 'a'), equals(true)); 144 | expect(hasKey(s, 'b'), equals(false)); 145 | expect(hasKey(s, 2), equals(false)); 146 | }); 147 | 148 | test("- PSet", (){ 149 | PSet s = persist(new Set.from(['a', 'c'])); 150 | expect(hasKey(s, 'a'), equals(true)); 151 | expect(hasKey(s, 'b'), equals(false)); 152 | }); 153 | }); 154 | 155 | group("get", () { 156 | test("- PersistentVector", (){ 157 | PVec s = persist([1, 2, 3]); 158 | expect(get(s, 0), equals(1)); 159 | expect(get(s, 2), equals(3)); 160 | expect(get(s, -1, 10), equals(10)); 161 | expect(() => get(s, -1), throws); 162 | }); 163 | test("- PersistentMap", (){ 164 | PMap s = persist({'a': 5, 'b': {'c': 1}}); 165 | expect(get(s, 'a'), equals(5)); 166 | expect(get(s, 'b'), eqPer({'c':1})); 167 | expect(get(s, 'c', 10), equals(10)); 168 | expect(() => get(s, 'c'), throws); 169 | }); 170 | 171 | test("- PSet", (){ 172 | PSet s = persist(new Set.from(['a', 'c'])); 173 | expect(get(s, 'a'), equals('a')); 174 | expect(get(s, 'c'), equals('c')); 175 | expect(get(s, 'd', 10), equals(10)); 176 | expect(() => get(s, 'd'), throws); 177 | }); 178 | }); 179 | 180 | 181 | group("getIn", () { 182 | test("- Tree structure", (){ 183 | PVec s = persist([1, {'a': {'b': 4}}, 3]); 184 | expect(getIn(s, []), equals(s)); 185 | expect(getIn(s, [0]), equals(1)); 186 | expect(getIn(s, [1]), eqPer({'a': {'b': 4}})); 187 | expect(getIn(s, [1, 'a']), eqPer({'b': 4})); 188 | expect(getIn(s, [1, 'a', 'b']), equals(4)); 189 | expect(getIn(s, [1, 'c', 'c', 'c'], 15), equals(15)); 190 | expect(() => getIn(s, [1, 'c', 'c', 'c']), throws); 191 | expect(getIn(s, [1, 'a', 'c'], 17), equals(17)); 192 | expect(() => getIn(s, [1, 'a', 'c']), throws); 193 | }); 194 | }); 195 | 196 | group("find", () { 197 | test("- PersistentVector", (){ 198 | PVec s = persist([1, 2, 3]); 199 | expect(() => find(s, -1), throws); 200 | expect(find(s, -1, 17), equals(new Pair(-1, 17))); 201 | expect(find(s, 0, 1), equals(new Pair(0, 1))); 202 | expect(find(s, 1, 2), equals(new Pair(1, 2))); 203 | }); 204 | test("- PersistentMap", (){ 205 | PMap s = persist({'a': 5}); 206 | expect(() => find(s, -1), throws); 207 | expect(find(s, -1, 17), equals(new Pair(-1, 17))); 208 | expect(find(s, 'a'), equals(new Pair('a', 5))); 209 | expect(find(s, 'a', 17), equals(new Pair('a', 5))); 210 | }); 211 | 212 | test("- PSet", (){ 213 | PSet s = persist(new Set.from(['a', 'c'])); 214 | expect(() => find(s, -1), throws); 215 | expect(find(s, -1, 17), equals(new Pair(-1, 17))); 216 | expect(find(s, 'a'), equals(new Pair('a', 'a'))); 217 | expect(find(s, 'a', 17), equals(new Pair('a', 'a'))); 218 | }); 219 | }); 220 | 221 | group("assocIn", () { 222 | test("- Tree structure", (){ 223 | PVec s = persist([1, {'a': {'b': 4}}, 3]); 224 | expect(assocIn(s, [], persist({'c':10})), eqPer({'c': 10})); 225 | expect(assocIn(s, [], 5), equals(5)); 226 | expect(assocIn(s, [0], 5), equals(assoc(s, 0, 5))); 227 | expect(assocIn(s, [1, 'a'], 17), eqPer([1, {'a': 17}, 3])); 228 | expect(assocIn(s, [1, 'a', 'b'], 17), eqPer([1, {'a': {'b':17}}, 3])); 229 | expect(assocIn(s, [1, 'a', 'c'], {'c':5}), eqPer([1, {'a': {'b': 4, 'c':{'c':5}}}, 3])); 230 | expect(() => assocIn(s, [1, 'c', 'c', 'c'], 14), throws); 231 | }); 232 | }); 233 | 234 | group("updateIn", () { 235 | test("- Tree structure", (){ 236 | PVec s = persist([1, {'a': {'b': 4}}, 3]); 237 | inc(x) => ++x; 238 | expect(updateIn(s, [], (x) => persist({})), eqPer({})); 239 | expect(() => updateIn(s, [], inc), throws); 240 | expect(updateIn(s, [0], inc), equals(assoc(s, 0, 2))); 241 | 242 | expect(updateIn(s, [1, 'a'], (x) => assoc(x, 'b', 6)), eqPer([1, {'a': {'b': 6}}, 3])); 243 | expect(updateIn(s, [1, 'a', 'b'], inc), eqPer([1, {'a': {'b':5}}, 3])); 244 | expect(() => updateIn(s, [1, 'a', 'c'], inc), throws); 245 | 246 | maybeInc([x]) => (x == null)? 0 : ++x; 247 | expect(updateIn(s, [1, 'a', 'c'], maybeInc), eqPer([1, {'a': {'b':4, 'c': 0}}, 3])); 248 | expect(() => updateIn(s, [1, 'c', 'c', 'c'], inc), throws); 249 | }); 250 | }); 251 | 252 | group("zipmap", () { 253 | test("", () { 254 | expect(zipmap(['a', 'b', 'c'], [1, 2, 3]), eqPer({'a':1, 'b':2, 'c':3})); 255 | expect(zipmap(['a', 'b', 'c'], [1, 2, 3, 4, 5]), eqPer({'a':1, 'b':2, 'c':3})); 256 | expect(zipmap(['a', 'b', 'c', 'd', 'e'], [1, 2, 3]), eqPer({'a':1, 'b':2, 'c':3})); 257 | }); 258 | }); 259 | 260 | group("subvec", () { 261 | test("", () { 262 | var s = persist([1,2,3,4,5]); 263 | expect(subvec(s, 0), equals(s)); 264 | expect(subvec(s, 1), eqPer([2,3,4,5])); 265 | expect(subvec(s, 1, 3), eqPer([2,3])); 266 | expect(subvec(s, 1, 10), eqPer([2,3,4,5])); 267 | expect(subvec(s, 10), eqPer([])); 268 | expect(subvec(s, 1, -1), eqPer([])); 269 | }); 270 | }); 271 | 272 | group("disj", () { 273 | test("", () { 274 | var s = persist(new Set.from([1,'a','c'])); 275 | expect(disj(s, 1), eqPer(new Set.from(['a', 'c']))); 276 | expect(disj(s, 5), equals(s)); 277 | }); 278 | }); 279 | } 280 | -------------------------------------------------------------------------------- /test/randomized_map_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, VaccumLabs. 2 | // Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code 3 | // is governed by a BSD-style license that can be found in the LICENSE file. 4 | 5 | // Authors are listed in the AUTHORS file 6 | 7 | library randomized_map_test; 8 | 9 | import 'package:vacuum_persistent/persistent.dart'; 10 | import 'package:unittest/unittest.dart'; 11 | import 'dart:math'; 12 | import 'dart:core'; 13 | import 'utils.dart'; 14 | 15 | final _none = new Object(); 16 | final _getNone = () => _none; 17 | bool _isNone(val) => val == _none; 18 | 19 | const hashCodeModulo = 1000; 20 | 21 | class Value { 22 | dynamic val, hash; 23 | 24 | Value(this.val, this.hash); 25 | 26 | operator ==(other) => this.val == other.val; 27 | get hashCode => this.hash; 28 | } 29 | 30 | main() { 31 | run(10000, print_fn: (message) => print(message)); 32 | print('Test successfully finished'); 33 | } 34 | 35 | run(n, {print_fn}) { 36 | if (print_fn == null){ 37 | print_fn = (msg) => null; 38 | } 39 | doTest(n, print_fn); 40 | } 41 | 42 | doTest(operationsCnt, print_fn){ 43 | Random r = new Random(47); 44 | 45 | assertDeeplyEquals(a, b) { 46 | expect(a.length, equals(b.length)); 47 | a.keys.forEach((key) => expect(a[key], equals(b[key]))); 48 | } 49 | 50 | // test deepEquality and keys and values iterators 51 | assertInstancesAreSame(Map impls){ 52 | Map prevInst; 53 | for(String name in impls.keys){ 54 | Map inst = impls[name]; 55 | if(prevInst != null) { 56 | assertDeeplyEquals(prevInst['instance'], inst['instance']); 57 | if (probability(0.1)) { 58 | expect(prevInst['instance'].keys, unorderedEquals(inst['instance'].keys)); 59 | expect(prevInst['instance'].values, unorderedEquals(inst['instance'].values)); 60 | } 61 | } 62 | prevInst = inst; 63 | } 64 | } 65 | 66 | Map deepCopyMap(map){ 67 | Map res = new Map(); 68 | fn(k,v){res[k] = v;} 69 | if (map is Map) { 70 | map.forEach(fn); 71 | } else { 72 | map.forEachKeyValue(fn); 73 | } 74 | return res; 75 | } 76 | 77 | fn_adjust(String a) => '${a} adjusted'; 78 | 79 | Map impls = { 80 | 'map': { 81 | 'create': () => {}, 82 | 'bulkInsert': (Map me, Map updateWith) { 83 | updateWith.keys.fold(me, (_, k) => me[k] = updateWith[k]); 84 | return me; 85 | }, 86 | 'bulkDelete': (Map me, List keys) { 87 | keys.forEach((k) => me.remove(k)); 88 | return me; 89 | }, 90 | 'bulkAdjust': (Map me, List keys, adjust) { 91 | keys.forEach((k) => me[k] = adjust(me[k])); 92 | return me; 93 | }, 94 | 'deepCopy': (Map me) => deepCopyMap(me) 95 | }, 96 | 'persistent': { 97 | 'create': () => new PMap(), 98 | 'bulkInsert': (PMap me, Map updateWith) => 99 | updateWith.keys.fold(me, (me, k) => me.assoc(k, updateWith[k])), 100 | 'bulkDelete': (PMap me, List keys) => 101 | keys.fold(me, (me, k) => me.delete(k, missingOk: true)), 102 | 'bulkAdjust': (PMap me, List keys, adjust) => 103 | keys.fold(me, (me, k) => me.update(k, adjust)), 104 | 'deepCopy': (PMap me) => me 105 | }, 106 | // always transient 107 | 'transient': { 108 | 'create': () => new TMap(), 109 | 'bulkInsert': (TMap me, Map updateWith) => 110 | updateWith.keys.fold(me, (me, k) => me.doAssoc(k, updateWith[k])), 111 | 'bulkDelete': (TMap me, List keys) => 112 | keys.fold(me, (me, k) => me.doDelete(k, missingOk: true)), 113 | 'bulkAdjust': (TMap me, List keys, adjust) => 114 | keys.fold(me, (me, k) => me.doUpdate(k, adjust)), 115 | 'deepCopy': (TMap me) { 116 | TMap res = new TMap(); 117 | me.forEachKeyValue((k, v) => res.doAssoc(k, v)); 118 | return res; 119 | } 120 | }, 121 | // uses transient impl for bulk insert, delete atomicaly 122 | 'persistentWithTransient': { 123 | 'create': () => new PMap(), 124 | 'bulkInsert': (PMap me, Map updateWith) => 125 | me.withTransient((TMap me) => 126 | updateWith.keys.fold(me, (me, k) => me.doAssoc(k, updateWith[k]))), 127 | 'bulkDelete': (PMap me, List keys) => 128 | me.withTransient((TMap me) => 129 | keys.fold(me, (me, k) => me.doDelete(k, missingOk: true))), 130 | 'bulkAdjust': (PMap me, List keys, adjust) => 131 | me.withTransient((TMap me) => 132 | keys.fold(me, (me, k) => me.doUpdate(k, adjust))), 133 | 'deepCopy': (PMap me) => me 134 | }, 135 | 136 | 'randomlyChangingPersistentTransient': 'will be defined later', 137 | }; 138 | 139 | // helper for randomlyChangingPersistentTransient implementation 140 | impl_for(map){ 141 | if (map is PMap) return impls['persistent']; 142 | if (map is TMap) return impls['transient']; 143 | throw new Exception('shouldnt get here'); 144 | } 145 | 146 | randomlyChangeImpl(m){ 147 | if(probability(0.1)){ 148 | if (m is PMap){ 149 | return m.asTransient(); 150 | } else { 151 | return m.asPersistent(); 152 | } 153 | } else { 154 | return m; 155 | } 156 | } 157 | 158 | // from time to time randomly change the implementation from persistent to 159 | // transient and vice versa; may perform multiple bulk operations in one 160 | // transient state 161 | impls['randomlyChangingPersistentTransient'] = 162 | { 163 | 'create': () => new PMap(), 164 | 'bulkInsert': (me, Map updateWith) => randomlyChangeImpl(impl_for(me)['bulkInsert'](me, updateWith)), 165 | 'bulkDelete': (me, List keys) => randomlyChangeImpl(impl_for(me)['bulkDelete'](me, keys)), 166 | 'bulkAdjust': (me, List keys, adjust) => randomlyChangeImpl(impl_for(me)['bulkAdjust'](me, keys, adjust)), 167 | 'deepCopy': (me) => deepCopyMap(me) 168 | }; 169 | 170 | 171 | int range = 10000; 172 | List all_keys = []; 173 | List all_values = []; 174 | 175 | for (int i=0; i implements Iterable> { 24 | 25 | /** 26 | * Returns the value bound to [key]. 27 | * 28 | * If [key] is not bound, [notFound] is returned; if [notFound] is not set, Exception 29 | * is thrown. 30 | */ 31 | V get(K key, [V notFound]); 32 | 33 | /** 34 | * Returns the value bound to [key]. 35 | * 36 | * Throws exception if [key] is not bound. 37 | */ 38 | V operator [](K key); 39 | 40 | /** 41 | * Evaluates `f(key, value)` for each (`key`, `value`) pair in `this`. 42 | */ 43 | void forEachKeyValue(f(K key, V value)); 44 | 45 | /// Returns a mutable copy of `this`. 46 | Map toMap(); 47 | 48 | /// The keys of `this`. 49 | Iterable get keys; 50 | 51 | /// Returns true if contains [key] 52 | bool containsKey(K key); 53 | 54 | /// Returns true if contains [key] 55 | bool hasKey(K key); 56 | 57 | /// The values of `this`. 58 | Iterable get values; 59 | 60 | /// An iterator through the entries of `this`. 61 | Iterator> get iterator; 62 | 63 | /// The number of entries of `this`. 64 | int get length; 65 | } 66 | 67 | /** 68 | * A persistent map, binding keys of type [K] to values of type [V]. Null 69 | * values are supported but null keys are not. 70 | * 71 | * Persistent data structure is an immutable structure, that provides effective 72 | * creation of slightly mutated copies. 73 | * 74 | * In all the examples below `{k1: v1, k2: v2, ...}` is a shorthand for 75 | * `new PersistentMap.fromMap({k1: v1, k2: v2, ...})`. 76 | */ 77 | 78 | abstract class PMap implements ReadMap, PersistentIndexedCollection { 79 | 80 | /** Creates an empty [PMap] using its default implementation. */ 81 | factory PMap() => new _Leaf.empty(null); 82 | 83 | /** 84 | * dispatches to either PMap.fromMap or PMap.fromPairs 85 | */ 86 | factory PMap.from(dynamic source) { 87 | if (source is Map) { 88 | return new PMap.fromMap(source); 89 | } else { 90 | if (source is Iterable) { 91 | if (source.isEmpty) { 92 | return new PMap(); 93 | } else { 94 | if (source.first is Pair) { 95 | return new PMap.fromPairs(source); 96 | } 97 | } 98 | } 99 | } 100 | throw new Exception('cannot construct PMap from ${source.runtimeType} ($source)'); 101 | } 102 | 103 | /** 104 | * Creates an immutable copy of [map] using the default implementation of 105 | * [PMap]. 106 | */ 107 | factory PMap.fromMap(Map map) => new _Node.fromMap(map); 108 | 109 | /** 110 | * Creates a [PMap] from an [Iterable] of [Pair]s using the default 111 | * implementation of [PMap]. 112 | */ 113 | factory PMap.fromPairs(Iterable> pairs) => new _Node.fromPairs(pairs); 114 | 115 | /** 116 | * The equality operator. 117 | * 118 | * Two persistent maps are equal if and only if their sets of keys are equal, 119 | * and the equal keys are bound to the equal values. 120 | * 121 | * Two sets of keys are equal if and only if for each key exists 122 | * an equal key in the other set. 123 | */ 124 | bool operator== (other); 125 | 126 | /* 127 | * The documentation is inherited from the Object 128 | */ 129 | int get hashCode; 130 | 131 | /** 132 | * Returns a new map identical to `this` except that it binds [key] to 133 | * [value]. 134 | * 135 | * If [key] was bound to some `oldvalue` in `this`, it is nevertheless bound 136 | * to [value] in the new map. 137 | * 138 | * {'a': 1}.assoc('b', 2) == {'a': 1, 'b': 2} 139 | * {'a': 1, 'b': 2}.assoc('b', 3) == {'a': 3, 'b': 3} 140 | */ 141 | PMap assoc(K key, V value); 142 | 143 | /** 144 | * Returns a new map identical to `this` except that it doesn't bind [key] 145 | * anymore. 146 | * 147 | * If [key] is not bound and [missingOk] is not `true`, an Exception is thrown. 148 | * If [key] is not bound and [missingOk] is specified as `true`, 149 | * the same map is returned. [missingOk] defaults to `false`. 150 | * 151 | * {'a': 1, 'b': 2}.delete('b') == {'a': 1} 152 | * {'a': 1}.delete('b') // throws an Exception 153 | */ 154 | PMap delete(K key, {bool missingOk: false}); 155 | 156 | /** 157 | * Returns a new map identical to `this` except that the value it possibly 158 | * binds to [key] has been adjusted by [f]. 159 | * 160 | * [f] should have one of the following signatures: V f(V value), V f([V value]) 161 | * 162 | * If [key] is not bound, [f] with no arguments will be called, and the result 163 | * will be associated with [key]. If [key] is not bound and [f] cannot take no arguments, 164 | * an Exception will be thrown. 165 | * 166 | * {'a': 1, 'b': 2}.update('b', (x) => x + 1) == {'a': 1, 'b': 3} 167 | * {'a': 1}.update('b', (x) => x + 1) // throws 168 | * {'a': 2}.update('b', ([x]) => x == null ? 0 : x + 1) == {'a': 2, 'b': 0} 169 | */ 170 | // PersistentMap update(K key, dynamic f); 171 | 172 | /** 173 | * Returns a new map identical to `this` where each value has been updated by 174 | * [f]. 175 | * 176 | * {'a': 1, 'b': 2}.mapValues((x) => x + 1) == {'a': 2, 'b': 3} 177 | * {}.mapValues((x) => x + 1) == {} 178 | */ 179 | // ReadMap mapValues(f(V value)); 180 | 181 | /** 182 | * Returns a transient copy of `this`. 183 | * 184 | * This is usually called to make many changes and 185 | * then create a new [PMap]. 186 | * 187 | * var persistent1 = new PersistentMap.from({'a':1}); 188 | * var transient = persistent1.asTransient(); 189 | * transient.doAssoc({'b':2}); 190 | * var persistent2 = new transient.asPersistent(); 191 | */ 192 | TMap asTransient(); 193 | 194 | /** 195 | * Creates a transient copy of `this`, lets it to be modified by [change] 196 | * and returns a persistent result. 197 | * 198 | * var persistent1 = new PersistentMap.from({'a':1}); 199 | * var persistent2 = persistent1.withTransient((m){ 200 | * m.doAssoc({'b':2}); 201 | * }); 202 | */ 203 | PMap withTransient(dynamic change(TMap)); 204 | 205 | /** 206 | * Returns a new map whose (key, value) pairs are the union of those of `this` 207 | * and [other]. 208 | * 209 | * The union is right-biased: if a key is present in both `this` and [other], 210 | * the value from [other] is retained. If [combine] is provided, the retained 211 | * value for a `key` present in both `this` and [other] is then 212 | * `combine(leftvalue, rightvalue)` where `leftvalue` is the value bound to 213 | * `key` in `this` and `rightvalue` is the one bound to `key` in [other]. 214 | * 215 | * {'a': 1}.union({'b': 2}) == {'a': 1, 'b': 2} 216 | * {'a': 1}.union({'a': 3, 'b': 2}) == {'a': 3, 'b': 2} 217 | * {'a': 1}.union({'a': 3, 'b': 2}, (x,y) => x + y) == {'a': 4, 'b': 2} 218 | * 219 | * Note that [union] is commutative if and only if [combine] is provided and 220 | * if it is commutative. 221 | */ 222 | PMap 223 | union(PMap other, [V combine(V left, V right)]); 224 | 225 | /** 226 | * Returns a new map whose (key, value) pairs are the intersection of those of 227 | * `this` and [other]. 228 | * 229 | * The intersection is right-biased: values from [other] are retained. If 230 | * [combine] is provided, the retained value for a `key` present in both 231 | * `this` and [other] is then `combine(leftvalue, rightvalue)` where 232 | * `leftvalue` is the value bound to `key` in `this` and `rightvalue` is the 233 | * one bound to `key` in [other]. 234 | * 235 | * {'a': 1}.intersection({'b': 2}) == {} 236 | * {'a': 1}.intersection({'a': 3, 'b': 2}) == {'a': 3} 237 | * {'a': 1}.intersection({'a': 3, 'b': 2}, (x,y) => x + y) == {'a': 4} 238 | * 239 | * Note that [intersection] is commutative if and only if [combine] is 240 | * provided and if it is commutative. 241 | */ 242 | PMap 243 | intersection(PMap other, [V combine(V left, V right)]); 244 | 245 | /// A strict (non-lazy) version of [map]. 246 | PMap strictMap(Pair f(Pair pair)); 247 | 248 | /// A strict (non-lazy) version of [where]. 249 | PMap strictWhere(bool f(Pair pair)); 250 | 251 | PMap update(K key, dynamic updateF); 252 | 253 | } 254 | 255 | /** 256 | * A transient map, binding keys of type [K] to values of type [V]. Null values 257 | * are supported but null keys are not. 258 | * 259 | * Transient data structure is a mutable structure, which can be efficiently 260 | * converted to the persistent data structure. It is usually created from 261 | * a persistent structure to apply some changes and obtain a new persistent 262 | * structure. The less changes are done, the more efficient is the conversion. 263 | */ 264 | abstract class TMap implements ReadMap { 265 | 266 | /** 267 | * Creates an empty map using the default implementation of 268 | * [TMap]. 269 | */ 270 | factory TMap() => new _TMapImpl(); 271 | 272 | /** 273 | * Binds [key] to [value]. 274 | * 275 | * If [key] was bound to some `oldvalue`, it is nevertheless bound 276 | * to [value]. 277 | * 278 | * var map = PersistentMap.fromMap({'a': 1}).asTransient(); 279 | * map.doAssoc('b', 2); // map is now {'a': 1, 'b': 2} 280 | * map.doAssoc('b', 3); // map is now {'a': 1, 'b': 3} 281 | */ 282 | TMap 283 | doAssoc(K key, V value); 284 | 285 | /** 286 | * Unbinds [key]. 287 | * 288 | * If [key] is not bound and [missingOk] is `false`, exception is thrown. 289 | * If [key] is not bound and [missingOk] is specified as `true`, nothing happens. 290 | * [missingOk] defaults to `false`. 291 | * 292 | * var map = PersistentMap.fromMap({'a': 1, 'b': 2}).asTransient(); 293 | * map.doDelete('b', 2); // map is now {'a': 1} 294 | * map.doDelete('b', 2, missingOk: true); // map is still {'a': 1} 295 | */ 296 | TMap doDelete(K key, {bool missingOk: false}) ; 297 | 298 | /** 299 | * Adjusts the value that is possibly bound to [key] by applying [f]. 300 | * 301 | * [f] should have one of the following signatures: V f(V value), V f([V value]) 302 | * 303 | * If [key] is not bound, result of calling [f] with no arguments is associated with [key] 304 | * If [key] is not bound and [f] can not be called with no arguments, [Exception] is thrown. 305 | * 306 | * var map = PersistentMap.fromMap({'a': 1}).asTransient(); 307 | * map.doUpdate('b', (x) => x + 1); // throws 308 | * map.doUpdate('b', ([x]) => x == null ? 2 : x + 1); // map is now {'a': 1, 'b': 2} 309 | * map.doUpdate('b', (x) => x + 1); // map is now {'a': 1, 'b': 3} 310 | */ 311 | TMap doUpdate(K key, dynamic f); 312 | 313 | /** 314 | * Updates all values by passing them to [f] and replacing them by results. 315 | * 316 | * var map = PersistentMap.fromMap({'a': 1, 'b': 2}).asTransient(); 317 | * map.mapValues((x) => x + 1) // map is now {'a': 2, 'b': 3} 318 | */ 319 | // TMap doMapValues(f(V value)); 320 | 321 | /** 322 | * Returns a persistent copy of `this`. 323 | * 324 | * This is ussualy called when changes to `this` 325 | * are finished 326 | * 327 | * var persistent1 = new PersistentMap.from({'a':1}); 328 | * var transient = persistent1.asTransient(); 329 | * transient.doAssoc({'b':2}); 330 | * var persistent2 = new transient.asPersistent(); 331 | */ 332 | PMap asPersistent(); 333 | 334 | operator []=(key, value); 335 | } 336 | -------------------------------------------------------------------------------- /lib/src/vector_impl.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, VacuumLabs. 2 | // Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code 3 | // is governed by a BSD-style license that can be found in the LICENSE file. 4 | 5 | // Authors are listed in the AUTHORS file 6 | 7 | part of persistent; 8 | 9 | const int _SHIFT = 5; 10 | const int _SIZE = 1 << _SHIFT; 11 | const int _MASK = _SIZE - 1; 12 | var _NOT_SET; 13 | 14 | _getNotSet() { 15 | if (_NOT_SET == null) 16 | _NOT_SET = {}; 17 | return _NOT_SET; 18 | } 19 | 20 | // Wrapper for referencing bool values 21 | class _Bool { 22 | bool value = false; 23 | } 24 | 25 | abstract class _PersistentVectorBase extends IterableBase { 26 | int _size; 27 | 28 | E _get(int index, [E notFound = _none]); 29 | 30 | E get first => _get(0); 31 | E get last => _get(this.length > 0 ? this.length - 1 : 0); 32 | int get length => _size; 33 | Iterator get iterator; 34 | } 35 | 36 | abstract class _BaseVectorImpl extends _PersistentVectorBase { 37 | _Owner _owner; 38 | _VNode _root; 39 | _VNode _tail; 40 | int _level; 41 | bool __altered = false; 42 | 43 | _BaseVectorImpl._prototype() { 44 | this._owner = null; 45 | this._root = new _VNode([], _owner); 46 | this._tail = _root; 47 | this._level = _SHIFT; 48 | this._size = 0; 49 | } 50 | 51 | Iterator get iterator => new _VectorIterator([this._root, this._tail]); 52 | 53 | bool hasKey(int key) => ((key >= 0) && (key < this.length)); 54 | 55 | E _get(int index, [E notFound = _none]) { 56 | try { 57 | index = _checkIndex(index); 58 | } catch(e) { 59 | if (notFound == _none) { 60 | throw(e); 61 | } else { 62 | return notFound; 63 | } 64 | } 65 | 66 | var node = _vectorNodeFor(index); 67 | var maskedIndex = index & _MASK; 68 | // if resize ever gets publicly exposed, we need to check if node != null 69 | return node._get(maskedIndex); 70 | } 71 | 72 | _BaseVectorImpl _set(int index, E value) { 73 | index = _checkIndex(index); 74 | 75 | var vector = this; 76 | var newTail = vector._tail; 77 | var newRoot = vector._root; 78 | var didAlter = new _Bool(); 79 | if (index >= _getTailOffset(vector._size)) { 80 | newTail = newTail._update(vector._owner, 0, index, value, didAlter); 81 | } else { 82 | newRoot = newRoot._update(vector._owner, vector._level, index, value, didAlter); 83 | } 84 | if (!didAlter.value) { 85 | return vector; 86 | } 87 | if (vector._owner != null) { 88 | vector._root = newRoot; 89 | vector._tail = newTail; 90 | vector.__altered = true; 91 | return vector; 92 | } 93 | return new _PersistentVectorImpl._make(vector._size, vector._level, newRoot, newTail); 94 | } 95 | 96 | _BaseVectorImpl _push(E value) { 97 | var len = this.length; 98 | return this._withTransient((vect) => vect._resize(len+1)._set(len, value)); 99 | } 100 | 101 | _BaseVectorImpl _pop() { 102 | return this._resize(this.length-1); 103 | } 104 | 105 | int _getTailOffset(int size) { 106 | return size < _SIZE ? 0 : (((size - 1) >> _SHIFT) << _SHIFT); 107 | } 108 | 109 | int _checkIndex(int index) { 110 | if (index < 0 || index >= _size) { 111 | throw new RangeError.value(index); 112 | } 113 | return index; 114 | } 115 | 116 | _VNode _vectorNodeFor(int index) { 117 | if (index >= _getTailOffset(this._size)) { 118 | return this._tail; 119 | } 120 | if (index < 1 << (this._level + _SHIFT)) { 121 | var node = this._root; 122 | var level = this._level; 123 | while (node != null && level > 0) { 124 | node = node._get((index >> level) & _MASK); 125 | level -= _SHIFT; 126 | } 127 | return node; 128 | } 129 | return null; 130 | } 131 | 132 | _BaseVectorImpl _resize(int end) { 133 | if (end < 0) { 134 | throw new RangeError.value(end); 135 | } 136 | var owner; 137 | if (_owner == null) { 138 | owner = _owner; 139 | } else { 140 | owner = new _Owner(); 141 | } 142 | var oldSize = _size; 143 | var newSize = end; 144 | 145 | if (oldSize == newSize) return this; 146 | var newLevel = _level; 147 | var newRoot = _root; 148 | 149 | var oldTailOffset = _getTailOffset(oldSize); 150 | var newTailOffset = _getTailOffset(newSize); 151 | while (newTailOffset >= 1 << (newLevel + _SHIFT)) { 152 | newRoot = new _VNode(newRoot != null && newRoot.length > 0 ? [newRoot] : [], owner); 153 | newLevel += _SHIFT; 154 | } 155 | 156 | var oldTail = _tail; 157 | var newTail = newTailOffset < oldTailOffset ? 158 | _vectorNodeFor(newSize - 1) : 159 | newTailOffset > oldTailOffset ? new _VNode([], owner) : oldTail; 160 | 161 | if (newTailOffset > oldTailOffset && oldSize > 0 && oldTail.length > 0) { 162 | newRoot = _transientVNode(newRoot, owner); 163 | var node = newRoot; 164 | for (var level = newLevel; level > _SHIFT; level -= _SHIFT) { 165 | var idx = (oldTailOffset >> level) & _MASK; 166 | node._set(idx , _transientVNode(node._get(idx), owner)); 167 | node = node._get(idx); 168 | } 169 | node._set((oldTailOffset >> _SHIFT) & _MASK, oldTail); 170 | } 171 | 172 | if (newTailOffset < oldTailOffset) { 173 | newRoot = _transientVNode(newRoot, owner); 174 | var node = newRoot; 175 | var parent = null; 176 | var idx = null; 177 | for (var level = newLevel; level > _SHIFT; level -= _SHIFT) { 178 | parent = node; 179 | idx = (newTailOffset >> level) & _MASK; 180 | node._set(idx, _transientVNode(node._get(idx), owner)); 181 | node = node._get(idx); 182 | } 183 | var newNode = node._removeAfter(owner, node.length - 1); 184 | if (parent == null) { 185 | newRoot = newNode; 186 | } else { 187 | parent._set(idx, newNode); 188 | } 189 | } 190 | 191 | if (newSize < oldSize) { 192 | newTail = newTail._removeAfter(owner, newSize); 193 | } 194 | 195 | if (_owner != null) { 196 | _size = newSize; 197 | _level = newLevel; 198 | _root = newRoot; 199 | _tail = newTail; 200 | __altered = true; 201 | return this; 202 | } 203 | return new _PersistentVectorImpl._make(newSize, newLevel, newRoot, newTail); 204 | 205 | } 206 | 207 | // TODO: debug funkcia, umazat 208 | void printInfo() { 209 | print("Size: $_size"); 210 | print("Level: $_level"); 211 | print("Root: $_root"); 212 | print("Tail: $_tail"); 213 | } 214 | 215 | _BaseVectorImpl _ensureOwner(_Owner ownerID) { 216 | if (ownerID == this._owner) { 217 | return this; 218 | } 219 | if (ownerID == null) { 220 | this._owner = ownerID; 221 | return new _PersistentVectorImpl._make(this._size, this._level, this._root, this._tail); 222 | } 223 | return new _TransientVectorImpl._make(this._size, this._level, this._root, this._tail, ownerID); 224 | } 225 | 226 | _TransientVectorImpl _asTransient() { 227 | return this._owner != null ? this : this._ensureOwner(new _Owner()); 228 | } 229 | 230 | _PersistentVectorImpl _asPersistent() { 231 | return this._ensureOwner(null); 232 | } 233 | 234 | _BaseVectorImpl _withTransient(fn) { 235 | var transient = this._asTransient(); 236 | fn(transient); 237 | return transient.wasAltered() ? transient._ensureOwner(this._owner) : this; 238 | } 239 | 240 | } 241 | 242 | class _VectorIterator implements Iterator { 243 | List _array; 244 | int _index = 0; 245 | Iterator _current = null; 246 | 247 | _VectorIterator(this._array); 248 | E get current => (_current != null) ? _current.current : null; 249 | 250 | bool moveNext() { 251 | while(_index < _array.length) { 252 | if (_current == null) { 253 | _current = _array[_index].iterator; 254 | } 255 | if (_current.moveNext()) { 256 | return true; 257 | } else { 258 | _current = null; 259 | _index++; 260 | } 261 | } 262 | return false; 263 | } 264 | } 265 | 266 | class _VNode { 267 | List _array; 268 | _Owner _ownerID; 269 | 270 | int get length => _array.length; 271 | 272 | Iterator get iterator { 273 | if (_array.length == 0) return _array.iterator; 274 | if (_array[0] is _VNode) return new _VectorIterator(_array); 275 | return _array.iterator; 276 | } 277 | 278 | String toString() { 279 | return "VNode: " + _array.toString(); 280 | } 281 | 282 | _VNode(this._array, this._ownerID); 283 | 284 | void _set(int index, value) { 285 | if (_array.length > index) { 286 | _array[index] = value; 287 | } else if (_array.length == index) { 288 | _array.add(value); 289 | } else { 290 | throw new Exception("Should not happen; ${_array.length} ${index}"); 291 | } 292 | } 293 | 294 | _get(int index) => (index >= 0 && index < this.length) ? this._array[index] : null; 295 | 296 | _VNode _removeAfter(_Owner ownerID, int newSize) { 297 | var sizeIndex = (newSize - 1) & _MASK; 298 | if (newSize != 0 && sizeIndex >= this.length - 1) { 299 | return this; 300 | } 301 | var editable = _transientVNode(this, ownerID); 302 | if (newSize == 0) { 303 | editable._array = []; 304 | } else { 305 | editable._array.removeRange(sizeIndex + 1, editable.length); 306 | } 307 | return editable; 308 | 309 | } 310 | 311 | _VNode _update(ownerID, level, index, value, _Bool didAlter) { 312 | var deleted = value == _getNotSet(); 313 | var node = this; 314 | var newNode; 315 | var idx = (index >> level) & _MASK; 316 | var nodeHas = node != null && idx < node._array.length; 317 | if (deleted && !nodeHas) { 318 | return node; 319 | } 320 | if (level > 0) { 321 | var lowerNode; 322 | if (node != null && node._array.length > idx) { 323 | lowerNode = node._array[idx]; 324 | } else { 325 | lowerNode = null; 326 | } 327 | var newLowerNode = lowerNode._update(ownerID, level - _SHIFT, index, value, didAlter); 328 | if (newLowerNode == lowerNode) { 329 | return node; 330 | } 331 | var newNode = _transientVNode(node, ownerID); 332 | newNode._set(idx, newLowerNode); 333 | return newNode; 334 | } 335 | 336 | if (!deleted && nodeHas && node._array[idx] == value) { 337 | return node; 338 | } 339 | 340 | didAlter.value = true; 341 | 342 | newNode = _transientVNode(node, ownerID); 343 | if (deleted) { 344 | newNode._set(idx, null); 345 | } else { 346 | newNode._set(idx, value); 347 | } 348 | return newNode; 349 | } 350 | } 351 | 352 | _VNode _transientVNode(_VNode node, _Owner ownerID) { 353 | if (ownerID != null && node != null && ownerID == node._ownerID) { 354 | return node; 355 | } 356 | return new _VNode(node != null ? node._array.sublist(0) : [], ownerID); 357 | } 358 | 359 | class _PersistentVectorImpl extends _BaseVectorImpl implements PVec { 360 | // cached hashCode. 361 | int _hashCode = null; 362 | 363 | 364 | factory _PersistentVectorImpl.from(Iterable values) { 365 | if (values.length == 0) { 366 | return new _PersistentVectorImpl.empty(); 367 | } 368 | _PersistentVectorImpl result = new _PersistentVectorImpl.empty(); 369 | result = result.withTransient((vector) { 370 | values.forEach((E value) { 371 | vector.doPush(value); 372 | }); 373 | return vector; 374 | }); 375 | return result; 376 | } 377 | 378 | factory _PersistentVectorImpl.empty() => new _PersistentVectorImpl._prototype(); 379 | _PersistentVectorImpl._prototype() : super._prototype(); 380 | 381 | factory _PersistentVectorImpl._make(int size, int level, _VNode root, _VNode tail) { 382 | var x = new _PersistentVectorImpl._prototype(); 383 | x._size = size; 384 | x._level = level; 385 | x._root = root; 386 | x._tail = tail; 387 | x._owner = null; 388 | return x; 389 | } 390 | 391 | int get hashCode { 392 | if (this._hashCode == null) { 393 | return hashObjects(this); 394 | } 395 | return this._hashCode; 396 | } 397 | 398 | bool operator==(other) { 399 | if (other is! _PersistentVectorImpl) return false; 400 | _PersistentVectorImpl otherVector = other; 401 | if (this.hashCode != otherVector.hashCode) return false; 402 | if (this.length != otherVector.length) return false; 403 | for (int i = 0; i < this.length; i++) { 404 | if (this._get(i) != otherVector.get(i)) return false; 405 | } 406 | return true; 407 | } 408 | 409 | _PersistentVectorImpl _clear() { 410 | if (this.length == 0) { 411 | return this; 412 | } 413 | return new _PersistentVectorImpl.empty(); 414 | } 415 | 416 | _TransientVectorImpl asTransient() => _asTransient(); 417 | _PersistentVectorImpl withTransient(fn) => _withTransient(fn); 418 | _PersistentVectorImpl push(E value) => _push(value); 419 | _PersistentVectorImpl pop() => _pop(); 420 | _PersistentVectorImpl set(int index, E value) => _set(index, value); 421 | E get(int index, [E notFound = _none]) => _get(index, notFound); 422 | E operator[](int index) => get(index); 423 | } 424 | 425 | class _TransientVectorImpl extends _BaseVectorImpl implements TVec { 426 | _TransientVectorImpl._prototype() : super._prototype(); 427 | 428 | factory _TransientVectorImpl._make(int size, int level, _VNode root, _VNode tail, _Owner ownerID) { 429 | var x = new _TransientVectorImpl._prototype(); 430 | x._size = size; 431 | x._level = level; 432 | x._root = root; 433 | x._tail = tail; 434 | x._owner = ownerID; 435 | return x; 436 | } 437 | 438 | bool wasAltered() { 439 | return this.__altered; 440 | } 441 | 442 | _TransientVectorImpl _clear() { 443 | this._size = 0; 444 | this._level = _SHIFT; 445 | this._root = this._tail = null; 446 | this.__altered = true; 447 | return this; 448 | } 449 | 450 | _PersistentVectorImpl asPersistent() => _asPersistent(); 451 | void doPush(E value) { 452 | _push(value); 453 | } 454 | void doPop() { 455 | _pop(); 456 | } 457 | E get(int index, [E notFound = _none]) => _get(index, notFound); 458 | E operator[](int index) => _get(index); 459 | void doSet(int index, E value) { 460 | _set(index, value); 461 | } 462 | void operator []=(int index, E value) { 463 | _set(index, value); 464 | } 465 | } -------------------------------------------------------------------------------- /lib/src/map_impl.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, VacuumLabs. 2 | // Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code 3 | // is governed by a BSD-style license that can be found in the LICENSE file. 4 | 5 | // Authors are listed in the AUTHORS file 6 | 7 | part of persistent; 8 | 9 | 10 | // this turns out to be the fastest combination 11 | 12 | const branchingBits = 4; 13 | const maxDepth = 6; 14 | 15 | //const branchingBits = 3; 16 | //const maxDepth = 9; 17 | 18 | //const branchingBits = 5; 19 | //const maxDepth = 5; 20 | 21 | 22 | const branching = 1 << branchingBits; 23 | const branchingMask = (1 << branchingBits)-1; 24 | const allHashMask = (1 << (maxDepth+1) * branchingBits)-1; 25 | 26 | const leafSize = branching * 3; 27 | const leafSizeMin = branching * 2; 28 | 29 | const binSearchThr = 4; 30 | const recsize = 3; //0 - hash, 1 - key, 2 - val 31 | 32 | final _random = new Random(); 33 | 34 | _ThrowKeyError(key) => throw new Exception('Key Error: ${key} is not defined'); 35 | 36 | _ThrowUpdateKeyError(key, exception) => throw new Exception('Key $key was not found, calling update with no arguments threw: $exception'); 37 | 38 | _getUpdateValue(key, updateF) { 39 | try { 40 | return updateF(); 41 | } catch(e) { 42 | _ThrowUpdateKeyError(key, e); 43 | } 44 | } 45 | 46 | /// see technical.md for explanation of what this does 47 | _mangleHash(hash){ 48 | var _tmp = hash ^ (hash << 8); 49 | return ((_tmp ^ (_tmp << 16)) & allHashMask); 50 | } 51 | 52 | /// no array compression here (aka bitpos style). Why? See technical.md 53 | 54 | _getBranch(hash, depth){ 55 | return (hash >> (depth*branchingBits)) & branchingMask; 56 | } 57 | 58 | 59 | 60 | class _TMapImpl 61 | extends IterableBase> 62 | implements TMap { 63 | // Although PMap can be represented a simple _Node, Transient map needs 64 | // separate structure with a mutable reference to _Node. 65 | _Node _root; 66 | 67 | _Owner _owner; 68 | get owner => _owner != null ? _owner : 69 | throw new Exception('Cannot modify TMap after calling asPersistent.'); 70 | 71 | factory _TMapImpl() => new _TMapImpl.fromPersistent(new PMap()); 72 | 73 | /** 74 | * Creates an immutable copy of [map] using the default implementation of 75 | * [TMap]. 76 | */ 77 | _TMapImpl.fromPersistent(_Node map) { 78 | _owner = new _Owner(); 79 | _root = map; 80 | } 81 | 82 | TMap _adjustRootAndReturn(newRoot) { 83 | _root = newRoot; 84 | return this; 85 | } 86 | 87 | TMap 88 | doAssoc(K key, V value) { 89 | return _adjustRootAndReturn(_root._assoc(owner, key, value)); 90 | } 91 | 92 | operator []=(key, value){ 93 | this.doAssoc(key, value); 94 | } 95 | 96 | TMap doDelete(K key, {bool missingOk: false}) { 97 | return _adjustRootAndReturn(_root._delete(owner, key, _mangleHash(key.hashCode), maxDepth, missingOk)); 98 | } 99 | 100 | TMap doUpdate(K key, dynamic updateF) { 101 | return _adjustRootAndReturn(_root._update(owner, key, updateF)); 102 | } 103 | 104 | PMap asPersistent() { 105 | _owner = null; 106 | return this._root; 107 | } 108 | 109 | toString() => 'TMap($_root)'; 110 | 111 | V get(K key, [V notFound = _none]) => _root.get(key, notFound); 112 | 113 | V operator [](K key) => _root.get(key); 114 | 115 | void forEachKeyValue(f) => _root.forEachKeyValue(f); 116 | 117 | Map toMap() =>_root.toMap(); 118 | 119 | Iterable get keys => _root.keys; 120 | 121 | Iterable get values => _root.values; 122 | 123 | Iterator get iterator => _root.iterator; 124 | 125 | int get length => _root.length; 126 | 127 | bool containsKey(key) => _root.containsKey(key); 128 | 129 | bool hasKey(key) => _root.hasKey(key); 130 | 131 | } 132 | 133 | 134 | /** 135 | * Superclass for _EmptyMap, _Leaf and _SubMap. 136 | */ 137 | abstract class _Node extends IterableBase> implements PMap { 138 | _Owner _owner; 139 | int _length; 140 | int _hash; 141 | get length => _length; 142 | 143 | _Node(this._owner, this._length); 144 | 145 | factory _Node.fromMap(map){ 146 | _Node root = new _Leaf.empty(null); 147 | map.forEach((K key, V value) { 148 | root = root._assoc(null, key, value); 149 | }); 150 | return root; 151 | } 152 | 153 | factory _Node.fromPairs(pairs){ 154 | var _root = new _Leaf.empty(null); 155 | pairs.forEach((pair) { 156 | _root = _root._assoc(null, pair.fst, pair.snd); 157 | }); 158 | return _root; 159 | } 160 | 161 | _forEachKVSegment(f); 162 | 163 | V _get(K key, int hash, int depth); 164 | _Node _insertOneWith(_Owner owner, key, val, hash, int depth, [update]); 165 | 166 | int get hashCode; 167 | 168 | _Node _update(_Owner owner, K key, dynamic updateF){ 169 | return _insertOneWith(owner, key, null, _mangleHash(key.hashCode), maxDepth, updateF); 170 | } 171 | 172 | PMap update(K key, dynamic updateF) => 173 | _insertOneWith(null, key, null, _mangleHash(key.hashCode), maxDepth, updateF); 174 | 175 | _Node _assoc(_Owner owner, K key, V value) => 176 | _insertOneWith(owner, key, value, _mangleHash(key.hashCode), maxDepth); 177 | 178 | PMap assoc(K key, V value) => _assoc(null, key, value); 179 | 180 | _Node _delete(_Owner owner, K key, int hash, int depth, bool missingOk); 181 | 182 | PMap delete(K key, {bool missingOk: false}) => _delete(null, key, _mangleHash(key.hashCode), maxDepth, missingOk); 183 | 184 | bool operator ==(other) { 185 | if (other is! _Node) return false; 186 | if (identical(this, other)) return true; 187 | _Node me = this; 188 | if (me.length != other.length) { 189 | return false; 190 | } 191 | if (me is _Leaf && other is _Leaf) { 192 | List mekv = (me as _Leaf)._kv; 193 | List okv = other._kv; 194 | var lastMatch=0; 195 | for (int i=0; i toMap() { 275 | Map result = new Map(); 276 | this.forEachKeyValue((K k, V v) { result[k] = v; }); 277 | return result; 278 | } 279 | 280 | String toString() { 281 | StringBuffer buffer = new StringBuffer('{'); 282 | bool comma = false; 283 | this.forEachKeyValue((K k, V v) { 284 | if (comma) buffer.write(', '); 285 | buffer.write('$k: $v'); 286 | comma = true; 287 | }); 288 | buffer.write('}'); 289 | return buffer.toString(); 290 | } 291 | 292 | Iterable get keys => this.map((Pair pair) => pair.fst); 293 | 294 | Iterable get values => this.map((Pair pair) => pair.snd); 295 | 296 | void forEachKeyValue(f(K key, V value)); 297 | 298 | bool containsKey(key) { 299 | final _none = new Object(); 300 | final value = this.get(key, _none); 301 | return value != _none; 302 | } 303 | 304 | bool hasKey(key) => containsKey(key); 305 | 306 | TMap asTransient() { 307 | return new _TMapImpl.fromPersistent(this); 308 | } 309 | 310 | PMap withTransient(dynamic f(TMap map)) { 311 | TMap transient = this.asTransient(); 312 | f(transient); 313 | return transient.asPersistent(); 314 | } 315 | 316 | V operator [](K key) => get(key); 317 | 318 | PMap strictMap(Pair f(Pair pair)) => 319 | new PMap.fromPairs(this.map(f)); 320 | 321 | PMap strictWhere(bool f(Pair pair)) => 322 | new PMap.fromPairs(this.where(f)); 323 | 324 | static _returnRight(left, right) => right; 325 | 326 | PMap union( 327 | PMap other, 328 | [V combine(V left, V right) = _returnRight] 329 | ){ 330 | if(other is _Node){ 331 | return _union(other as _Node, combine, maxDepth); 332 | } else { 333 | var result = this.asTransient(); 334 | other.forEachKeyValue((K key, V value){ 335 | if(result.hasKey(key)){ 336 | result.doAssoc(key, combine(this[key], value)); 337 | } else { 338 | result.doAssoc(key, value); 339 | } 340 | }); 341 | return result.asPersistent(); 342 | } 343 | } 344 | 345 | PMap intersection( 346 | PMap other, 347 | [V combine(V left, V right) = _returnRight] 348 | ){ 349 | if(other is _Node){ 350 | return _intersection(other as _Node, combine, maxDepth); 351 | } else { 352 | var result = new PMap().asTransient(); 353 | other.forEachKeyValue((K key, V value){ 354 | if(this.hasKey(key)){ 355 | result.doAssoc(key, combine(this[key], value)); 356 | } 357 | }); 358 | return result.asPersistent(); 359 | } 360 | } 361 | 362 | PMap _union( 363 | _Node other, 364 | V combine(V left, V right), 365 | int depth 366 | ); 367 | 368 | PMap _intersection( 369 | _Node other, 370 | V combine(V left, V right), 371 | int depth 372 | ); 373 | } 374 | 375 | 376 | class _Leaf extends _Node { 377 | List _kv; 378 | get private_kv => _kv; 379 | 380 | get iterator { 381 | List> pairs = []; 382 | for (int i=0; i<_kv.length; i+=recsize){ 383 | pairs.add(new Pair(_kv[i+1], _kv[i+2])); 384 | } 385 | return pairs.iterator; 386 | } 387 | 388 | /// check whether order-by-hashcode invariant (see technical.md) holds 389 | void sanityCheck(){ 390 | var lasthash = double.NEGATIVE_INFINITY; 391 | for (int i=0; i<_kv.length; i+=recsize){ 392 | if (lasthash>_kv[i]) { 393 | throw new Exception('invariant violated'); 394 | } 395 | lasthash = _kv[i]; 396 | } 397 | } 398 | 399 | int get hashCode { 400 | if(_hash != null) return _hash; 401 | _hash = 0; 402 | for(int i=0; i<_kv.length; i+=recsize){ 403 | _hash ^= hash2(_kv[i], _kv[i+2].hashCode); 404 | 405 | } 406 | return _hash; 407 | } 408 | 409 | _Leaf.abc(_Owner owner, _kv) : super(owner, _kv.length ~/ recsize){ 410 | this._kv = _kv; 411 | } 412 | 413 | _Leaf.empty(_Owner owner): super(owner, 0) { 414 | this._kv = []; 415 | } 416 | 417 | factory _Leaf.fromSubmap(_Owner owner, _SubMap sm) { 418 | List _kv = []; 419 | sm._forEachKVSegment((kv){ 420 | _kv.addAll(kv); 421 | }); 422 | var nres = new _Leaf.abc(owner, _kv); 423 | return nres; 424 | } 425 | 426 | factory _Leaf.ensureOwner(_Leaf old, _Owner owner, kv, int length) { 427 | if(_ownerEquals(owner, old._owner)) { 428 | old._kv = kv; 429 | old._length = length; 430 | return old; 431 | } 432 | return new _Leaf.abc(owner, kv); 433 | } 434 | 435 | /// see technical.md for explanation of what this does 436 | 437 | _Node _polish(_Owner owner, int depth, List _kv) { 438 | assert(_kv.length % recsize == 0); 439 | // depth == -1 means we are at the bottom level; we consumed all 440 | // information from 'hash' and we have to extend the _Leaf no matter how 441 | // long it gets 442 | if (_kv.length < recsize * leafSize || depth == -1) { 443 | return new _Leaf.abc(owner, _kv); 444 | } else { 445 | List kvs = new List.generate(branching, (_) => []); 446 | for (int i=0; i<_kv.length; i+=recsize){ 447 | int branch = _getBranch(_kv[i], depth); 448 | kvs[branch].add(_kv[i]); 449 | kvs[branch].add(_kv[i + 1]); 450 | kvs[branch].add(_kv[i + 2]); 451 | } 452 | List <_Node> array = new List.generate(branching, 453 | (i) => new _Leaf.abc(owner, kvs[i])); 454 | return new _SubMap.abc(owner, array, _kv.length ~/ recsize); 455 | } 456 | } 457 | 458 | _insert(List into, key, val, hash, [update]){ 459 | assert(into.length % recsize == 0); 460 | if (into.length == 0) { 461 | into.addAll([hash, key, val]); 462 | return; 463 | } 464 | int from = 0; 465 | int to = (into.length ~/ recsize) - 1; 466 | while(to - from > binSearchThr){ 467 | int mid = (from + to) ~/ 2; 468 | var midh = into[mid*recsize]; 469 | if (midh > hash){ 470 | to = mid; 471 | } else if (midh < hash){ 472 | from = mid; 473 | } else { 474 | break; 475 | } 476 | } 477 | 478 | for (int i=from*recsize; i<=to*recsize; i+=recsize) { 479 | assert(i%recsize == 0); 480 | if (hash <= into[i]) { 481 | if (hash < into[i]) { 482 | into.insertAll(i, [hash, key, val]); 483 | return; 484 | } 485 | if (key == into[i+1]) { 486 | if (update == null) { 487 | into[i+2] = val; 488 | } else { 489 | into[i+2] = update(into[i+2]); 490 | } 491 | return; 492 | } 493 | } 494 | } 495 | 496 | if (update == null) { 497 | into.addAll([hash, key, val]); 498 | } else { 499 | into.addAll([hash, key, _getUpdateValue(key, update)]); 500 | } 501 | assert(into.length % recsize == 0); 502 | } 503 | 504 | _Node _insertOneWith(_Owner owner, key, val, hash, int depth, [update]) { 505 | List nkv = _makeCopyIfNeeded(owner, this._owner, _kv); 506 | _insert(nkv, key, val, hash, update); 507 | return _polish(owner, depth, nkv); 508 | } 509 | 510 | _Node _delete(_Owner owner, K key, int hash, int depth, bool missingOk) { 511 | bool found = false; 512 | List nkv = _makeCopyIfNeeded(owner, this._owner, _kv); 513 | for (int i=0; i.ensureOwner(this, owner, nkv, nkv.length ~/ recsize); 532 | } 533 | } 534 | 535 | V _get(K key, int hash, int depth) { 536 | int f=0; 537 | int from = 0; 538 | int to = _kv.length ~/ recsize; 539 | while(to - from > binSearchThr){ 540 | int mid = (from + to) ~/ 2; 541 | var midh = _kv[mid * recsize]; 542 | if (midh > hash){ 543 | to = mid; 544 | } else if (midh < hash){ 545 | from = mid; 546 | } else { 547 | break; 548 | } 549 | } 550 | for(int i=from*recsize; i "_Leaf($_kv)"; 565 | 566 | _forEachKVSegment(f){ 567 | f(_kv); 568 | } 569 | 570 | PMap _union( 571 | _Node other, 572 | V combine(V left, V right), 573 | int depth 574 | ){ 575 | // TODO: More efficient union of two leafs. 576 | _Owner owner = new _Owner(); 577 | for(int i = 0; i < _kv.length; i+=3){ 578 | var res = other._get(_kv[i+1], _kv[i], depth); 579 | if(_isNone(res)){ 580 | other = 581 | other._insertOneWith(owner, _kv[i+1], _kv[i+2], _kv[i], depth); 582 | } else { 583 | other = 584 | other._insertOneWith( 585 | owner, _kv[i+1], 586 | combine(_kv[i+2], res), 587 | _kv[i], 588 | depth 589 | ); 590 | } 591 | } 592 | other._owner = null; 593 | return other; 594 | } 595 | 596 | PMap _intersection( 597 | _Node other, 598 | V combine(V left, V right), 599 | int depth 600 | ){ 601 | List _nkv = []; 602 | for(int i = 0; i < _kv.length; i+=3){ 603 | var res = other._get(_kv[i+1], _kv[i], depth); 604 | if(!_isNone(res)){ 605 | _nkv.addAll([_kv[i], _kv[i+1], combine(_kv[i+2], res)]); 606 | } 607 | } 608 | return new _Leaf.abc(null, _nkv); 609 | } 610 | } 611 | 612 | class _SubMapIterator implements Iterator> { 613 | List<_Node> _array; 614 | int _index = 0; 615 | // invariant: _currentIterator != null => _currentIterator.current != null 616 | Iterator> _currentIterator = null; 617 | 618 | _SubMapIterator(this._array); 619 | 620 | Pair get current => 621 | (_currentIterator != null) ? _currentIterator.current : null; 622 | 623 | bool moveNext() { 624 | while (_index < _array.length) { 625 | if (_currentIterator == null) { 626 | _currentIterator = _array[_index].iterator; 627 | } 628 | if (_currentIterator.moveNext()) { 629 | return true; 630 | } else { 631 | _currentIterator = null; 632 | _index++; 633 | } 634 | } 635 | return false; 636 | } 637 | } 638 | 639 | 640 | class _SubMap extends _Node { 641 | List<_Node> _array; 642 | 643 | Iterator> get iterator => new _SubMapIterator(_array); 644 | 645 | _SubMap.abc(_Owner owner, this._array, int length) : super(owner, length); 646 | 647 | factory _SubMap.ensureOwner(_SubMap old, _Owner owner, array, int length) { 648 | if(_ownerEquals(owner, old._owner)) { 649 | old._array = array; 650 | old._length = length; 651 | } 652 | return new _SubMap.abc(owner, array, length); 653 | } 654 | 655 | int get hashCode { 656 | if(_hash != null) return _hash; 657 | _hash = 0; 658 | for(var child in _array){ 659 | _hash ^= child.hashCode; 660 | 661 | } 662 | return _hash; 663 | } 664 | 665 | V _get(K key, int hash, int depth) { 666 | int branch = _getBranch(hash, depth); 667 | _Node map = _array[branch]; 668 | return map._get(key, hash, depth - 1); 669 | } 670 | 671 | _Node _insertOneWith(_Owner owner, key, val, hash, int depth, [update]) { 672 | int branch = _getBranch(hash, depth); 673 | _Node m = _array[branch]; 674 | int oldSize = m.length; 675 | _Node newM = m._insertOneWith(owner, key, val, hash, depth - 1, update); 676 | if(identical(m, newM)) { 677 | if(oldSize != m.length) this._length += m.length - oldSize; 678 | return this; 679 | } 680 | List<_Node> newarray = _makeCopyIfNeeded(owner, this._owner, _array); 681 | newarray[branch] = newM; 682 | int delta = newM.length - oldSize; 683 | return new _SubMap.ensureOwner(this, owner, newarray, length + delta); 684 | } 685 | 686 | _Node _delete(owner, K key, int hash, int depth, bool missingOk) { 687 | int branch = _getBranch(hash, depth); 688 | _Node child = _array[branch]; 689 | int childLength = child.length; 690 | // need to remember child length as this may modify 691 | // the child (if working with transient) 692 | _Node newChild = child._delete(owner, key, hash, depth - 1, missingOk); 693 | int newLength = this.length + (newChild.length - childLength); 694 | if (identical(child, newChild)) { 695 | this._length = newLength; 696 | return this; 697 | } 698 | List<_Node> newarray = new List<_Node>.from(_array); 699 | newarray[branch] = newChild; 700 | _Node res = new _SubMap.ensureOwner(this, owner, newarray, newLength); 701 | 702 | // if submap is too small, let's replace it by _Leaf 703 | if (res._length >= leafSizeMin) { 704 | return res; 705 | } else { 706 | return new _Leaf.fromSubmap(owner, res); 707 | } 708 | } 709 | 710 | _forEachKVSegment(f) { 711 | _array.forEach((child) => child._forEachKVSegment(f)); 712 | } 713 | 714 | forEachKeyValue(f(K, V)) { 715 | _array.forEach((mi) => mi.forEachKeyValue(f)); 716 | } 717 | 718 | toDebugString() => "_SubMap($_array)"; 719 | 720 | PMap _union( 721 | _Node other, 722 | V combine(V left, V right), 723 | int depth 724 | ){ 725 | if(other is _SubMap){ 726 | var children = new List.generate(branching, (int i) => ( 727 | _array[i]._union((other as _SubMap)._array[i], combine, depth-1) 728 | )); 729 | int size = children.fold(0, (int sum, _Node x) => 730 | sum+=x.length 731 | ); 732 | return new _SubMap.abc(null, children, size); 733 | } else { 734 | return other._union(this, (x,y) => combine(y,x), depth); 735 | } 736 | } 737 | 738 | PMap _intersection( 739 | _Node other, 740 | V combine(V left, V right), 741 | int depth 742 | ){ 743 | if(other is _SubMap){ 744 | var children = new List.generate(branching, (int i) => ( 745 | _array[i]._intersection((other as _SubMap)._array[i], combine, depth-1) 746 | )); 747 | int size = children.fold(0, (int sum, _Node x) => 748 | sum+=x.length 749 | ); 750 | var res = new _SubMap.abc(null, children, size); 751 | if (size >= leafSizeMin) { 752 | return res; 753 | } else { 754 | return new _Leaf.fromSubmap(null, res); 755 | } 756 | } else { 757 | return other._intersection(this, (x,y) => combine(y,x), depth); 758 | } 759 | } 760 | } 761 | 762 | _ownerEquals(_Owner a, _Owner b) { 763 | return a != null && a == b; 764 | } 765 | 766 | /// usually, we need to copy some arrays when associng. However, when working 767 | /// with transients (and the owners match), it is safe just to modify the array 768 | _makeCopyIfNeeded(_Owner a, _Owner b, List c) { 769 | if(_ownerEquals(a, b)) 770 | return c; 771 | else return c.sublist(0); 772 | } 773 | -------------------------------------------------------------------------------- /lib/src/functions.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, VacuumLabs. 2 | // Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code 3 | // is governed by a BSD-style license that can be found in the LICENSE file. 4 | 5 | // Authors are listed in the AUTHORS file 6 | 7 | part of persistent; 8 | 9 | _dispatch(x, {op:"operation", map, vec, set}) { 10 | if (x is PMap) { 11 | if (map != null) return map(); 12 | } else if (x is PVec) { 13 | if (vec != null) return vec(); 14 | } else if (x is PSet) { 15 | if (set != null) return set(); 16 | } 17 | throw new Exception("${x.runtimeType} does not support $op operation"); 18 | } 19 | 20 | _firstP(p) => p is Pair? p.fst : p.first; 21 | _secondP(p) => (p is Pair)? p.snd : p.last; 22 | 23 | /** 24 | * Returns a new collection which is the result of inserting elements to persistent 25 | * [coll] ([PMap]/[PSet]/[PVec]). 26 | * Accepts up to 9 positional elements in one call. If you need to insert 27 | * more elements, call [into] with [List] of elements. 28 | * When conjing to a map as element use [List] of length 2 or [Pair]. 29 | * Inserting element to [PSet], which is already presented, does nothing. 30 | * Inserting element to [PMap] with existing key will overwrite that key. 31 | * 32 | * Examples: 33 | * PersistentVector pv = persist([1, 2]) 34 | * conj(pv, 3, 4, 5); // == persist([1, 2, 3, 4, 5]) 35 | * 36 | * PersistentMap pm = new PersistentMap(); 37 | * conj(pm, ['a', 8]); // == persist({'a': 8}); 38 | * conj(pm, new Pair(['a', 6]), ['b', 10]); // == persist({'a': 6, 'b': 10}) 39 | */ 40 | PersistentCollection conj(PersistentCollection coll, arg0, [arg1 = _none, arg2 = _none, arg3 = _none, arg4 = _none, arg5 = _none, arg6 = _none, arg7 = _none, arg8 = _none, arg9 = _none]) { 41 | var varArgs = [arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9].where((x) => x != _none); 42 | return into(coll, varArgs); 43 | } 44 | 45 | /** 46 | * Returns a new collection which is the result of inserting all elements of [iter] 47 | * to persistent [coll] ([PMap]/[PSet]/[PVec]). 48 | * Inserting element to [PSet], which is already presented, does nothing. 49 | * Inserting element to [PMap] with existing key will overwrite that key. 50 | * 51 | * Examples: 52 | * PersistentVector pv = persist([]); 53 | * insert(pv, [0, 2, 4]); // == persist([0, 2, 4]) 54 | * 55 | * PersistentMap pm1 = persist({'a': 10, 'b': 9}); 56 | * PersistentMap pm2 = persist({'b': 15, 'c': 7}); 57 | * insert(pm1, pm2); // persist({'a':10, 'b': 15, 'c': 7}) 58 | * insert(pm1, [['b', 15], new Pair(c, 7)]); // == persist({'a':10, 'b': 15, 'c': 7}); 59 | * 60 | * PSet perSet = new PSet(); 61 | * into(perSet, [1,2,1,3,2]); // == persist(new Set.from([1, 2, 3])) 62 | */ 63 | PersistentCollection into(PersistentCollection coll, Iterable iter) { 64 | return _dispatch(coll, 65 | op: 'into', 66 | map:()=> (coll as PMap).withTransient((TMap t) => iter.forEach((arg) => t.doAssoc(_firstP(arg), _secondP(arg)))), 67 | vec:()=> (coll as PVec).withTransient((t) => iter.forEach((arg) => t.doPush(arg))), 68 | set:()=> (coll as PSet).withTransient((t) => iter.forEach((arg) => t.doInsert(arg))) 69 | ); 70 | } 71 | /** 72 | * Returns a new collection which is the result of inserting new keys and values 73 | * into [PersistentIndexedCollection] [coll] ([PMap]/[PVec]). 74 | * Accepts up to 9 key:value positional arguments. If you need more arguments use [assocI] with [Iterable]. 75 | * 76 | * Example: 77 | * PersistentMap pm = persist({}); 78 | * assoc(pm, 'a', 5, 'b', 6); // == persist({'a': 5, 'b': 6}) 79 | * 80 | * PersistentVector p = persist([1, 2, 3]); 81 | * assoc(pm, 0, 'a', 2, 'b', 0, 'c'); // == persist(['c', 2, 'b']) 82 | */ 83 | PersistentCollection assoc(PersistentIndexedCollection coll, key0, val0, [ 84 | key1 = _none, val1 = _none, 85 | key2 = _none, val2 = _none, 86 | key3 = _none, val3 = _none, 87 | key4 = _none, val4 = _none, 88 | key5 = _none, val5 = _none, 89 | key6 = _none, val6 = _none, 90 | key7 = _none, val7 = _none, 91 | key8 = _none, val8 = _none, 92 | key9 = _none, val9 = _none 93 | ]) { 94 | var argsAll = [[key0,val0], 95 | [key1,val1], 96 | [key2,val2], 97 | [key3,val3], 98 | [key4,val4], 99 | [key5,val5], 100 | [key6,val6], 101 | [key7,val7], 102 | [key8,val8], 103 | [key9,val9]]; 104 | argsAll.forEach((a) => (a[0] != _none && a[1] ==_none)? 105 | throw new ArgumentError("Even number of keys and values is required") : null); 106 | var varArgs = argsAll.where((x) => x[0]!= _none && x[1] != _none); 107 | return assocI(coll, varArgs); 108 | } 109 | 110 | /** 111 | * Returns a new collection which is the result of adding all elements of [iter] 112 | * into [PersistentIndexedCollection] [coll] ([PMap]/[PVec]). 113 | * Elements of [iter] should be [Pair] or [List] with 2 arguments. 114 | * 115 | * Example: 116 | * PersistentMap pm1 = persist({}); 117 | * assoc(pm1, [['a', 5], new Pair('b', 6)]); // == persist({'a': 5, 'b': 6}) 118 | * 119 | * PersistentVector pm2 = persist([1, 2, 3]); 120 | * assoc(pm2, [[0, 'a'], [2, 'b'], [0, 'c']]); // == persist(['c', 2, 'b']) 121 | */ 122 | PersistentCollection assocI(PersistentIndexedCollection coll, Iterable iter) { 123 | return _dispatch(coll, 124 | op: 'assocI', 125 | map:()=> into(coll, iter), 126 | vec:()=> (coll as PVec).withTransient((t) => iter.forEach((arg) => t[_firstP(arg)] = _secondP(arg))) 127 | ); 128 | } 129 | 130 | /** 131 | * Returns a new [PersistentCollection] which is the result of removing keys from 132 | * persistent [coll] ([PMap]/[PSet]/[PVec]). 133 | * Accepts up to 9 key:value positional arguments. 134 | * If you need more arguments use [dissocI] with [Iterable]. 135 | * 136 | * Removing not existing key do nothing. 137 | * 138 | * Example: 139 | * PersistentMap p = persist({'a': 10, 'b':15, 'c': 17}); 140 | * dissoc(p, 'c', 'b'); // == persist({'a': 10}) 141 | * dissoc(p, 'a'); // == persist({'b': 15, 'c': 17}) 142 | * 143 | * PSet p = persist(['a', 'b', 'c']); 144 | * dissoc(p, 'c', 'b'); // == persist(['a']) 145 | * dissoc(p, 'a'); // == persist(['b', 'c']) 146 | */ 147 | PersistentCollection dissoc(PersistentCollection coll, arg0, [arg1 = _none, arg2 = _none, arg3 = _none, arg4 = _none, arg5 = _none, arg6 = _none, arg7 = _none, arg8 = _none, arg9 = _none]) { 148 | var varArgs = [arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9].where((x) => x != _none); 149 | return dissocI(coll, varArgs); 150 | } 151 | 152 | /** 153 | * Returns a new [Persistent] collection which is the result of removing all 154 | * keys in [iter] from persistent [coll] 155 | * ([PMap]/[PSet]/[PVec]). 156 | * 157 | * Example: 158 | * PersistentMap p = persist({'a': 10, 'b':15, 'c': 17}); 159 | * dissocI(p, ['c', 'b']); // == persist({'a': 10}) 160 | * dissocI(p, ['a']); // == persist({'b': 15, 'c': 17}) 161 | * 162 | * PSet p = persist(['a', 'b', 'c', 'd']); 163 | * dissocI(p, ['a', 'b']); // == persist(['c', 'd']) 164 | * 165 | * PersistentVector p = persist(['a', 'b', 'c', 'd']); 166 | * dissocI(p, [0, 2]); // == persist(['b', 'd']) 167 | * 168 | */ 169 | PersistentCollection dissocI(PersistentCollection coll, Iterable iter){ 170 | return _dispatch(coll, 171 | op: 'dissocI', 172 | map:()=> (coll as PMap).withTransient((TMap t) => 173 | iter.forEach((arg) => t.doDelete(arg, missingOk: true))), 174 | vec:()=> _dissocFromVector(coll as PVec, iter), 175 | set:()=> (coll as PSet).withTransient((TSet t) => 176 | iter.forEach((arg) => t.doDelete(arg, missingOk: true))) 177 | ); 178 | } 179 | 180 | //TODO implement doDelete from Vector more effective 181 | PVec _dissocFromVector(PVec pv, Iterable indexes) { 182 | var r = []; 183 | var indexesSet = indexes.toSet(); 184 | for(int i =0;i< pv.length;i++) (!indexesSet.contains(i))? r.add(pv[i]) : null; 185 | return per(r); 186 | } 187 | 188 | 189 | /** 190 | * Returns a new [PVec] which is the result of removing duplicate elements inside [iter]. 191 | * 192 | * Example: 193 | * PersistentVector p = persist([1, 2, 1, 3, 1, 2]); 194 | * distinct(p); // == persist([1, 2, 3]) 195 | */ 196 | PVec distinct(Iterable iter) { 197 | var r = []; 198 | var s = new Set(); 199 | iter.forEach((i) { 200 | if (!s.contains(i)) { 201 | r.add(i); 202 | s.add(i); 203 | } 204 | }); 205 | return persist(r); 206 | } 207 | 208 | /** 209 | * Returns a new empty collection of the same type as [coll] ([PMap]/[PSet]/[PVec]). 210 | * 211 | * Example: 212 | * PersistentVector pv = persist([1, 2, 3]); 213 | * empty(pv); // == persist([]) 214 | * PersistentMap pv = persist({'a': 10, 'b': 11}); 215 | * empty(pv); // == persist({}) 216 | */ 217 | PersistentCollection empty(PersistentCollection coll) { 218 | return _dispatch(coll, 219 | op: 'empty', 220 | map:()=> new PMap(), 221 | vec:()=> new PVec(), 222 | set:()=> new PSet() 223 | ); 224 | } 225 | 226 | /** 227 | * Returns true if persistent [coll] ([PMap]/[PSet]/[PVec]) contains [key]. 228 | * As for [PSet], true is returned if [key] is element of [coll]. 229 | * As for [PVec], true is returned if [key] is correct index in [coll]. 230 | * 231 | * Example: 232 | * PersistantVector pv = persist([1, 2, 5, 7]); 233 | * hasKey(pv, 0); // == true 234 | * hasKey(pv, 3); // == true 235 | * hasKey(pv, 'a'); // == false 236 | * hasKey(pv, -1); // == false 237 | * 238 | * PersistantSet ps = persist(new Set.from(['a', 'b'])); 239 | * hasKey(ps, 'a'); // == true 240 | * hasKey(ps, 0); // == false 241 | * 242 | * PersistantMap pm = persist({'a' : 10, 'b': 18}); 243 | * hasKey(pm, 'a'); // == true 244 | * hasKey(pm, 'c'); // == false 245 | */ 246 | bool hasKey(PersistentCollection coll, key) { 247 | return _dispatch(coll, 248 | op: 'hasKey', 249 | map:()=> (coll as PMap).containsKey(key), 250 | vec:()=> key >= 0 && key < (coll as PVec).length, 251 | set:()=> (coll as PSet).contains(key) 252 | ); 253 | } 254 | 255 | //TODO Sadly, it doesn't throw any specific Exceptions 256 | /** 257 | * Returns an element of persistent [coll] ([PMap]/[PSet]/[PVec]) stored under 258 | * [key]. [PVec] takes [key] as index. [PSet] takes [key] as element of that set 259 | * and returns it. 260 | * Optional argument [notFound] is returned if [coll] doesn't have that key. 261 | * If you don't specify [notFound] and [key] is missing in [coll], then [Exception] is thrown. 262 | * [null] is a valid value for [notFound] and results in same behaviour as dart maps. 263 | * 264 | * Example: 265 | * PersistantMap pm = persist({'a': 10}); 266 | * get(pm, 'a'); // == 10 267 | * get(pm, 'b'); // throws ... 268 | * get(pm, 'b', null); // null 269 | * 270 | * PersistentVector = persist([11, 22, 33]); 271 | * get(pv, 1); // == 22 272 | * get(pv, -1); // throw .. 273 | * get(pv, -1, 10); // == 10 274 | * 275 | * PSet ps = persist(new Set.from(['a', 'b'])); 276 | * get(ps, 'a'); // == 'a' 277 | * get(ps, 'c'); // throw .. 278 | * get(ps, 'c', 17); // 17 279 | */ 280 | dynamic get(PersistentCollection coll, key, [notFound = _none]) { 281 | return (coll as dynamic).get(key, notFound); 282 | } 283 | 284 | //TODO Sadly, it doesn't throw any specific Exceptions 285 | /** 286 | * Returns an element form recursive persistent [coll] under path of keys. 287 | * [coll] can contain nested [PMap]s, [PVec]s and [PSet]s. 288 | * Optional argument [notFound] is returned if [coll] doesn't have that key path. 289 | * If you don't specify [notFound] and key path is missing in [coll], then [Exception] is thrown. 290 | * 291 | * Example: 292 | * PersistentMap pm = persist({'a': {'b': 10}}); 293 | * getIn(pm, ['a', 'b']); // == 10 294 | * getIn(pm, ['a', 'b', 'c']); // throw 295 | * getIn(pm, ['a', 'b', 'c'], null); // null 296 | * 297 | * PersistentVector pv = persist([{'a': 0}, 5]); 298 | * getIn(pv, [0, 'a']); // == 0 299 | * getIn(pv, [0, 'b']); // throws 300 | * getIn(pv, [0, 'b'], 47); // 47 301 | */ 302 | getIn(PersistentCollection coll, Iterable keys, [notFound = _none]) { 303 | try { 304 | return _getIn(coll, keys, notFound); 305 | } catch (e){ 306 | throw new ArgumentError("Key path $keys doesn't exist in $coll, $e"); 307 | } 308 | } 309 | 310 | _getIn(PersistentCollection coll, Iterable keys, [notFound = _none]) { 311 | if (keys.length == 0) return coll; 312 | if (keys.length == 1) return get(coll, keys.first, notFound); 313 | return getIn(get(coll, keys.first, persist({})), keys.skip(1), notFound); 314 | } 315 | 316 | /** 317 | * Return [Pair] of [key] and result of [get] ([coll], [key], [notFound]). 318 | * 319 | * Example: 320 | * PersistentMap pm = persist({'a' : 10}); 321 | * find(pm, 'a'); // == new Pair('a', 10); 322 | * find(pm, 'b'); // throw 323 | * find(pm, 'b', 15); // == new Pair('b', 15) 324 | */ 325 | Pair find(PersistentCollection coll, key, [notFound = _none]) { 326 | return new Pair(key, get(coll, key, notFound)); 327 | } 328 | 329 | /** 330 | * Returns a new persistent collection which is the result of [assoc] of [val] under [keys] path in [coll]. 331 | * If key path is not present in collection, then [Exception] is thrown 332 | * 333 | * Example: 334 | * PersistentMap pm = persist({'a': [1, 2, 3]}); 335 | * assocIn(pm, ['a', 0], 17); // == persist({'a': [17, 2, 3]); 336 | * 337 | * PersistenMap pm = persist({'a': {'b': 10}}); 338 | * assocIn(pm, ['a', 'c'], 17); // == persist({'a': {'b': 10, 'c': 17}); 339 | * assocIn(pm, ['a', 'c', 'd'], 17); // throws 340 | */ 341 | dynamic assocIn(PersistentIndexedCollection coll, Iterable keys, val) { 342 | try{ 343 | return _assocIn(coll, keys, val); 344 | } catch (e) { 345 | rethrow; 346 | throw new Exception("Key path $keys doesn't exist in coll, $e"); 347 | } 348 | } 349 | 350 | dynamic _assocIn(PersistentIndexedCollection coll, keys, val) { 351 | if (keys.length == 0) return val; 352 | if (keys.length == 1) { 353 | return assoc(coll, keys.first, persist(val)); 354 | } else { 355 | return assoc(coll, keys.first, _assocIn(get(coll, keys.first), keys.skip(1), val)); 356 | } 357 | } 358 | 359 | 360 | /** 361 | * Returns a new persistent collection which is the result of [assoc] of apllied [f] to value under [keys] path in [coll]. 362 | * If only the last key from path is not present, [f] is called without arguments and result is added to return value. 363 | * If other part of key path is not present in collection, then exception is thrown. 364 | * 365 | * Example: 366 | * PersistentMap pm = persist({'a': {'b': 10}}); 367 | * inc(x) => x+1; 368 | * maybeInc([x]) => (x != null)? x+1 : 0; 369 | * updateIn(pm, ['a', 'b'], inc) // == persist({'a': {'b': 11}}) 370 | * updateIn(pm, ['a', 'c'], inc) // throws 371 | * updateIn(pm, ['a', 'c'], maybeInc) // == persist({'a': {'b': 10, 'c': 0}}) 372 | */ 373 | dynamic updateIn(PersistentIndexedCollection coll, Iterable keys, Function f) { 374 | try{ 375 | return _updateIn(coll, keys, f); 376 | } catch (e) { 377 | rethrow; 378 | // throw new Exception("Key path $keys doesn't exist in coll, $e"); 379 | } 380 | } 381 | 382 | dynamic _updateIn(PersistentIndexedCollection coll, Iterable keys, f) { 383 | if (keys.length == 0) return f(coll); 384 | if (keys.length == 1) { 385 | return assoc(coll, keys.first, hasKey(coll, keys.first)? f(get(coll,keys.first)) : f()); 386 | } else { 387 | return assoc(coll, keys.first, _updateIn(get(coll, keys.first), keys.skip(1), f)); 388 | } 389 | } 390 | 391 | /** 392 | * Take iterable [s0] as keys and [s1] as values and return [PMap] constructed from key/value pairs. 393 | * If they have different length, the longer is trimmed to shorter one. 394 | * 395 | * Example: 396 | * zipmap(['a', 'b'], [1, 2]); // == persist({'a': 1, 'b': 2}) 397 | * zipmap(['a', 'b', 'c'], [1, 2, 3, 4, 5]); // == persist({'a': 1, 'b': 2, 'c': 3}) 398 | * zipmap(['a', 'b', 'c', 'd', 'e'], [1, 2, 3]); // == persist({'a': 1, 'b': 2, 'c': 3}) 399 | */ 400 | PMap zipmap(Iterable s0, Iterable s1) { 401 | var m = {}; 402 | while (s0.isNotEmpty && s1.isNotEmpty) { 403 | m[s0.first] = s1.first; 404 | s0 = s0.skip(1); 405 | s1 = s1.skip(1); 406 | } 407 | return persist(m); 408 | } 409 | 410 | /** 411 | * Returns a new [PVec] as subvector from [vector], starting at index [start] inclusive and 412 | * ending at index [end] excusive. If [end] is not set, the length of [vector] is taken as [end] index. 413 | * If they overlap, empty vector is returned. 414 | * 415 | * Example: 416 | * PersistentVector pv = persist([1, 2, 3, 4]); 417 | * subvec(pv, 0); // == persist([1, 2, 3, 4]) 418 | * subvec(pv, 2); // == persist([3, 4]) 419 | * subvec(pv, 1, 3); // == persist([2, 3]) 420 | * subvec(pv, -1, 1); // == persist([1]) 421 | * subvec(pv, 10, -5); // == persist([]) 422 | */ 423 | PVec subvec(PVec vector, start, [end]) { 424 | if (end == null) end = vector.length; 425 | var numberOfElem = end-start; 426 | if (numberOfElem < 0) numberOfElem = 0; 427 | return persist(vector.skip(start).take(numberOfElem)); 428 | } 429 | 430 | /** 431 | * Returns the number of elements in [coll] ([PMap]/[PSet]/[PVec]). 432 | * 433 | * Example: 434 | * PersistentVector pv = persist([1, 2, 3]); 435 | * count(pv); // == 3 436 | * 437 | * PersistentMap pm = persist({'a': 10, 'b': 17}); 438 | * count(pm); // == 2 439 | * 440 | * PSet ps = persist(new Set.from('a')); 441 | * count(ps); // == 1 442 | */ 443 | num count(PersistentCollection coll) => (coll as Iterable).length; 444 | 445 | /** 446 | * Returns whether [coll] ([PMap]/[PSet]/[PVec]) is empty. 447 | * 448 | * Example: 449 | * PersistentVector pv = persist([1, 2, 3]); 450 | * isEmpty(pv); // == false 451 | * 452 | * PersistentMap pm = persist({}); 453 | * isEmpty(pm); // == true 454 | * 455 | * PSet ps = persist(new Set.from('a')); 456 | * isEmpty(ps); // == false 457 | */ 458 | bool isEmpty(PersistentCollection coll) => (coll as Iterable).isEmpty; 459 | 460 | /** 461 | * Reverse order of iteration on [coll]. 462 | * 463 | * Example: 464 | * PersistentVector pv = persist([1, 2, 3]); 465 | * reverse(pv); // == iterable(1, 2, 3) 466 | */ 467 | Iterable reverse(PersistentCollection coll) => persist((coll as Iterable).toList().reversed); 468 | 469 | /** 470 | * Get iterable from keys of [map] ([PMap]). 471 | * 472 | * Example: 473 | * PersistentMap pm = persist({'a' : 10, 'c': 11}); 474 | * keys(pm); // == iterable('a', 'b') 475 | */ 476 | Iterable keys(PMap map) => map.keys; 477 | 478 | /** 479 | * Get iterable from values of [map] ([PMap]). 480 | * 481 | * Example: 482 | * PersistentMap pm = persist({'a' : 10, 'c': 11}); 483 | * values(pm); // == iterable(10, 11) 484 | */ 485 | Iterable values(PMap map) => map.values; 486 | 487 | /** 488 | * Returns a new [PSet] as result of removing [elem] from [coll]. 489 | * It is an inverted operation to [conj]. 490 | * If element is not in [coll], original [coll] is returned. 491 | * 492 | * Example: 493 | * PSet ps = persist(new Set.from(['a', 'b'])); 494 | * disj(ps, 'a'); // == persist(new Set.from(['b'])); 495 | */ 496 | PSet disj(PSet coll, elem) => coll.delete(elem, missingOk: true); 497 | 498 | /** 499 | * Returns a new [PSet] as union of [s1] and [s2]. 500 | * 501 | * Example: 502 | * PSet ps1 = persist(new Set.from(['a', 'b'])); 503 | * PSet ps1 = persist(new Set.from(['b', 'c'])); 504 | * union(ps1, ps2); // == persist(new Set.from(['a', 'b', 'c'])); 505 | */ 506 | PSet union(PSet s1, PSet s2) => s1.union(s2); 507 | 508 | /** 509 | * Returns a new [PSet] as intersection of [s1] and [s2]. 510 | * 511 | * Example: 512 | * PSet ps1 = persist(new Set.from(['a', 'b'])); 513 | * PSet ps1 = persist(new Set.from(['b', 'c'])); 514 | * intersection(ps1, ps2); // == persist(new Set.from(['b'])); 515 | */ 516 | PSet intersection(PSet s1, PSet s2) => s1.intersection(s2); 517 | 518 | /** 519 | * Returns a new [PSet] as difference of [s1] and [s2]. 520 | * 521 | * Example: 522 | * PSet ps1 = persist(new Set.from(['a', 'b'])); 523 | * PSet ps1 = persist(new Set.from(['b', 'c'])); 524 | * difference(ps1, ps2); // == persist(new Set.from(['a'])); 525 | */ 526 | PSet difference(PSet s1, PSet s2) => s1.difference(s2); 527 | 528 | /** 529 | * Returns true if [PSet] [s1] is a subset of [s2]. 530 | * Inverse operation is [isSuperset]. 531 | * 532 | * Example: 533 | * PSet ps1 = persist(new Set.from(['a', 'b', 'c'])); 534 | * PSet ps1 = persist(new Set.from(['b', 'c'])); 535 | * isSubset(ps2, ps1); // == true 536 | * isSubset(ps1, ps2); // == false 537 | */ 538 | bool isSubset(PSet s1, PSet s2) => intersection(s1,s2) == s1; 539 | 540 | 541 | /** 542 | * Returns true if [PSet] [s1] is a superset of [s2]. 543 | * Inverse operation is [isSubset]. 544 | * 545 | * Example: 546 | * PSet ps1 = persist(new Set.from(['a', 'b', 'c'])); 547 | * PSet ps1 = persist(new Set.from(['b', 'c'])); 548 | * isSubset(ps2, ps1); // == false 549 | * isSubset(ps1, ps2); // == true 550 | */ 551 | bool isSuperset(PSet s1, PSet s2) => isSubset(s2, s1); 552 | 553 | // ------------------- SEQUENCES --------------------- 554 | 555 | first(Iterable i) => i.isEmpty ? null: i.first; 556 | 557 | Iterable rest(Iterable i) => i.skip(1); 558 | 559 | Iterable seq(dynamic c) { 560 | if (c is Iterable) { 561 | return c; 562 | } else if (c is String){ 563 | return c.split(''); 564 | } else if (c is Map) { 565 | return persist(c); 566 | } 567 | throw new ArgumentError("Can't convert ${c.runtimeType} to Iterable"); 568 | } 569 | 570 | cons(dynamic val, dynamic coll) => [[val], seq(coll)].expand((x) => x); 571 | 572 | concatI(Iterable a) => a.map((e) => seq(e)).expand((x) => x); 573 | 574 | concat(arg0, arg1, [arg2 = _none, arg3 = _none, arg4 = _none, arg5 = _none, arg6 = _none, arg7 = _none, arg8 = _none, arg9 = _none]) 575 | => concatI([arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9].where((x) => x != _none)); 576 | 577 | //flatten ?? what to do on maps? sets? 578 | 579 | void forEach(Iterable i, f) => i.forEach(f); 580 | 581 | Iterable map(f, Iterable i) => i.map(f); 582 | 583 | Iterable mapcatI(f, Iterable ii) => concatI(ii.map((i) => map(f, i))); 584 | 585 | mapcat(f, arg0, arg1, [arg2 = _none, arg3 = _none, arg4 = _none, arg5 = _none, arg6 = _none, arg7 = _none, arg8 = _none, arg9 = _none]) 586 | => mapcatI(f, [arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9].where((x) => x != _none)); 587 | 588 | Iterable filter(pred, Iterable coll) => coll.where(pred); 589 | 590 | Iterable remove(pred, Iterable coll) => filter((x) => !x, coll); 591 | 592 | //TODO maybe descrive better what it does 593 | /** reduce(f, collection) 594 | * reduce(f, intialValue, collection) 595 | * 596 | * Reduce take function [f] of up to two arguments collection and optionaly 597 | * initial value as second argument. Collection is provided as second argument or 598 | * as third argument if initial value is provided 599 | * 600 | * If called on empty collection f() is returned. 601 | * If called on collection with one item this item is returned. 602 | * If called with at least two elements 603 | * - and initial value is provided then collection is foleded with initial value. 604 | * - with no initial value rest of collection is folded with first element and f. 605 | */ 606 | reduce(f, second, [third = _none]) { 607 | Iterable seq; 608 | var initialVal = _none; 609 | if (third == _none) { 610 | seq = second; 611 | } else { 612 | seq = third; 613 | initialVal = second; 614 | } 615 | if (seq.isEmpty) return f(); 616 | if (rest(seq).isEmpty) return first(seq); 617 | return (initialVal == _none)? rest(seq).fold(first(seq), f) : seq.fold(initialVal, f); 618 | } 619 | 620 | //reduce_kv do we want it ??? 621 | 622 | Iterable take(n, Iterable i) => i.take(n); 623 | Iterable takeWhile(pred, Iterable i) => i.takeWhile(pred); 624 | Iterable drop(n, Iterable i) => i.skip(n); 625 | Iterable dropWhile(n, Iterable i) => i.skipWhile(n); 626 | dynamic some(pred, Iterable i) => i.firstWhere(pred); 627 | bool every(pred, Iterable i) => i.every(pred); 628 | 629 | //sort clash with usefull ?? 630 | //sort_by clash with usefull ?? 631 | 632 | Iterable interpose(val, Iterable i) => rest(i.expand((x) => [val, x])); 633 | //interleave(coll1, coll2, ...) 634 | Iterable repeat(Iterable i) => quiver.cycle(i); 635 | --------------------------------------------------------------------------------