├── analysis_options.yaml ├── .gitignore ├── benchmark ├── run.sh ├── map_bench.dart ├── map_bench_wordcount.dart ├── src │ ├── benchmark.dart │ ├── simple_map_2.dart │ └── simple_map_1.dart └── map_bench_wordcount.csv ├── example ├── main.dart ├── set_example.dart └── map_example.dart ├── pubspec.yaml ├── lib ├── persistent.dart └── src │ ├── pair.dart │ ├── option.dart │ ├── set.dart │ ├── set_impl.dart │ ├── linked_list.dart │ ├── map.dart │ └── map_impl.dart ├── CHANGELOG.md ├── test ├── run.sh ├── src │ ├── set_model.dart │ ├── map_model.dart │ └── test_util.dart ├── map_test.dart ├── option_test.dart └── set_test.dart ├── README.md ├── LICENSE └── haskell-model ├── ImTest.hs ├── SmallMap.hs └── ImMap.hs /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | analyzer: 2 | strong-mode: true 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .packages 3 | persistent.iml 4 | pubspec.lock 5 | 6 | -------------------------------------------------------------------------------- /benchmark/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." && pwd )" 4 | 5 | DART_VERSION=`dart --version 2>&1` 6 | GIT_HASH=`git rev-parse HEAD` 7 | DATE=`date -u` 8 | SCORE=`dart $ROOT_DIR/benchmark/map_bench_wordcount.dart` 9 | 10 | echo "\"$( echo $DART_VERSION | sed "s/\"/\"\"/g" )\", $GIT_HASH, $DATE, $SCORE" >> $ROOT_DIR/benchmark/map_bench_wordcount.csv 11 | 12 | -------------------------------------------------------------------------------- /example/main.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, 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 | import 'map_example.dart' as map_example; 7 | import 'set_example.dart' as set_example; 8 | 9 | main() { 10 | map_example.main(); 11 | set_example.main(); 12 | } -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: persistent 2 | version: 1.0.0 3 | author: Paul Brauner 4 | description: > 5 | Efficient persistent data structures for Dart. 6 | "Persistent" means immutable here, not "saved on disk". 7 | homepage: https://github.com/polux/persistent 8 | environment: 9 | sdk: '>=0.8.10+6 <2.0.0' 10 | dev_dependencies: 11 | args: '>=0.9.0 <0.13.0' 12 | enumerators: '>=0.6.0 <0.7.0' 13 | http: '>=0.9.0 <0.12.0' 14 | propcheck: '>=0.6.0 <0.7.0' 15 | unittest: '>=0.9.0 <0.12.0' 16 | -------------------------------------------------------------------------------- /lib/persistent.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 persistent; 7 | 8 | import 'dart:collection'; 9 | import 'dart:math'; 10 | 11 | part 'src/map.dart'; 12 | part 'src/map_impl.dart'; 13 | part 'src/set.dart'; 14 | part 'src/set_impl.dart'; 15 | part 'src/linked_list.dart'; 16 | part 'src/option.dart'; 17 | part 'src/pair.dart'; 18 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Persistent Changelog 2 | 3 | ## 1.0.0 4 | 5 | - Pass dart 2 analyzer 6 | 7 | ## 0.9.0 8 | 9 | - Make strong mode compliant 10 | 11 | ## 0.8.3 12 | 13 | - Remove call to deprecated function in args parsing 14 | - Use new enumerators' `apply` in tests 15 | - Bump dependencies' upper bounds 16 | 17 | ## 0.8.2 18 | 19 | - Mode run_all_tests.sh to tests/run.sh to adhere to pub.drone.io's 20 | conventions. 21 | 22 | ## 0.8.1 23 | 24 | - Added `CHANGELOG.md` 25 | - Moved benchmarks to the standard `benchmark` directory 26 | -------------------------------------------------------------------------------- /lib/src/pair.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 | class Pair { 9 | final A fst; 10 | final B snd; 11 | 12 | Pair(this.fst, this.snd); 13 | 14 | bool operator ==(Object other) { 15 | return (other is Pair) && fst == other.fst && snd == other.snd; 16 | } 17 | 18 | int get hashCode => fst.hashCode + 31 * snd.hashCode; 19 | 20 | String toString() => "Pair($fst, $snd)"; 21 | } 22 | -------------------------------------------------------------------------------- /benchmark/map_bench.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_bench; 7 | 8 | import 'package:persistent/persistent.dart'; 9 | 10 | import 'dart:math'; 11 | 12 | part 'src/benchmark.dart'; 13 | part 'src/simple_map_1.dart'; 14 | part 'src/simple_map_2.dart'; 15 | 16 | void main() { 17 | Benchmark.warmup(); 18 | for (int n = 0; n < 20000; n += 100) { 19 | Benchmark benchmark = new Benchmark(n); 20 | print("$n: ${benchmark.bench()}"); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." && pwd )" 4 | 5 | dartanalyzer $ROOT_DIR/lib/*.dart \ 6 | && dartanalyzer $ROOT_DIR/test/*.dart \ 7 | && dartanalyzer $ROOT_DIR/benchmark/*.dart \ 8 | && dartanalyzer $ROOT_DIR/example/*.dart \ 9 | && dart --enable-checked-mode $ROOT_DIR/example/map_example.dart \ 10 | && dart --enable-checked-mode $ROOT_DIR/example/set_example.dart \ 11 | && dart --enable-checked-mode $ROOT_DIR/test/option_test.dart \ 12 | && dart $ROOT_DIR/test/map_test.dart --quiet --quickCheckMaxSize=300 --smallCheckDepth=7 \ 13 | && dart --enable-checked-mode $ROOT_DIR/test/map_test.dart --quiet --quickCheckMaxSize=100 --smallCheckDepth=5 \ 14 | && dart $ROOT_DIR/test/set_test.dart --quiet --quickCheckMaxSize=200 --smallCheckDepth=10 \ 15 | && dart --enable-checked-mode $ROOT_DIR/test/set_test.dart --quiet --quickCheckMaxSize=100 --smallCheckDepth=6 16 | -------------------------------------------------------------------------------- /example/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 set_example; 7 | 8 | import 'package: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/map_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:persistent/persistent.dart'; 9 | 10 | main() { 11 | final emptyMap = new PersistentMap(); 12 | final m1 = emptyMap.insert('a', 1).insert('b', 2); 13 | final m2 = new PersistentMap.fromMap({'a': 3, 'c': 4}); 14 | 15 | print(m1); // {a: 1, b: 2} 16 | print(m2); // {c: 4, a: 3} 17 | print(m1.lookup('a')); // Option.some(1) 18 | print(m1.lookup('c')); // Option.none() 19 | 20 | final m3 = m1.delete('a'); 21 | print(m1); // {a: 1, b: 2} 22 | print(m3); // {b: 2} 23 | 24 | final m4 = m1.union(m2, (n, m) => n + m); 25 | print(m4); // {c: 4, a: 4, b: 2} 26 | 27 | final m5 = m1.mapValues((n) => n + 1); 28 | print(m5); // {a: 2, b: 3} 29 | 30 | final m6 = m1.adjust('a', (n) => n + 1); 31 | print(m6); // {a: 2, b: 2} 32 | 33 | for (final pair in m4) { 34 | print(pair); // Pair(a, 4), Pair(c, 4), Pair(b, 2) 35 | } 36 | 37 | print(m4.keys.toList()); // [a, c, b] 38 | print(m4.values.toList()); // [4, 4, 2] 39 | } 40 | -------------------------------------------------------------------------------- /benchmark/map_bench_wordcount.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:http/http.dart' as http; 3 | import 'package:persistent/persistent.dart'; 4 | 5 | add(n, m) => n + m; 6 | insertWord(map, word) => map.insert(word, 1, add); 7 | count(words) => words.fold(new PersistentMap(), insertWord); 8 | 9 | final AUSTEEN = 'http://www.gutenberg.org/files/1342/1342.txt'; 10 | final DOYLE = 'http://www.gutenberg.org/files/1661/1661.txt'; 11 | 12 | Future> getWords(String url) { 13 | return http.get(url).then((response) => response.body.split(' ')); 14 | } 15 | 16 | void run(List doyle, List austeen) { 17 | final map1 = count(austeen); 18 | final map2 = count(doyle); 19 | final result = map1.union(map2, add); 20 | if (result.length != 36028) { 21 | throw new StateError("something's wrong"); 22 | } 23 | } 24 | 25 | void bench(List doyle, List austeen) { 26 | // warmup 27 | run(doyle, austeen); 28 | 29 | final watch = new Stopwatch(); 30 | watch.start(); 31 | for (int i = 0; i < 10; i++) { 32 | run(doyle, austeen); 33 | } 34 | watch.stop(); 35 | print(watch.elapsedMilliseconds / 10); 36 | } 37 | 38 | main() { 39 | Future.wait([getWords(AUSTEEN), getWords(DOYLE)]).then( 40 | (result) => bench(result[0], result[1])); 41 | } 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Efficient Persistent Data Structures 2 | 3 | Mostly efficient persistent maps and sets for now. Also option types. 4 | 5 | "Persistent" means immutable here, not "saved on disk". 6 | 7 | ```dart 8 | import 'package:persistent/persistent.dart'; 9 | 10 | main() { 11 | final emptyMap = new PersistentMap(); 12 | final m1 = emptyMap.insert('a', 1).insert('b', 2); 13 | final m2 = new PersistentMap.fromMap({'a': 3, 'c': 4}); 14 | 15 | print(m1); // {a: 1, b: 2} 16 | print(m2); // {c: 4, a: 3} 17 | print(m1.lookup('a')); // Option.some(1) 18 | print(m1.lookup('c')); // Option.none() 19 | 20 | final m3 = m1.delete('a'); 21 | print(m1); // {a: 1, b: 2} 22 | print(m3); // {b: 2} 23 | 24 | final m4 = m1.union(m2, (n,m) => n + m); 25 | print(m4); // {c: 4, a: 4, b: 2} 26 | 27 | final m5 = m1.mapValues((n) => n + 1); 28 | print(m5); // {a: 2, b: 3} 29 | 30 | final m6 = m1.adjust('a', (n) => n + 1); 31 | print(m6); // {a: 2, b: 2} 32 | } 33 | ``` 34 | 35 | ## Try it! 36 | 37 | ``` 38 | git clone https://github.com/polux/persistent.git 39 | cd persistent 40 | pub install 41 | dart example/map_example.dart 42 | dart example/set_example.dart 43 | dart test/map_bench.dart 44 | ``` 45 | 46 | ## More 47 | 48 | See [ImplementationDetails](https://github.com/polux/persistent/wiki/ImplementationDetails) and the [generated API documentation](http://polux.github.io/persistent/continuous/persistent/PersistentMap.html) for more information. 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2012, Google Inc. All rights reserved. Redistribution and 2 | use in source and binary forms, with or without modification, are 3 | permitted provided that the following conditions are met: 4 | * Redistributions of source code must retain the above copyright 5 | notice, this list of conditions and the following disclaimer. 6 | * Redistributions in binary form must reproduce the above 7 | copyright notice, this list of conditions and the following 8 | disclaimer in the documentation and/or other materials provided 9 | with the distribution. 10 | * Neither the name of Google Inc. nor the names of its 11 | contributors may be used to endorse or promote products derived 12 | from this software without specific prior written permission. 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 15 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 16 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 17 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 18 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 19 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /benchmark/src/benchmark.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 map_bench; 7 | 8 | class Benchmark { 9 | int size; 10 | 11 | Benchmark([this.size = 1000]); 12 | 13 | static warmup() { 14 | new Benchmark(1000).bench(); 15 | } 16 | 17 | Map bench() { 18 | var res = {}; 19 | res["Linked List"] = _bench(() => new SimplePersistentMap()); 20 | res["Mutable Map"] = _bench(() => new SimplePersistentMap2()); 21 | res["Hash Trie"] = _bench(() => new PersistentMap()); 22 | return res; 23 | } 24 | 25 | int _bench(PersistentMap empty()) { 26 | final stopwatch = new Stopwatch(); 27 | stopwatch.start(); 28 | 29 | PersistentMap map = empty(); 30 | for (int i = 0; i < size; i++) { 31 | map = map.insert("key$i", "foo", (String x, String y) => x + y); 32 | map = map.insert("key$i", "bar", (String x, String y) => x + y); 33 | } 34 | 35 | for (int i = size * 2; i >= 0; i--) { 36 | map.lookup("key$i"); 37 | } 38 | for (int i = 0; i <= size * 2; i++) { 39 | map.lookup("key$i"); 40 | } 41 | 42 | PersistentMap saved = map; 43 | for (int i = size * 2; i >= 0; i--) { 44 | map = map.delete("key$i"); 45 | } 46 | map = saved; 47 | for (int i = 0; i < size * 2; i++) { 48 | map = map.delete("key$i"); 49 | } 50 | 51 | stopwatch.stop(); 52 | return stopwatch.elapsedMicroseconds; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /test/src/set_model.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 test_util; 7 | 8 | /** 9 | * Naive implementation of PersistentSet using dart:core [Set]s. 10 | */ 11 | class ModelSet extends PersistentSetBase { 12 | Set zet; 13 | 14 | ModelSet(this.zet); 15 | 16 | ModelSet insert(E element) { 17 | Set newset = new Set.from(zet); 18 | newset.add(element); 19 | return new ModelSet(newset); 20 | } 21 | 22 | ModelSet delete(E element) { 23 | Set newset = new Set.from(zet); 24 | newset.remove(element); 25 | return new ModelSet(newset); 26 | } 27 | 28 | ModelSet union(PersistentSet other) { 29 | Set newset = new Set.from(zet); 30 | newset.addAll((other as ModelSet).zet); 31 | return new ModelSet(newset); 32 | } 33 | 34 | ModelSet difference(PersistentSet other) { 35 | Set newset = new Set.from(zet); 36 | newset.removeAll((other as ModelSet).zet); 37 | return new ModelSet(newset); 38 | } 39 | 40 | ModelSet> cartesianProduct(PersistentSet other) { 41 | Set newset = new Set(); 42 | for (E e1 in zet) { 43 | for (final e2 in (other as ModelSet).zet) { 44 | newset.add(new Pair(e1, e2)); 45 | } 46 | } 47 | return new ModelSet(newset); 48 | } 49 | 50 | ModelSet intersection(PersistentSet other) { 51 | Set newset = new Set.from(zet); 52 | return new ModelSet(newset.where((E e) => other.contains(e)).toSet()); 53 | } 54 | 55 | Iterator get iterator => zet.iterator; 56 | 57 | E pickRandomElement([Random random]) { 58 | throw new UnsupportedError(""); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lib/src/option.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 | class Option { 9 | final T _value; 10 | final bool isDefined; 11 | 12 | const Option._internal(this.isDefined, this._value); 13 | 14 | factory Option.none() => const Option._internal(false, null); 15 | 16 | factory Option.some(T value) => new Option._internal(true, value); 17 | 18 | factory Option.fromNullable(T nullableValue) => nullableValue == null 19 | ? new Option.none() 20 | : new Option.some(nullableValue); 21 | 22 | T get value { 23 | if (isDefined) return _value; 24 | throw new StateError('Option.none() has no value'); 25 | } 26 | 27 | T get asNullable => isDefined ? _value : null; 28 | 29 | T orElse(T defaultValue) => isDefined ? _value : defaultValue; 30 | 31 | T orElseCompute(T defaultValue()) => isDefined ? _value : defaultValue(); 32 | 33 | /// [:forall U, Option map(U f(T value)):] 34 | Option map(f(T value)) => isDefined ? new Option.some(f(_value)) : this; 35 | 36 | /// [:forall U, Option map(Option f(T value)):] 37 | Option expand(Option f(T value)) => isDefined ? f(_value) : this; 38 | 39 | /// Precondition: [:this is Option