├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── benchmark ├── map_bench.dart ├── map_bench_wordcount.csv ├── map_bench_wordcount.dart ├── run.sh └── src │ ├── benchmark.dart │ ├── simple_map_1.dart │ └── simple_map_2.dart ├── example ├── main.dart ├── map_example.dart └── set_example.dart ├── haskell-model ├── ImMap.hs ├── ImTest.hs └── SmallMap.hs ├── lib ├── persistent.dart └── src │ ├── linked_list.dart │ ├── map.dart │ ├── map_impl.dart │ ├── option.dart │ ├── pair.dart │ ├── set.dart │ └── set_impl.dart ├── pubspec.yaml └── test ├── map_test.dart ├── option_test.dart ├── run.sh ├── set_test.dart └── src ├── map_model.dart ├── set_model.dart └── test_util.dart /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .packages 3 | persistent.iml 4 | pubspec.lock 5 | 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | analyzer: 2 | strong-mode: true 3 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /benchmark/map_bench_wordcount.csv: -------------------------------------------------------------------------------- 1 | Dart VM version: 0.5.0.1_r21823 (Mon Apr 22 14:02:11 2013), 535c1ae834b3bb23f004a712f36fdf8681d7d5db, Wed Apr 24 22:48:18 UTC 2013, 1178.0 2 | Dart VM version: 0.5.0.1_r21823 (Mon Apr 22 14:02:11 2013), 30cf32f6e403b60a8734b2d8780f39bcdee339e3, Thu Apr 25 17:40:55 UTC 2013, 1186.4 3 | Dart VM version: 0.5.1.0_r22072 (Fri Apr 26 12:54:33 2013), 32df58d26606334e00338c947c4f00fbdfc277ae, Mon Apr 29 21:16:52 UTC 2013, 883.4 4 | Dart VM version: 0.5.5.0_r22416 (Mon May 6 10:31:00 2013), c4c634e4d987c1461aa80e3d8627623ec04165f6, Tue May 7 21:58:28 UTC 2013, 663.5 5 | "Dart VM version: 0.5.7.2_r22611 (Fri May 10 22:19:57 2013) on ""linux_x64""", 82ef833adcb9ff1af44ca3d02431cec36a605451, Mon May 13 23:00:06 UTC 2013, 2397.7 6 | "Dart VM version: 0.5.9.0_r22879 (Sat May 18 21:32:26 2013) on ""linux_x64""", 18e8301013b444cc0a0ba89605dd784a6baf0546, Tue May 21 00:35:15 UTC 2013, 501.3 7 | "Dart VM version: 0.5.11.1_r23200 (Mon May 27 00:02:29 2013) on ""linux_x64""", c08c2f50683e6ad8647ec2572bbb99a1d368a819, Wed Jun 5 06:18:28 UTC 2013, 581.8 8 | "Dart VM version: 0.5.20.2_r24160 (Tue Jun 18 20:43:32 2013) on ""linux_x64""", 4004a85d1e6e9a9b16a39ea7d81b6cac7d80dc2f, Wed Jun 19 19:09:31 UTC 2013, 513.6 9 | "Dart VM version: 0.6.3.3_r24898 (Thu Jul 11 07:47:12 2013) on ""linux_x64""", b1323f8a6bd330f89b48ca18e7821aab898a58c3, Tue Jul 16 00:01:25 UTC 2013, 522.8 10 | "Dart VM version: 0.6.5.0_r25017 (Mon Jul 15 15:01:03 2013) on ""linux_x64""", 248ae0108cce466418185170214125b9ce0768bd, Wed Jul 17 22:35:09 UTC 2013, 528.6 11 | "Dart VM version: 0.6.9.2_r25388 (Tue Jul 23 20:28:04 2013) on ""linux_x64""", 248ae0108cce466418185170214125b9ce0768bd, Fri Jul 26 20:37:00 UTC 2013, 506.0 12 | "Dart VM version: 0.6.13.0_r25630 (Tue Jul 30 14:33:28 2013) on ""linux_x64""", 8c941a35b8168300626389780bacee56c6842552, Wed Jul 31 17:12:06 UTC 2013, 553.8 13 | "Dart VM version: 0.6.15.3_r25822 (Tue Aug 6 13:39:55 2013) on ""linux_x64""", 196bed96bf2a209249aa945658e51567febe5006, Sun Aug 11 20:50:07 UTC 2013, 518.6 14 | "Dart VM version: 0.7.3.1_r27487 (Fri Sep 13 13:27:39 2013) on ""linux_x64""", e1fbb8e11d30a9d7f7788d8846755aac296a0af4, Tue Sep 17 22:38:58 UTC 2013, 541.5 15 | "Dart VM version: 0.7.5.3_r27776 (Mon Sep 23 14:31:35 2013) on ""linux_x64""", 800e9cc2bc700d9d87533772e8b537ae0fd42e46, Tue Sep 24 19:20:20 UTC 2013, 556.3 16 | "Dart VM version: 0.7.6.4_r28108 (Tue Oct 1 14:32:31 2013) on ""linux_x64""", 5c80ab2f0f72d2607b41a2d08c80fe680449053b, Thu Oct 3 00:54:55 UTC 2013, 519.6 17 | "Dart VM version: 0.8.5.1_r28990 (Tue Oct 22 04:50:58 2013) on ""linux_x64""", ffb7eee674f3be752956e8d99a9079ab4d3d8e3c, Tue Oct 22 19:41:31 UTC 2013, 559.7 18 | "Dart VM version: 0.8.7.0_r29341 (Mon Oct 28 02:00:50 2013) on ""linux_x64""", 147768fc8239ad23d1074b5f4029d7dab0f2113d, Mon Oct 28 22:13:49 UTC 2013, 510.3 19 | "Dart VM version: 0.8.10.8_r30039 (Thu Nov 7 03:03:33 2013) on ""linux_x64""", bd99a6c075b968fee23c541eee631e6396fbc262, Thu Nov 7 22:28:22 UTC 2013, 533.3 20 | "Dart VM version: 1.1.0-dev.5.0 (Fri Dec 20 05:55:37 2013) on ""linux_x64""", 4cdf453c59369653d3e9432632ef4420e732fb36, Sat Jan 4 15:40:45 UTC 2014, 432.6 21 | "Dart VM version: 1.2.0-dev.4.0 (Fri Feb 7 10:21:55 2014) on ""linux_x64""", 41c8cd12714847609f1647a4df5c20131cde759e, Tue Feb 11 22:37:52 UTC 2014, 463.3 22 | "Dart VM version: 1.5.0-dev.2.0 (Mon May 26 07:29:28 2014) on ""linux_x64""", d3a3d1b4917de54dc1ddbe2f61169b35f21f85fb, Sun Jun 1 16:40:46 UTC 2014, 359.7 23 | "Dart VM version: 1.5.0-dev.4.7 (Wed Jun 11 01:57:57 2014) on ""linux_x64""", dc6fe1c592a83e15d798a37f77013cafcb46c500, Thu Jun 12 09:00:32 UTC 2014, 345.6 24 | "Dart VM version: 1.6.0-dev.9.7 (Tue Aug 26 01:12:00 2014) on ""linux_x64""", 80acc43553a5cc72579899658507e707722019f0, Tue Sep 16 07:32:44 UTC 2014, 372.0 25 | "Dart VM version: 1.9.0-dev.2.2 (Sun Dec 21 11:44:20 2014) on ""linux_x64""", b774d676e091d894be42e17229d0de52a3c3999f, Wed Dec 31 13:20:35 UTC 2014, 213.4 26 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /benchmark/src/simple_map_1.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 | /** 9 | * Naive implementation of PersistentMap using a [LinkedList] of [Pair]s. 10 | */ 11 | class SimplePersistentMap extends PersistentMapBase { 12 | final LinkedList> _list; 13 | 14 | bool get isEmpty => _list.isNil; 15 | 16 | SimplePersistentMap._internal(this._list); 17 | factory SimplePersistentMap() => new SimplePersistentMap._internal(new Nil()); 18 | 19 | PersistentMap insert(K key, V value, [V combine(V x, V y)]) { 20 | combine = (combine != null) ? combine : (V x, V y) => y; 21 | LinkedList> newList() { 22 | LinkedListBuilder> builder = 23 | new LinkedListBuilder>(); 24 | LinkedList> it = _list; 25 | while (it.isCons) { 26 | Cons> cons = it.asCons; 27 | Pair elem = cons.elem; 28 | if (elem.fst == key) { 29 | builder.add(new Pair(key, combine(elem.snd, value))); 30 | return builder.build(cons.tail); 31 | } 32 | builder.add(elem); 33 | it = cons.tail; 34 | } 35 | builder.add(new Pair(key, value)); 36 | return builder.build(); 37 | } 38 | 39 | return new SimplePersistentMap._internal(newList()); 40 | } 41 | 42 | PersistentMap delete(K key) => 43 | new SimplePersistentMap._internal(_list.strictWhere((p) => p.fst != key)); 44 | 45 | Option lookup(K key) { 46 | LinkedList> it = _list; 47 | while (it.isCons) { 48 | Cons> cons = it.asCons; 49 | Pair elem = cons.elem; 50 | if (elem.fst == key) return new Option.some(elem.snd); 51 | it = cons.tail; 52 | } 53 | return new Option.none(); 54 | } 55 | 56 | PersistentMap mapValues(U f(V value)) => 57 | new SimplePersistentMap._internal( 58 | _list.strictMap((p) => new Pair(p.fst, f(p.snd)))); 59 | 60 | forEachKeyValue(f(K key, V value)) { 61 | Map tmp = new Map(); 62 | _list.foreach((pair) { 63 | if (!tmp.containsKey(pair.fst)) tmp[pair.fst] = pair.snd; 64 | }); 65 | tmp.forEach(f); 66 | } 67 | 68 | String toString() => _list.toString(); 69 | 70 | int get length => toMap().length; 71 | 72 | PersistentMap union(PersistentMap other, [V combine(V x, V y)]) { 73 | throw new UnsupportedError("union is not supported"); 74 | } 75 | 76 | PersistentMap intersection(PersistentMap other, 77 | [V combine(V x, V y)]) { 78 | throw new UnsupportedError("intersection is not supported"); 79 | } 80 | 81 | PersistentMap adjust(K key, V update(V value)) { 82 | throw new UnsupportedError("adjust is not supported"); 83 | } 84 | 85 | Iterator> get iterator { 86 | final res = >[]; 87 | this.forEachKeyValue((k, v) { 88 | res.add(new Pair(k, v)); 89 | }); 90 | return res.iterator; 91 | } 92 | 93 | Pair pickRandomEntry([Random random]) { 94 | throw new UnsupportedError(""); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /benchmark/src/simple_map_2.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 | /** 9 | * Naive implementation of PersistentMap dart:core [Map]s. 10 | */ 11 | class SimplePersistentMap2 extends PersistentMapBase { 12 | final Map _map; 13 | 14 | bool get isEmpty => _map.isEmpty; 15 | 16 | SimplePersistentMap2._internal(this._map); 17 | factory SimplePersistentMap2() => 18 | new SimplePersistentMap2._internal(new Map()); 19 | 20 | PersistentMap insert(K key, V value, [V combine(V x, V y)]) { 21 | combine = (combine != null) ? combine : (V x, V y) => y; 22 | Map newmap = new Map.from(_map); 23 | newmap[key] = _map.containsKey(key) ? combine(_map[key], value) : value; 24 | return new SimplePersistentMap2._internal(newmap); 25 | } 26 | 27 | PersistentMap delete(K key) { 28 | Map newmap = new Map.from(_map); 29 | newmap.remove(key); 30 | return new SimplePersistentMap2._internal(newmap); 31 | } 32 | 33 | Option lookup(K key) { 34 | if (_map.containsKey(key)) { 35 | return new Option.some(_map[key]); 36 | } else { 37 | return new Option.none(); 38 | } 39 | } 40 | 41 | PersistentMap mapValues(U f(V value)) { 42 | Map newmap = new Map.from(_map); 43 | _map.forEach((K key, V value) { 44 | newmap[key] = f(value); 45 | }); 46 | return new SimplePersistentMap2._internal(newmap); 47 | } 48 | 49 | PersistentMap adjust(K key, V update(V value)) { 50 | if (_map.containsKey(key)) { 51 | Map newmap = new Map.from(_map); 52 | newmap[key] = update(_map[key]); 53 | return new SimplePersistentMap2._internal(newmap); 54 | } 55 | return this; 56 | } 57 | 58 | void forEachKeyValue(f(K key, V value)) { 59 | _map.forEach(f); 60 | } 61 | 62 | String toString() => _map.toString(); 63 | 64 | int get length => _map.length; 65 | 66 | PersistentMap union(PersistentMap other, [V combine(V x, V y)]) { 67 | throw new UnsupportedError("union is not supported"); 68 | } 69 | 70 | PersistentMap intersection(PersistentMap other, 71 | [V combine(V x, V y)]) { 72 | throw new UnsupportedError("intersection is not supported"); 73 | } 74 | 75 | Iterator> get iterator { 76 | final res = >[]; 77 | this.forEachKeyValue((k, v) { 78 | res.add(new Pair(k, v)); 79 | }); 80 | return res.iterator; 81 | } 82 | 83 | Pair pickRandomEntry([Random random]) { 84 | throw new UnsupportedError(""); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /haskell-model/ImMap.hs: -------------------------------------------------------------------------------- 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 | module ImMap ( 7 | Map(), empty, adjust, lookup, insert, delete, unionWith, toList, 8 | fromList, member, size, insertWith 9 | ) where 10 | 11 | import qualified SmallMap as IM 12 | import qualified Data.Map as M -- for cheak equality 13 | import Prelude hiding (lookup) 14 | import qualified Prelude as P 15 | import Data.Hashable 16 | import Data.Bits 17 | import Data.Maybe 18 | import Data.List (null, (\\)) 19 | import Control.Arrow((***)) 20 | 21 | data Map k v = EmptyMap 22 | | Leaf Int [(k, v)] 23 | | SubMap (IM.SmallMap (Map k v)) 24 | deriving (Show) 25 | 26 | empty = EmptyMap 27 | 28 | -- part n i selects the nth part of i 29 | part n i = (i `shiftR` (n * 5)) .&. 0x1f 30 | hash30 k = (hash k `shiftR` 2) .&. 0x3fffffff 31 | 32 | lookup :: (Eq k, Hashable k) => k -> Map k v -> Maybe v 33 | lookup k m = lookup' 0 m 34 | where 35 | code = hash30 k 36 | lookup' _ EmptyMap = Nothing 37 | lookup' _ (Leaf h assocs) | code == h = P.lookup k assocs 38 | | otherwise = Nothing 39 | lookup' l (SubMap m) = lookup' (l+1) =<< IM.lookup (part l code) m 40 | 41 | 42 | insert :: (Eq k, Hashable k) => k -> v -> Map k v -> Map k v 43 | insert k v m = insertWith const k v m 44 | 45 | insertWith :: (Eq k, Hashable k) => (v -> v -> v) -> k -> v -> Map k v -> Map k v 46 | insertWith f k v m = insertWith' f 0 (hash30 k) [(k,v)] m 47 | 48 | insertWith' :: Eq k 49 | => (v -> v -> v) -- merge function 50 | -> Int -- depth 51 | -> Int -- hashcode 52 | -> [(k, v)] -- assoc list to insert 53 | -> Map k v 54 | -> Map k v 55 | insertWith' f depth code kvs m = insertWith'' depth m 56 | where 57 | insertWith'' _ EmptyMap = Leaf code kvs 58 | insertWith'' 6 (Leaf h assocs) = Leaf code (insertAssocWith f kvs assocs) 59 | insertWith'' l lf@(Leaf code2 assocs) 60 | | code == code2 = Leaf code (insertAssocWith f kvs assocs) 61 | | otherwise = insertWith'' l (SubMap $ IM.singleton (part l code2) lf) 62 | insertWith'' l (SubMap im) = 63 | SubMap $ case IM.lookup i im of 64 | Nothing -> IM.insert i (Leaf code kvs) im 65 | Just m -> IM.insert i (insertWith'' (l+1) m) im 66 | where i = part l code 67 | 68 | insertAssocWith :: Eq k 69 | => (v -> v -> v) -- merge function 70 | -> [(k, v)] -- assoc list to insert 71 | -> [(k, v)] 72 | -> [(k, v)] 73 | insertAssocWith f kvs assocs = foldl iter assocs kvs 74 | where 75 | iter acc (k,v) = insertAssocWith' k v acc 76 | insertAssocWith' k v [] = [(k,v)] 77 | insertAssocWith' k v (p@(k', v') : assocs) 78 | | k == k' = (k, f v v') : assocs 79 | | otherwise = p : insertAssocWith' k v assocs 80 | 81 | delete :: (Eq k, Hashable k) => k -> Map k v -> Map k v 82 | delete k m = delete' 0 m 83 | where 84 | code = hash30 k 85 | delete' _ EmptyMap = EmptyMap 86 | delete' _ lf@(Leaf h assocs) 87 | | h == code = 88 | case filter (\p -> fst p /= k) assocs of 89 | [] -> EmptyMap 90 | assocs' -> (Leaf h assocs') 91 | | otherwise = lf 92 | delete' l m@(SubMap im) = 93 | case IM.lookup i im of 94 | Nothing -> m 95 | Just m' -> build $ case delete' (l+1) m' of 96 | EmptyMap -> IM.delete i im 97 | m'' -> IM.insert i m'' im 98 | where i = part l code 99 | build im = case IM.elems im of 100 | [] -> error "should never happen" 101 | [l@(Leaf _ _)] -> l 102 | _ -> SubMap im 103 | 104 | 105 | unionWith :: Eq k => (v -> v -> v) -> Map k v -> Map k v -> Map k v 106 | unionWith f m1 m2 = unionWith' 0 m1 m2 107 | where unionWith' _ EmptyMap m = m 108 | unionWith' _ m EmptyMap = m 109 | unionWith' d (SubMap sm1) (SubMap sm2) = SubMap sm 110 | where sm = IM.unionWith (unionWith' (d+1)) sm1 sm2 111 | unionWith' d m@(SubMap sm) (Leaf h kvs) = insertWith' (flip f) d h kvs m 112 | unionWith' d (Leaf h kvs) m@(SubMap sm) = insertWith' f d h kvs m 113 | unionWith' d (Leaf h1 kvs1) m@(Leaf h2 kvs2) = insertWith' f d h1 kvs1 m 114 | 115 | adjust :: (Eq k, Hashable k) => k -> (v -> v) -> Map k v -> Map k v 116 | adjust k f m = adjust' 0 m 117 | where code = hash30 k 118 | adjust' _ e@EmptyMap = e 119 | adjust' _ l@(Leaf h kvs) | h == code = Leaf h (adjust'' kvs) 120 | | otherwise = l 121 | adjust' d (SubMap sm) = SubMap (IM.adjust (adjust' (d+1)) (part d code) sm) 122 | 123 | adjust'' [] = [] 124 | adjust'' (kv@(k',v):kvs) | k' == k = (k', f v):kvs 125 | | otherwise = kv : adjust'' kvs 126 | 127 | size :: Map k v -> Int 128 | size EmptyMap = 0 129 | size (Leaf _ kvs) = length kvs 130 | size (SubMap sm) = IM.fold (\m a -> size m + a) 0 sm 131 | 132 | toList EmptyMap = [] 133 | toList (Leaf _ xs) = xs 134 | toList (SubMap sm) = concatMap toList (IM.elems sm) 135 | 136 | member k = isJust . lookup k 137 | 138 | fromList l = foldl (\m (k,v) -> insert k v m) EmptyMap l 139 | 140 | instance Functor (Map k) where 141 | fmap f m = fmap' m 142 | where fmap' EmptyMap = EmptyMap 143 | fmap' (Leaf h kvs) = Leaf h (map (id *** f) kvs) 144 | fmap' (SubMap sm) = SubMap (IM.map fmap' sm) 145 | 146 | instance (Eq k, Eq v) => Eq (Map k v) where 147 | EmptyMap == EmptyMap = True 148 | Leaf h1 kvs1 == Leaf h2 kvs2 = h1 == h2 && eqPermut kvs1 kvs2 149 | where eqPermut xs ys = null (xs \\ ys) 150 | SubMap sm1 == SubMap sm2 = sm1 == sm2 151 | _ == _ = False 152 | -------------------------------------------------------------------------------- /haskell-model/ImTest.hs: -------------------------------------------------------------------------------- 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 | import qualified Data.Map as M 7 | import qualified ImMap as IM 8 | 9 | import Test.QuickCheck 10 | import Control.Applicative 11 | import Data.Hashable 12 | import Control.Arrow((***)) 13 | import Text.Show.Functions 14 | 15 | -- a datatype with an imperfect hash function 16 | data Key = Key String Bool 17 | deriving (Show, Eq, Ord) 18 | 19 | instance Hashable Key where 20 | hash (Key s _) = hash s 21 | 22 | instance Arbitrary Key where 23 | arbitrary = Key <$> arbitrary <*> arbitrary 24 | shrink (Key s b) = Key <$> shrink s <*> shrink b 25 | 26 | instance (Ord k, Arbitrary k, Arbitrary v) => Arbitrary (M.Map k v) where 27 | arbitrary = M.fromList <$> arbitrary 28 | shrink = map M.fromList . shrink . M.toList 29 | 30 | from = IM.fromList . M.toList 31 | same im m = M.fromList (IM.toList im) == m 32 | 33 | type Val = Int 34 | type M = M.Map Key Val 35 | 36 | equalsProp :: M -> M -> Bool 37 | equalsProp m1 m2 = (from m1 == from m2) == (m1 == m2) 38 | 39 | insertProp :: M -> Key -> Val -> (Val -> Val -> Val) -> Bool 40 | insertProp m k v f = IM.insertWith f k v (from m) `same` M.insertWith f k v m 41 | 42 | deleteProp :: M -> Key -> Bool 43 | deleteProp m k = IM.delete k (from m) `same` M.delete k m 44 | 45 | lookupProp :: M -> Key -> Bool 46 | lookupProp m k = IM.lookup k (from m) == M.lookup k m 47 | 48 | adjustProp :: M -> Key -> (Val -> Val) -> Bool 49 | adjustProp m k f = IM.adjust k f (from m) `same` M.adjust f k m 50 | 51 | fmapProp :: M -> (Val -> Val) -> Bool 52 | fmapProp m f = fmap f (from m) `same` fmap f m 53 | 54 | sizeProp :: M -> Bool 55 | sizeProp m = IM.size (from m) == M.size m 56 | 57 | unionProp :: M -> M -> (Val -> Val -> Val) -> Bool 58 | unionProp m1 m2 f = 59 | IM.unionWith f (from m1) (from m2) `same` M.unionWith f m1 m2 60 | 61 | check p = quickCheckWith args p 62 | where args = stdArgs { maxSuccess = 1000 } 63 | 64 | main = do 65 | check equalsProp 66 | check insertProp 67 | check deleteProp 68 | check lookupProp 69 | check adjustProp 70 | check fmapProp 71 | check sizeProp 72 | check unionProp 73 | -------------------------------------------------------------------------------- /haskell-model/SmallMap.hs: -------------------------------------------------------------------------------- 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 | module SmallMap ( 7 | SmallMap(), 8 | empty, 9 | singleton, 10 | lookup, 11 | elems, 12 | insert, 13 | delete, 14 | adjust, 15 | map, 16 | unionWith, 17 | fold 18 | ) where 19 | 20 | import Prelude hiding (lookup, map) 21 | import qualified Data.Array.IArray as A 22 | import Data.Maybe(catMaybes) 23 | import Data.Bits 24 | import GHC.Int 25 | 26 | import Debug.Trace 27 | 28 | type BitMap = Int 29 | type Mask = Int 30 | 31 | data SmallMap a = SmallMap 32 | { bitmap :: BitMap 33 | , array :: A.Array Int a 34 | } deriving (Eq, Show) 35 | 36 | 37 | popcount x = 38 | let x1 = x - ((x `shiftR` 1) .&. 0x55555555); 39 | x2 = (x1 .&. 0x33333333) + ((x1 `shiftR` 2) .&. 0x33333333); 40 | x3 = (x2 + (x2 `shiftR` 4)) .&. 0x0F0F0F0F; 41 | x4 = x3 + (x3 `shiftR` 8); 42 | x5 = x4 + (x4 `shiftR` 16); 43 | in x5 .&. 0x0000003F; 44 | 45 | mask :: Int -> Mask 46 | mask i = 1 `shiftL` i 47 | 48 | index :: BitMap -> Mask -> Int 49 | index b m = popcount (b .&. (m - 1)) 50 | 51 | set i x a = a A.// [(i, x)] 52 | 53 | insertAt i x a = A.listArray (0, u+1) na 54 | where (_, u) = A.bounds a 55 | na = take i ea ++ [x] ++ drop i ea 56 | ea = A.elems a 57 | 58 | deleteAt i a = A.listArray (0, u-1) na 59 | where (_, u) = A.bounds a 60 | na = take i ea ++ drop (i+1) ea 61 | ea = A.elems a 62 | 63 | empty :: SmallMap a 64 | empty = SmallMap 0 (A.listArray (0, -1) []) 65 | 66 | insert :: Int -> a -> SmallMap a -> SmallMap a 67 | insert i x (SmallMap b a) 68 | | (b .&. msk) == 0 = SmallMap (b .|. msk) (insertAt idx x a) 69 | | otherwise = SmallMap b (set idx x a) 70 | where msk = mask i 71 | idx = index b msk 72 | 73 | singleton :: Int -> a -> SmallMap a 74 | singleton i x = insert i x empty 75 | 76 | lookup :: Int -> SmallMap a -> Maybe a 77 | lookup i (SmallMap b a) 78 | | (b .&. msk) == 0 = Nothing 79 | | otherwise = Just (a A.! idx) 80 | where msk = mask i 81 | idx = index b msk 82 | 83 | delete :: Int -> SmallMap a -> SmallMap a 84 | delete i sm@(SmallMap b a) 85 | | (b .&. msk) == 0 = error "no value at i in delete" 86 | | otherwise = SmallMap (b .&. complement msk) (deleteAt idx a) 87 | where msk = mask i 88 | idx = index b msk 89 | 90 | unionWith :: (a -> a -> a) -> SmallMap a -> SmallMap a -> SmallMap a 91 | unionWith f (SmallMap b1 a1) (SmallMap b2 a2) = SmallMap b a 92 | where b = b1 .|. b2 93 | b' = b1 .&. b2 94 | a = go (A.array (0, popcount b - 1) []) 1 0 0 0 95 | go a m i j c | m > b = a 96 | | b' .&. m /= 0 = go (set c (f (a1 A.! i) (a2 A.! j)) a) 97 | (m `shiftL` 1) (i + 1) (j + 1) (c + 1) 98 | | b1 .&. m /= 0 = go (set c (a1 A.! i) a) 99 | (m `shiftL` 1) (i + 1) j (c + 1) 100 | | b2 .&. m /= 0 = go (set c (a2 A.! j) a) 101 | (m `shiftL` 1) i (j + 1) (c + 1) 102 | | otherwise = go a (m `shiftL` 1) i j c 103 | 104 | 105 | adjust :: (a -> a) -> Int -> SmallMap a -> SmallMap a 106 | adjust f i sm@(SmallMap b a) 107 | | (b .&. msk) == 0 = sm 108 | | otherwise = SmallMap b (set idx (f oldval) a) 109 | where msk = mask i 110 | idx = index b msk 111 | oldval = a A.! idx 112 | 113 | fold :: (a -> b -> b) -> b -> SmallMap a -> b 114 | fold f z = foldr f z . elems 115 | 116 | elems :: SmallMap a -> [a] 117 | elems = A.elems . array 118 | 119 | map :: (a -> b) -> SmallMap a -> SmallMap b 120 | map f (SmallMap b a) = SmallMap b (A.amap f a) 121 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | abstract class LinkedList implements Iterable { 9 | bool get isNil; 10 | bool get isCons; 11 | Nil get asNil; 12 | Cons get asCons; 13 | 14 | void foreach(f(E elem)); 15 | 16 | /// A strict (non-lazy) version of [:map:]. 17 | LinkedList strictMap(F f(E elem)); 18 | 19 | /// A strict (non-lazy) version of [:where:]. 20 | LinkedList strictWhere(bool f(E elem)); 21 | } 22 | 23 | class LinkedListBuilder { 24 | LinkedList _first = null; 25 | Cons _last = null; 26 | 27 | void add(E x) { 28 | Cons cons = new Cons(x, null); 29 | if (_first == null) { 30 | _first = cons; 31 | } else { 32 | _last.tail = cons; 33 | } 34 | _last = cons; 35 | } 36 | 37 | LinkedList build([tail = null]) { 38 | if (tail == null) tail = new Nil(); 39 | if (_first == null) { 40 | return tail; 41 | } else { 42 | _last.tail = tail; 43 | return _first; 44 | } 45 | } 46 | } 47 | 48 | abstract class _LinkedListBase extends IterableBase 49 | implements LinkedList { 50 | void foreach(f(E elem)) { 51 | LinkedList it = this; 52 | while (!it.isNil) { 53 | Cons cons = it.asCons; 54 | f(cons.elem); 55 | it = cons.tail; 56 | } 57 | } 58 | 59 | LinkedList strictMap(F f(E elem)) { 60 | LinkedListBuilder builder = new LinkedListBuilder(); 61 | LinkedList it = this; 62 | while (it.isCons) { 63 | Cons cons = it.asCons; 64 | E elem = cons.elem; 65 | builder.add(f(elem)); 66 | it = cons.tail; 67 | } 68 | return builder.build(); 69 | } 70 | 71 | LinkedList strictWhere(bool f(E elem)) { 72 | LinkedListBuilder builder = new LinkedListBuilder(); 73 | LinkedList it = this; 74 | while (it.isCons) { 75 | Cons cons = it.asCons; 76 | E elem = cons.elem; 77 | if (f(elem)) builder.add(elem); 78 | it = cons.tail; 79 | } 80 | return builder.build(); 81 | } 82 | } 83 | 84 | class _NilIterator implements Iterator { 85 | const _NilIterator(); 86 | E get current => null; 87 | bool moveNext() => false; 88 | } 89 | 90 | class Nil extends _LinkedListBase { 91 | bool get isNil => true; 92 | bool get isCons => false; 93 | Nil get asNil => this; 94 | Cons get asCons => null; 95 | 96 | toString() => "nil()"; 97 | 98 | int get length => 0; 99 | 100 | Iterator get iterator => const _NilIterator(); 101 | } 102 | 103 | class _ConsIterator implements Iterator { 104 | final LinkedList _head; 105 | LinkedList _current = null; 106 | 107 | _ConsIterator(this._head); 108 | 109 | E get current => _current.isCons ? _current.asCons.elem : null; 110 | 111 | bool moveNext() { 112 | if (_current == null) { 113 | _current = _head; 114 | return _current.isCons; 115 | } 116 | if (_current.isCons) { 117 | _current = _current.asCons.tail; 118 | return _current.isCons; 119 | } 120 | return false; 121 | } 122 | } 123 | 124 | class Cons extends _LinkedListBase { 125 | int _length = null; 126 | 127 | final E elem; 128 | LinkedList tail; 129 | 130 | Cons(this.elem, this.tail); 131 | 132 | bool get isNil => false; 133 | bool get isCons => true; 134 | Nil get asNil => null; 135 | Cons get asCons => this; 136 | 137 | toString() => "cons($elem, $tail)"; 138 | 139 | int get length { 140 | if (_length == null) { 141 | _length = tail.length + 1; 142 | } 143 | return _length; 144 | } 145 | 146 | Iterator get iterator => new _ConsIterator(this); 147 | } 148 | -------------------------------------------------------------------------------- /lib/src/map.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 | // Authors: 5 | // Paul Brauner (polux@google.com) 6 | // Rafael Brandão (rafa.bra@gmail.com) 7 | 8 | part of persistent; 9 | 10 | /** 11 | * An immutable map, binding keys of type [K] to values of type [V]. Null values 12 | * are supported but null keys are not. 13 | * 14 | * In all the examples below `{k1: v1, k2: v2, ...}` is a shorthand for 15 | * `PersistentMap.fromMap({k1: v1, k2: v2, ...})`. 16 | */ 17 | abstract class PersistentMap implements Iterable> { 18 | /** Creates an empty [PersistentMap] using its default implementation. */ 19 | const factory PersistentMap() = _EmptyMap; 20 | 21 | /** 22 | * Creates an immutable copy of [map] using the default implementation of 23 | * [PersistentMap]. 24 | */ 25 | factory PersistentMap.fromMap(Map map) { 26 | PersistentMap result = new _EmptyMap(); 27 | map.forEach((K key, V value) { 28 | result = result.insert(key, value); 29 | }); 30 | return result; 31 | } 32 | 33 | /** 34 | * Creates a [PersistentMap] from an [Iterable] of [Pair]s using the default 35 | * implementation of [PersistentMap]. 36 | */ 37 | factory PersistentMap.fromPairs(Iterable> pairs) { 38 | PersistentMap result = new _EmptyMap(); 39 | pairs.forEach((pair) { 40 | result = result.insert(pair.fst, pair.snd); 41 | }); 42 | return result; 43 | } 44 | 45 | /** 46 | * Returns a new map identical to `this` except that it binds [key] to 47 | * [value]. 48 | * 49 | * If [key] was bound to some `oldvalue` in `this`, it is nevertheless bound 50 | * to [value] in the new map. If [key] was bound to some `oldvalue` in `this` 51 | * and if [combine] is provided then [key] it is bound to 52 | * `combine(oldvalue, value)` in the new map. 53 | * 54 | * {'a': 1}.insert('b', 2) == {'a': 1, 'b', 2} 55 | * {'a': 1, 'b': 2}.insert('b', 3) == {'a': 3, 'b', 3} 56 | * {'a': 1, 'b': 2}.insert('b', 3, (x,y) => x - y) == {'a': 3, 'b', -1} 57 | */ 58 | PersistentMap insert(K key, V value, 59 | [V combine(V oldValue, V newValue)]); 60 | 61 | /** 62 | * Returns a new map identical to `this` except that it doesn't bind [key] 63 | * anymore. 64 | * 65 | * {'a': 1, 'b': 2}.delete('b') == {'a': 1} 66 | * {'a': 1}.delete('b') == {'a': 1} 67 | */ 68 | PersistentMap delete(K key); 69 | 70 | /** 71 | * Looks up the value possibly bound to [key] in `this`. Returns 72 | * `Option.some(value)` if it exists, `Option.none()` otherwise. 73 | * 74 | * {'a': 1}.lookup('b') == Option.none() 75 | * {'a': 1, 'b': 2}.lookup('b') == Option.some(2) 76 | */ 77 | Option lookup(K key); 78 | 79 | /** 80 | * Returns the value for the given [key] or [:null:] if [key] 81 | * is not in the map. 82 | */ 83 | V operator [](K key); 84 | 85 | /** 86 | * Evaluates `f(key, value)` for each (`key`, `value`) pair in `this`. 87 | */ 88 | void forEachKeyValue(f(K key, V value)); 89 | 90 | /** 91 | * Returns a new map identical to `this` except that the value it possibly 92 | * binds to [key] has been adjusted by [update]. 93 | * 94 | * {'a': 1, 'b': 2}.adjust('b', (x) => x + 1) == {'a', 1, 'b', 3} 95 | * {'a': 1}.adjust('b', (x) => x + 1) == {'a', 1} 96 | */ 97 | PersistentMap adjust(K key, V update(V value)); 98 | 99 | /** 100 | * Returns a new map identical to `this` where each value has been updated by 101 | * [f]. 102 | * 103 | * {'a': 1, 'b': 2}.mapValues((x) => x + 1) == {'a', 2, 'b', 3} 104 | * {}.mapValues((x) => x + 1) == {} 105 | */ 106 | PersistentMap mapValues(U f(V value)); 107 | 108 | /** 109 | * Returns a new map whose (key, value) pairs are the union of those of `this` 110 | * and [other]. 111 | * 112 | * The union is right-biased: if a key is present in both `this` and [other], 113 | * the value from [other] is retained. If [combine] is provided, the retained 114 | * value for a `key` present in both `this` and [other] is then 115 | * `combine(leftvalue, rightvalue)` where `leftvalue` is the value bound to 116 | * `key` in `this` and `rightvalue` is the one bound to `key` in [other]. 117 | * 118 | * {'a': 1}.union({'b': 2}) == {'a': 1, 'b': 2} 119 | * {'a': 1}.union({'a': 3, 'b': 2}) == {'a': 3, 'b': 2} 120 | * {'a': 1}.union({'a': 3, 'b': 2}, (x,y) => x + y) == {'a': 4, 'b': 2} 121 | * 122 | * Note that [union] is commutative if and only if [combine] is provided and 123 | * if it is commutative. 124 | */ 125 | PersistentMap union(PersistentMap other, 126 | [V combine(V left, V right)]); 127 | 128 | /** 129 | * Returns a new map whose (key, value) pairs are the intersection of those of 130 | * `this` and [other]. 131 | * 132 | * The intersection is right-biased: values from [other] are retained. If 133 | * [combine] is provided, the retained value for a `key` present in both 134 | * `this` and [other] is then `combine(leftvalue, rightvalue)` where 135 | * `leftvalue` is the value bound to `key` in `this` and `rightvalue` is the 136 | * one bound to `key` in [other]. 137 | * 138 | * {'a': 1}.intersection({'b': 2}) == {} 139 | * {'a': 1}.intersection({'a': 3, 'b': 2}) == {'a': 3} 140 | * {'a': 1}.intersection({'a': 3, 'b': 2}, (x,y) => x + y) == {'a': 4} 141 | * 142 | * Note that [intersection] is commutative if and only if [combine] is 143 | * provided and if it is commutative. 144 | */ 145 | PersistentMap intersection(PersistentMap other, 146 | [V combine(V left, V right)]); 147 | 148 | /// Returns a mutable copy of `this`. 149 | Map toMap(); 150 | 151 | /// The keys of `this`. 152 | Iterable get keys; 153 | 154 | /// The values of `this`. 155 | Iterable get values; 156 | 157 | /// Randomly picks an entry of `this`. 158 | Pair pickRandomEntry([Random random]); 159 | 160 | /// A strict (non-lazy) version of [map]. 161 | PersistentMap strictMap(Pair f(Pair pair)); 162 | 163 | /// A strict (non-lazy) version of [where]. 164 | PersistentMap strictWhere(bool f(Pair pair)); 165 | } 166 | 167 | /** 168 | * A base class for implementations of [PersistentMap]. 169 | */ 170 | abstract class PersistentMapBase extends IterableBase> 171 | implements PersistentMap { 172 | const PersistentMapBase(); 173 | 174 | Map toMap() { 175 | Map result = new Map(); 176 | this.forEachKeyValue((K k, V v) { 177 | result[k] = v; 178 | }); 179 | return result; 180 | } 181 | 182 | String toString() { 183 | StringBuffer buffer = new StringBuffer('{'); 184 | bool comma = false; 185 | this.forEachKeyValue((K k, V v) { 186 | if (comma) buffer.write(', '); 187 | buffer.write('$k: $v'); 188 | comma = true; 189 | }); 190 | buffer.write('}'); 191 | return buffer.toString(); 192 | } 193 | 194 | // Optimized version of Iterable's contains 195 | bool contains(Object entry) { 196 | if (entry is Pair) { 197 | final value = this.lookup(entry.fst); 198 | return value.isDefined && value.value == entry.snd; 199 | } else { 200 | return false; 201 | } 202 | } 203 | 204 | Iterable get keys => this.map((Pair pair) => pair.fst); 205 | 206 | Iterable get values => this.map((Pair pair) => pair.snd); 207 | 208 | Pair pickRandomEntry([Random random]) => 209 | elementAt((random != null ? random : _random).nextInt(this.length)); 210 | 211 | V operator [](K key) => this.lookup(key).asNullable; 212 | 213 | PersistentMap strictMap(Pair f(Pair pair)) => 214 | new PersistentMap.fromPairs(this.map(f)); 215 | 216 | PersistentMap strictWhere(bool f(Pair pair)) => 217 | new PersistentMap.fromPairs(this.where(f)); 218 | } 219 | -------------------------------------------------------------------------------- /lib/src/map_impl.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 | final _random = new Random(); 9 | 10 | /** 11 | * Superclass for _EmptyMap, _Leaf and _SubMap. 12 | */ 13 | abstract class _APersistentMap extends PersistentMapBase { 14 | final int length; 15 | 16 | const _APersistentMap(this.length, this.isEmpty, this._isLeaf); 17 | 18 | final bool isEmpty; 19 | final bool _isLeaf; 20 | 21 | Option _lookup(K key, int hash, int depth); 22 | PersistentMap _insertWith(LinkedList> keyValues, int size, 23 | V combine(V x, V y), int hash, int depth); 24 | PersistentMap _intersectWith(LinkedList> keyValues, int size, 25 | V combine(V x, V y), int hash, int depth); 26 | PersistentMap _delete(K key, int hash, int depth); 27 | PersistentMap _adjust(K key, V update(V value), int hash, int depth); 28 | 29 | _APersistentMap _unionWith( 30 | _APersistentMap m, V combine(V x, V y), int depth); 31 | _APersistentMap _unionWithLeaf( 32 | _Leaf m, V combine(V x, V y), int depth); 33 | _APersistentMap _unionWithSubMap( 34 | _SubMap m, V combine(V x, V y), int depth); 35 | 36 | _APersistentMap _intersectionWith( 37 | _APersistentMap m, V combine(V x, V y), int depth); 38 | _APersistentMap _intersectionWithLeaf( 39 | _Leaf m, V combine(V x, V y), int depth); 40 | _APersistentMap _intersectionWithSubMap( 41 | _SubMap m, V combine(V x, V y), int depth); 42 | 43 | Pair _elementAt(int index); 44 | 45 | LinkedList> _onePair(K key, V value) => 46 | new Cons>(new Pair(key, value), new Nil>()); 47 | 48 | Option lookup(K key) => _lookup(key, key.hashCode & 0x3fffffff, 0); 49 | 50 | PersistentMap insert(K key, V value, [V combine(V x, V y)]) => 51 | _insertWith( 52 | _onePair(key, value), 53 | 1, 54 | (combine != null) ? combine : (V x, V y) => y, 55 | key.hashCode & 0x3fffffff, 56 | 0); 57 | 58 | PersistentMap delete(K key) => 59 | _delete(key, key.hashCode & 0x3fffffff, 0); 60 | 61 | PersistentMap adjust(K key, V update(V value)) => 62 | _adjust(key, update, key.hashCode & 0x3fffffff, 0); 63 | 64 | PersistentMap union(PersistentMap other, [V combine(V x, V y)]) => 65 | this._unionWith(other, (combine != null) ? combine : (V x, V y) => y, 0); 66 | 67 | PersistentMap intersection(PersistentMap other, 68 | [V combine(V left, V right)]) => 69 | this._intersectionWith( 70 | other, (combine != null) ? combine : (V x, V y) => y, 0); 71 | 72 | Pair elementAt(int index) { 73 | if (index < 0 || index >= length) throw new RangeError.value(index); 74 | return _elementAt(index); 75 | } 76 | 77 | // toString() => toDebugString(); 78 | } 79 | 80 | class _EmptyMapIterator implements Iterator> { 81 | const _EmptyMapIterator(); 82 | Pair get current => null; 83 | bool moveNext() => false; 84 | } 85 | 86 | class _EmptyMap extends _APersistentMap { 87 | const _EmptyMap() : super(0, true, false); 88 | 89 | Option _lookup(K key, int hash, int depth) => new Option.none(); 90 | 91 | PersistentMap _insertWith(LinkedList> keyValues, int size, 92 | V combine(V x, V y), int hash, int depth) { 93 | assert(size == keyValues.length); 94 | return new _Leaf(hash, keyValues, size); 95 | } 96 | 97 | PersistentMap _intersectWith(LinkedList> keyValues, int size, 98 | V combine(V x, V y), int hash, int depth) { 99 | assert(size == keyValues.length); 100 | return this; 101 | } 102 | 103 | PersistentMap _delete(K key, int hash, int depth) => this; 104 | 105 | PersistentMap _adjust(K key, V update(V value), int hash, int depth) => this; 106 | 107 | _APersistentMap _unionWith( 108 | PersistentMap m, V combine(V x, V y), int depth) => 109 | m; 110 | 111 | _APersistentMap _unionWithLeaf( 112 | _Leaf m, V combine(V x, V y), int depth) => 113 | m; 114 | 115 | _APersistentMap _unionWithSubMap( 116 | _SubMap m, V combine(V x, V y), int depth) => 117 | m; 118 | 119 | _APersistentMap _intersectionWith( 120 | _APersistentMap m, V combine(V x, V y), int depth) => 121 | this; 122 | 123 | _APersistentMap _intersectionWithLeaf( 124 | _Leaf m, V combine(V x, V y), int depth) => 125 | this; 126 | 127 | _APersistentMap _intersectionWithSubMap( 128 | _SubMap m, V combine(V x, V y), int depth) => 129 | this; 130 | 131 | PersistentMap mapValues(U f(V value)) => new _EmptyMap(); 132 | 133 | void forEachKeyValue(f(K key, V value)) {} 134 | 135 | bool operator ==(Object other) => other is _EmptyMap; 136 | 137 | int get hashCode => 127; 138 | 139 | Iterator> get iterator => const _EmptyMapIterator(); 140 | 141 | Pair _elementAt(int index) { 142 | throw new RangeError.value(index); 143 | } 144 | 145 | Pair get last { 146 | throw new StateError("Empty map has no entries"); 147 | } 148 | 149 | toDebugString() => "_EmptyMap()"; 150 | } 151 | 152 | class _Leaf extends _APersistentMap { 153 | /// hashCode of the keys stored in this leaf, used to speed-up failed lookups 154 | int _hash; 155 | 156 | /// Cached hashCode for this leaf 157 | int _cachedHashCode; 158 | 159 | LinkedList> _pairs; 160 | 161 | _Leaf(this._hash, pairs, int size) : super(size, false, true) { 162 | this._pairs = pairs; 163 | assert(size == pairs.length); 164 | } 165 | 166 | PersistentMap _insertWith(LinkedList> keyValues, int size, 167 | V combine(V x, V y), int hash, int depth) { 168 | assert(size == keyValues.length); 169 | // newsize is incremented as a side effect of insertPair 170 | int newsize = length; 171 | 172 | LinkedList> insertPair( 173 | Pair toInsert, LinkedList> pairs) { 174 | LinkedListBuilder> builder = 175 | new LinkedListBuilder>(); 176 | LinkedList> it = pairs; 177 | while (it.isCons) { 178 | Cons> cons = it.asCons; 179 | Pair elem = cons.elem; 180 | if (elem.fst == toInsert.fst) { 181 | builder.add( 182 | new Pair(toInsert.fst, combine(elem.snd, toInsert.snd))); 183 | return builder.build(cons.tail); 184 | } 185 | builder.add(elem); 186 | it = cons.tail; 187 | } 188 | builder.add(toInsert); 189 | newsize++; 190 | return builder.build(); 191 | } 192 | 193 | LinkedList> insertPairs( 194 | LinkedList> toInsert, LinkedList> pairs) { 195 | LinkedList> res = pairs; 196 | LinkedList> it = toInsert; 197 | while (it.isCons) { 198 | Cons> cons = it.asCons; 199 | Pair elem = cons.elem; 200 | res = insertPair(elem, res); 201 | it = cons.tail; 202 | } 203 | assert(newsize == res.length); 204 | return res; 205 | } 206 | 207 | if (depth > 5) { 208 | assert(_hash == hash); 209 | final LinkedList> newPairs = insertPairs(keyValues, _pairs); 210 | return new _Leaf(hash, newPairs, newsize); 211 | } else { 212 | if (hash == _hash) { 213 | final LinkedList> newPairs = insertPairs(keyValues, _pairs); 214 | return new _Leaf(hash, newPairs, newsize); 215 | } else { 216 | int branch = (_hash >> (depth * 5)) & 0x1f; 217 | List<_APersistentMap> array = <_APersistentMap>[this]; 218 | return new _SubMap(1 << branch, array, length) 219 | ._insertWith(keyValues, size, combine, hash, depth); 220 | } 221 | } 222 | } 223 | 224 | PersistentMap _intersectWith(LinkedList> keyValues, int size, 225 | V combine(V x, V y), int hash, int depth) { 226 | assert(size == keyValues.length); 227 | // TODO(polux): possibly faster implementation 228 | Map map = toMap(); 229 | LinkedListBuilder> builder = new LinkedListBuilder>(); 230 | int newsize = 0; 231 | keyValues.foreach((Pair pair) { 232 | if (map.containsKey(pair.fst)) { 233 | builder.add(new Pair(pair.fst, combine(map[pair.fst], pair.snd))); 234 | newsize++; 235 | } 236 | }); 237 | return new _Leaf(_hash, builder.build(), newsize); 238 | } 239 | 240 | PersistentMap _delete(K key, int hash, int depth) { 241 | if (hash != _hash) return this; 242 | bool found = false; 243 | LinkedList> newPairs = _pairs.strictWhere((p) { 244 | if (p.fst == key) { 245 | found = true; 246 | return false; 247 | } 248 | return true; 249 | }); 250 | return newPairs.isNil 251 | ? new _EmptyMap() 252 | : new _Leaf(_hash, newPairs, found ? length - 1 : length); 253 | } 254 | 255 | PersistentMap _adjust(K key, V update(V value), int hash, int depth) { 256 | LinkedList> adjustPairs() { 257 | LinkedListBuilder> builder = 258 | new LinkedListBuilder>(); 259 | LinkedList> it = _pairs; 260 | while (it.isCons) { 261 | Cons> cons = it.asCons; 262 | Pair elem = cons.elem; 263 | if (elem.fst == key) { 264 | builder.add(new Pair(key, update(elem.snd))); 265 | return builder.build(cons.tail); 266 | } 267 | builder.add(elem); 268 | it = cons.tail; 269 | } 270 | return builder.build(); 271 | } 272 | 273 | return (hash != _hash) 274 | ? this 275 | : new _Leaf(_hash, adjustPairs(), length); 276 | } 277 | 278 | _APersistentMap _unionWith( 279 | _APersistentMap m, V combine(V x, V y), int depth) => 280 | m._unionWithLeaf(this, combine, depth); 281 | 282 | _APersistentMap _unionWithLeaf( 283 | _Leaf m, V combine(V x, V y), int depth) => 284 | m._insertWith(_pairs, length, combine, _hash, depth); 285 | 286 | _APersistentMap _unionWithSubMap( 287 | _SubMap m, V combine(V x, V y), int depth) => 288 | m._insertWith(_pairs, length, combine, _hash, depth); 289 | 290 | _APersistentMap _intersectionWith( 291 | _APersistentMap m, V combine(V x, V y), int depth) => 292 | m._intersectionWithLeaf(this, combine, depth); 293 | 294 | _APersistentMap _intersectionWithLeaf( 295 | _Leaf m, V combine(V x, V y), int depth) => 296 | m._intersectWith(_pairs, length, combine, _hash, depth); 297 | 298 | _APersistentMap _intersectionWithSubMap( 299 | _SubMap m, V combine(V x, V y), int depth) => 300 | m._intersectWith(_pairs, length, combine, _hash, depth); 301 | 302 | Option _lookup(K key, int hash, int depth) { 303 | if (hash != _hash) return new Option.none(); 304 | LinkedList> it = _pairs; 305 | while (it.isCons) { 306 | Cons> cons = it.asCons; 307 | Pair elem = cons.elem; 308 | if (elem.fst == key) return new Option.some(elem.snd); 309 | it = cons.tail; 310 | } 311 | return new Option.none(); 312 | } 313 | 314 | PersistentMap mapValues(U f(V value)) => new _Leaf( 315 | _hash, _pairs.strictMap((p) => new Pair(p.fst, f(p.snd))), length); 316 | 317 | void forEachKeyValue(f(K key, V value)) { 318 | _pairs.foreach((Pair pair) => f(pair.fst, pair.snd)); 319 | } 320 | 321 | bool operator ==(Object other) { 322 | if (identical(this, other)) return true; 323 | if (other is! _Leaf) return false; 324 | _Leaf otherLeaf = other; 325 | if (_hash != otherLeaf._hash) return false; 326 | if (length != otherLeaf.length) return false; 327 | Map thisAsMap = toMap(); 328 | int counter = 0; 329 | LinkedList> it = otherLeaf._pairs; 330 | while (it.isCons) { 331 | Cons> cons = it.asCons; 332 | Pair elem = cons.elem; 333 | if ((elem.snd == null) && !thisAsMap.containsKey(elem.fst)) return false; 334 | if (thisAsMap[elem.fst] != elem.snd) return false; 335 | counter++; 336 | it = cons.tail; 337 | } 338 | return thisAsMap.length == counter; 339 | } 340 | 341 | int get hashCode { 342 | if (_cachedHashCode == null) { 343 | _cachedHashCode = 0; 344 | LinkedList> it = _pairs; 345 | while (it.isCons) { 346 | Cons> cons = it.asCons; 347 | _cachedHashCode ^= cons.elem.hashCode; 348 | it = cons.tail; 349 | } 350 | } 351 | return _cachedHashCode; 352 | } 353 | 354 | Iterator> get iterator => _pairs.iterator; 355 | 356 | Pair _elementAt(int index) { 357 | var tail = _pairs; 358 | for (int i = 0; i < index; i++) { 359 | tail = tail.asCons.tail; 360 | } 361 | return tail.asCons.elem; 362 | } 363 | 364 | Pair get last { 365 | Cons pairs = _pairs.asCons; 366 | while (!pairs.tail.isNil) { 367 | pairs = pairs.tail; 368 | } 369 | return pairs.elem; 370 | } 371 | 372 | toDebugString() => "_Leaf($_hash, $_pairs)"; 373 | } 374 | 375 | class _SubMapIterator implements Iterator> { 376 | List<_APersistentMap> _array; 377 | int _index = 0; 378 | // invariant: _currentIterator != null => _currentIterator.current != null 379 | Iterator> _currentIterator = null; 380 | 381 | _SubMapIterator(this._array); 382 | 383 | Pair get current => 384 | (_currentIterator != null) ? _currentIterator.current : null; 385 | 386 | bool moveNext() { 387 | while (_index < _array.length) { 388 | if (_currentIterator == null) { 389 | _currentIterator = _array[_index].iterator; 390 | } 391 | if (_currentIterator.moveNext()) { 392 | return true; 393 | } else { 394 | _currentIterator = null; 395 | _index++; 396 | } 397 | } 398 | return false; 399 | } 400 | } 401 | 402 | class _SubMap extends _APersistentMap { 403 | int _cachedHashCode; 404 | int _bitmap; 405 | List<_APersistentMap> _array; 406 | 407 | _SubMap(this._bitmap, this._array, int size) : super(size, false, false); 408 | 409 | static _popcount(int n) { 410 | n = n - ((n >> 1) & 0x55555555); 411 | n = (n & 0x33333333) + ((n >> 2) & 0x33333333); 412 | n = (n + (n >> 4)) & 0x0F0F0F0F; 413 | n = n + (n >> 8); 414 | n = n + (n >> 16); 415 | return n & 0x0000003F; 416 | } 417 | 418 | Option _lookup(K key, int hash, int depth) { 419 | int branch = (hash >> (depth * 5)) & 0x1f; 420 | int mask = 1 << branch; 421 | if ((_bitmap & mask) != 0) { 422 | int index = _popcount(_bitmap & (mask - 1)); 423 | _APersistentMap map = _array[index]; 424 | return map._lookup(key, hash, depth + 1); 425 | } else { 426 | return new Option.none(); 427 | } 428 | } 429 | 430 | PersistentMap _insertWith(LinkedList> keyValues, int size, 431 | V combine(V x, V y), int hash, int depth) { 432 | assert(size == keyValues.length); 433 | 434 | int branch = (hash >> (depth * 5)) & 0x1f; 435 | int mask = 1 << branch; 436 | int index = _popcount(_bitmap & (mask - 1)); 437 | 438 | if ((_bitmap & mask) != 0) { 439 | List<_APersistentMap> newarray = 440 | new List<_APersistentMap>.from(_array, growable: false); 441 | _APersistentMap m = _array[index]; 442 | _APersistentMap newM = 443 | m._insertWith(keyValues, size, combine, hash, depth + 1); 444 | newarray[index] = newM; 445 | int delta = newM.length - m.length; 446 | return new _SubMap(_bitmap, newarray, length + delta); 447 | } else { 448 | int newlength = _array.length + 1; 449 | List<_APersistentMap> newarray = 450 | new List<_APersistentMap>(newlength); 451 | // TODO: find out if there's a "copy array" native function somewhere 452 | for (int i = 0; i < index; i++) { 453 | newarray[i] = _array[i]; 454 | } 455 | for (int i = index; i < newlength - 1; i++) { 456 | newarray[i + 1] = _array[i]; 457 | } 458 | newarray[index] = new _Leaf(hash, keyValues, size); 459 | return new _SubMap(_bitmap | mask, newarray, length + size); 460 | } 461 | } 462 | 463 | PersistentMap _intersectWith(LinkedList> keyValues, int size, 464 | V combine(V x, V y), int hash, int depth) { 465 | assert(size == keyValues.length); 466 | 467 | int branch = (hash >> (depth * 5)) & 0x1f; 468 | int mask = 1 << branch; 469 | 470 | if ((_bitmap & mask) != 0) { 471 | int index = _popcount(_bitmap & (mask - 1)); 472 | _APersistentMap m = _array[index]; 473 | return m._intersectWith(keyValues, size, combine, hash, depth + 1); 474 | } else { 475 | return new _EmptyMap(); 476 | } 477 | } 478 | 479 | PersistentMap _delete(K key, int hash, int depth) { 480 | int branch = (hash >> (depth * 5)) & 0x1f; 481 | int mask = 1 << branch; 482 | 483 | if ((_bitmap & mask) != 0) { 484 | int index = _popcount(_bitmap & (mask - 1)); 485 | _APersistentMap m = _array[index]; 486 | _APersistentMap newm = m._delete(key, hash, depth + 1); 487 | int delta = newm.length - m.length; 488 | if (identical(m, newm)) { 489 | return this; 490 | } 491 | if (newm.isEmpty) { 492 | if (_array.length > 2) { 493 | int newsize = _array.length - 1; 494 | List<_APersistentMap> newarray = 495 | new List<_APersistentMap>(newsize); 496 | for (int i = 0; i < index; i++) { 497 | newarray[i] = _array[i]; 498 | } 499 | for (int i = index; i < newsize; i++) { 500 | newarray[i] = _array[i + 1]; 501 | } 502 | assert(newarray.length >= 2); 503 | return new _SubMap(_bitmap ^ mask, newarray, length + delta); 504 | } else { 505 | assert(_array.length == 2); 506 | assert(index == 0 || index == 1); 507 | _APersistentMap onlyValueLeft = _array[1 - index]; 508 | return onlyValueLeft._isLeaf 509 | ? onlyValueLeft 510 | : new _SubMap(_bitmap ^ mask, 511 | <_APersistentMap>[onlyValueLeft], length + delta); 512 | } 513 | } else if (newm._isLeaf) { 514 | if (_array.length == 1) { 515 | return newm; 516 | } else { 517 | List<_APersistentMap> newarray = 518 | new List<_APersistentMap>.from(_array, growable: false); 519 | newarray[index] = newm; 520 | return new _SubMap(_bitmap, newarray, length + delta); 521 | } 522 | } else { 523 | List<_APersistentMap> newarray = 524 | new List<_APersistentMap>.from(_array, growable: false); 525 | newarray[index] = newm; 526 | return new _SubMap(_bitmap, newarray, length + delta); 527 | } 528 | } else { 529 | return this; 530 | } 531 | } 532 | 533 | PersistentMap _adjust(K key, V update(V value), int hash, int depth) { 534 | int branch = (hash >> (depth * 5)) & 0x1f; 535 | int mask = 1 << branch; 536 | if ((_bitmap & mask) != 0) { 537 | int index = _popcount(_bitmap & (mask - 1)); 538 | _APersistentMap m = _array[index]; 539 | _APersistentMap newm = m._adjust(key, update, hash, depth + 1); 540 | if (identical(newm, m)) { 541 | return this; 542 | } 543 | List<_APersistentMap> newarray = 544 | new List<_APersistentMap>.from(_array, growable: false); 545 | newarray[index] = newm; 546 | return new _SubMap(_bitmap, newarray, length); 547 | } else { 548 | return this; 549 | } 550 | } 551 | 552 | _APersistentMap _unionWith( 553 | _APersistentMap m, V combine(V x, V y), int depth) => 554 | m._unionWithSubMap(this, combine, depth); 555 | 556 | _APersistentMap _unionWithLeaf( 557 | _Leaf m, V combine(V x, V y), int depth) => 558 | this._insertWith( 559 | m._pairs, m.length, (V v1, V v2) => combine(v2, v1), m._hash, depth); 560 | 561 | _APersistentMap _unionWithSubMap( 562 | _SubMap m, V combine(V x, V y), int depth) { 563 | int ormap = _bitmap | m._bitmap; 564 | int andmap = _bitmap & m._bitmap; 565 | List<_APersistentMap> newarray = 566 | new List<_APersistentMap>(_popcount(ormap)); 567 | int mask = 1, i = 0, i1 = 0, i2 = 0; 568 | int newSize = 0; 569 | while (mask <= ormap) { 570 | if ((andmap & mask) != 0) { 571 | _array[i1]; 572 | m._array[i2]; 573 | _APersistentMap newMap = 574 | m._array[i2]._unionWith(_array[i1], combine, depth + 1); 575 | newarray[i] = newMap; 576 | newSize += newMap.length; 577 | i1++; 578 | i2++; 579 | i++; 580 | } else if ((_bitmap & mask) != 0) { 581 | _APersistentMap newMap = _array[i1]; 582 | newarray[i] = newMap; 583 | newSize += newMap.length; 584 | i1++; 585 | i++; 586 | } else if ((m._bitmap & mask) != 0) { 587 | _APersistentMap newMap = m._array[i2]; 588 | newarray[i] = newMap; 589 | newSize += newMap.length; 590 | i2++; 591 | i++; 592 | } 593 | mask <<= 1; 594 | } 595 | return new _SubMap(ormap, newarray, newSize); 596 | } 597 | 598 | _APersistentMap _intersectionWith( 599 | _APersistentMap m, V combine(V x, V y), int depth) => 600 | m._intersectionWithSubMap(this, combine, depth); 601 | 602 | _APersistentMap _intersectionWithLeaf( 603 | _Leaf m, V combine(V x, V y), int depth) => 604 | _intersectWith( 605 | m._pairs, m.length, (V v1, V v2) => combine(v2, v1), m._hash, depth); 606 | 607 | _APersistentMap _intersectionWithSubMap( 608 | _SubMap m, V combine(V x, V y), int depth) { 609 | int andmap = _bitmap & m._bitmap; 610 | List<_APersistentMap> newarray = new List<_APersistentMap>(); 611 | int mask = 1, i1 = 0, i2 = 0; 612 | int newSize = 0; 613 | int newMask = 0; 614 | while (mask <= _bitmap) { 615 | if ((andmap & mask) != 0) { 616 | _array[i1]; 617 | m._array[i2]; 618 | _APersistentMap newMap = 619 | m._array[i2]._intersectionWith(_array[i1], combine, depth + 1); 620 | newarray.add(newMap); 621 | newSize += newMap.length; 622 | newMask |= mask; 623 | i1++; 624 | i2++; 625 | } else if ((_bitmap & mask) != 0) { 626 | i1++; 627 | } else if ((m._bitmap & mask) != 0) { 628 | i2++; 629 | } 630 | mask <<= 1; 631 | } 632 | if (newarray.length > 1) { 633 | return new _SubMap(newMask, newarray, newSize); 634 | } else if (newarray.length == 1) { 635 | _APersistentMap onlyValueLeft = newarray[0]; 636 | return onlyValueLeft._isLeaf 637 | ? onlyValueLeft 638 | : new _SubMap(newMask, newarray, newSize); 639 | } else { 640 | return new _EmptyMap(); 641 | } 642 | } 643 | 644 | PersistentMap mapValues(U f(V value)) { 645 | List<_APersistentMap> newarray = 646 | new List<_APersistentMap>.from(_array, growable: false); 647 | for (int i = 0; i < _array.length; i++) { 648 | _APersistentMap mi = _array[i]; 649 | newarray[i] = mi.mapValues(f); 650 | } 651 | return new _SubMap(_bitmap, newarray, length); 652 | } 653 | 654 | forEachKeyValue(f(K key, V value)) { 655 | _array.forEach((mi) => mi.forEachKeyValue(f)); 656 | } 657 | 658 | bool operator ==(Object other) { 659 | if (identical(this, other)) return true; 660 | if (other is! _SubMap) return false; 661 | _SubMap otherSubMap = other; 662 | if (_bitmap != otherSubMap._bitmap) return false; 663 | if (length != otherSubMap.length) return false; 664 | assert(_array.length == otherSubMap._array.length); 665 | for (int i = 0; i < _array.length; i++) { 666 | _APersistentMap mi = _array[i]; 667 | _APersistentMap omi = otherSubMap._array[i]; 668 | if (mi != omi) { 669 | return false; 670 | } 671 | } 672 | return true; 673 | } 674 | 675 | int get hashCode { 676 | if (_cachedHashCode == null) { 677 | _cachedHashCode = _bitmap; 678 | for (final subMap in _array) { 679 | _cachedHashCode = _cachedHashCode * 31 + subMap.hashCode; 680 | } 681 | } 682 | return _cachedHashCode; 683 | } 684 | 685 | Iterator> get iterator => new _SubMapIterator(_array); 686 | 687 | Pair get last => _array.last.last; 688 | 689 | Pair _elementAt(int index) { 690 | int newIndex = index; 691 | for (final subMap in _array) { 692 | int subLength = subMap.length; 693 | if (newIndex < subLength) { 694 | return subMap._elementAt(newIndex); 695 | } 696 | newIndex -= subLength; 697 | } 698 | throw new StateError("never happens"); 699 | } 700 | 701 | toDebugString() => "_SubMap($_array)"; 702 | } 703 | -------------------------------------------------------------------------------- /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