From 2eb0a5d0b5191b480deda0ad355cac60a8055612 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Thu, 22 Mar 2018 17:24:43 -0700 Subject: [PATCH 1/7] Update CHANGELOG/pubspec to include 0.28.1 (#427) Merges 27efc6c83c5a03180067d66a0068991876719653 from branch dart_1. --- CHANGELOG.md | 5 +++++ README.md | 2 +- pubspec.yaml | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b12dea6..74623dc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ * New: DelegatingList now includes real implementations of `operator +`, `indexWhere`, and `lastIndexWhere`. +#### 0.28.0+1 - 2018-03-22 + + * Remove use of `Maps.mapToString` in `LruMap`. + * Add @visibleForTesting annotation in `AvlTreeSet`. + #### 0.28.0 - 2018-01-19 * BREAKING CHANGE: The signature of `MultiMap`'s `update` stub has changed diff --git a/README.md b/README.md index 1b9fb262..0799a5ec 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Add Quiver to your project's pubspec.yaml file and run `pub get`. We recommend the following version constraint: dependencies: - quiver: '>=0.28.0 <0.29.0' + quiver: '>=0.28.1 <0.29.0' # Main Libraries diff --git a/pubspec.yaml b/pubspec.yaml index 4346d76f..01afc809 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: quiver -version: 0.28.0 +version: 0.28.1 authors: - Justin Fagnani - Yegor Jbanov From f43ecad7449f0d884ef13ee8cf5bbaf342c12b19 Mon Sep 17 00:00:00 2001 From: Alexandre Ardhuin Date: Fri, 23 Mar 2018 16:39:48 +0100 Subject: [PATCH 2/7] Update CHANGELOG.md (#428) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74623dc9..7eb9ff54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ #### 0.28.0+1 - 2018-03-22 * Remove use of `Maps.mapToString` in `LruMap`. - * Add @visibleForTesting annotation in `AvlTreeSet`. +   * Add `@visibleForTesting` annotation in `AvlTreeSet`. #### 0.28.0 - 2018-01-19 From 84968bd6e930058f323b298a4d5fe79ed7755f44 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Fri, 23 Mar 2018 11:11:42 -0700 Subject: [PATCH 3/7] Ensure that LruMap linkage is preserved (#429) Fixes two scenarios: 1. When a non-{head,tail} entry is removed, ensures that the next entry's 'previous' link is correctly linked to the removed entry's previous entry. 2. When the head entry is removed, ensure's that the new head's 'previous' link is nulled. --- lib/src/collection/lru_map.dart | 4 +++- test/collection/lru_map_test.dart | 9 +++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/src/collection/lru_map.dart b/lib/src/collection/lru_map.dart index d0e7813d..dd6aad5d 100644 --- a/lib/src/collection/lru_map.dart +++ b/lib/src/collection/lru_map.dart @@ -235,11 +235,13 @@ class LinkedLruHashMap implements LruMap { _head = _tail = null; } else if (entry == _head) { _head = _head.next; + _head?.previous = null; } else if (entry == _tail) { - _tail.previous.next = null; _tail = _tail.previous; + _tail?.next = null; } else { entry.previous.next = entry.next; + entry.next.previous = entry.previous; } return entry.value; } diff --git a/test/collection/lru_map_test.dart b/test/collection/lru_map_test.dart index 25150dcb..dd9b93e4 100644 --- a/test/collection/lru_map_test.dart +++ b/test/collection/lru_map_test.dart @@ -207,6 +207,15 @@ void main() { lruMap.remove('B'); expect(lruMap.keys.toList(), ['C', 'A']); }); + + test('linkage correctly preserved on remove', () { + lruMap.remove('B'); + lruMap['A']; + + final keys = []; + lruMap.forEach((String k, String v) => keys.add(k)); + expect(keys, ['A', 'C']); + }); }); test( From 65d56d67e002e28bdba3db7fc0f08c114b735ed4 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Fri, 23 Mar 2018 11:23:09 -0700 Subject: [PATCH 4/7] Update changelog for LruMap fix (#430) Adds a note about the fix committed in 84968bd. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7eb9ff54..d23e7022 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ `followedBy`, and accepts the `orElse` parameter on `singleWhere`. * New: DelegatingList now includes real implementations of `operator +`, `indexWhere`, and `lastIndexWhere`. + * Fix: Eliminate a bug where `LruMap` linkage is incorrectly preserved when + items are removed. #### 0.28.0+1 - 2018-03-22 From 84e8e43fb32aebd05145756d8a748def3ab84052 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Fri, 23 Mar 2018 11:26:37 -0700 Subject: [PATCH 5/7] Add .dart_tool to gitignore (#431) Pub and other dart tools now write cache files to the .dart_tool subdirectory. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 9d0511bc..497436f4 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ packages pubspec.lock build/ +.dart_tool/ .pub .packages From 520ee76ef0710f497cb838346117091e8cfbb3b1 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Fri, 23 Mar 2018 14:46:12 -0700 Subject: [PATCH 6/7] Add new Dart 2 Map, Iterable, and List methods to multimap stuff (#425) --- CHANGELOG.md | 10 +++++++ lib/src/collection/multimap.dart | 45 ++++++++++-------------------- test/collection/multimap_test.dart | 36 ++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d23e7022..3d799a2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,16 @@ `followedBy`, and accepts the `orElse` parameter on `singleWhere`. * New: DelegatingList now includes real implementations of `operator +`, `indexWhere`, and `lastIndexWhere`. + * New: The map returned by `Multimap.asMap()` now includes real + implementations of `get entries` and `removeWhere`. This class also has + "real" implementations of `addEntries`, `map`, `update`, and `updateAll`, + which just throw an `UnsupportedError`, as inserts and updates are not + allowed on map views. + * New: The list keys of `ListMultimap` now include real implementations of + `operator +`, `indexWhere`, and `lastIndexWhere`. + * New: The iterable keys of `ListMultimap` and `SetMultimap` now include a + real implementation of `followedBy`, and accept the `orElse` parameter on + `singleWhere`. * Fix: Eliminate a bug where `LruMap` linkage is incorrectly preserved when items are removed. diff --git a/lib/src/collection/multimap.dart b/lib/src/collection/multimap.dart index 80c59930..c04f2a3a 100644 --- a/lib/src/collection/multimap.dart +++ b/lib/src/collection/multimap.dart @@ -309,51 +309,36 @@ class _WrappedMap> implements Map { } @override - // TODO: Dart 2.0 requires this method to be implemented. - // ignore: override_on_non_overriding_getter - Iterable get entries { - // Change Iterable to Iterable> when - // the MapEntry class has been added. - throw new UnimplementedError("entries"); - } + Iterable> get entries => _multimap._map.entries; @override - // TODO: Dart 2.0 requires this method to be implemented. - // ignore: override_on_non_overriding_method - void addEntries(Iterable entries) { - // Change Iterable to Iterable> when - // the MapEntry class has been added. - throw new UnimplementedError("addEntries"); + void addEntries(Iterable> entries) { + throw new UnsupportedError("Insert unsupported on map view"); } @override - // TODO: Dart 2.0 requires this method to be implemented. - // ignore: override_on_non_overriding_method - Map map(Object transform(K key, C value)) { - // Change Object to MapEntry when - // the MapEntry class has been added. - throw new UnimplementedError("map"); - } + Map map(MapEntry transform(K key, C value)) => + _multimap._map.map(transform); @override - // TODO: Dart 2.0 requires this method to be implemented. - // ignore: override_on_non_overriding_method C update(K key, C update(C value), {C ifAbsent()}) { - throw new UnimplementedError("update"); + throw new UnsupportedError("Update unsupported on map view"); } @override - // TODO: Dart 2.0 requires this method to be implemented. - // ignore: override_on_non_overriding_method void updateAll(C update(K key, C value)) { - throw new UnimplementedError("updateAll"); + throw new UnsupportedError("Update unsupported on map view"); } @override - // TODO: Dart 2.0 requires this method to be implemented. - // ignore: override_on_non_overriding_method void removeWhere(bool test(K key, C value)) { - throw new UnimplementedError("removeWhere"); + var keysToRemove = []; + for (var key in keys) { + if (test(key, this[key])) keysToRemove.add(key); + } + for (var key in keysToRemove) { + _multimap.removeAll(key); + } } } @@ -606,8 +591,6 @@ class _WrappedList extends _WrappedIterable> } @override - // TODO: Dart 2.0 requires this method to be implemented. - // ignore: override_on_non_overriding_setter void set first(V value) { if (this.isEmpty) throw new RangeError.index(0, this); this[0] = value; diff --git a/test/collection/multimap_test.dart b/test/collection/multimap_test.dart index e0288bcc..7f139a8e 100644 --- a/test/collection/multimap_test.dart +++ b/test/collection/multimap_test.dart @@ -76,6 +76,21 @@ void main() { expect(() => map['k1'] = ['1', '2', '3'], throwsUnsupportedError); }); + test('addEntries should throw UnsupportedError', () { + expect(() => map.addEntries(>>[]), + throwsUnsupportedError); + }); + + test('update should throw UnsupportedError', () { + expect(() => map.update('k1', (_) => ['1', '2', '3']), + throwsUnsupportedError); + }); + + test('updateAll should throw UnsupportedError', () { + expect(() => map.updateAll((_, __) => ['1', '2', '3']), + throwsUnsupportedError); + }); + test('containsKey() should return false for missing key', () { expect(map.containsKey('k3'), isFalse); }); @@ -928,6 +943,27 @@ void main() { expect(mmap.containsKey('k1'), false); }); + test('should something about entries in returned map view', () { + var mmap = new SetMultimap() + ..add('k1', 'v1') + ..add('k1', 'v2'); + Map map = mmap.asMap(); + expect(map.entries, hasLength(1)); + expect(map.entries.single.key, equals('k1')); + expect(map.entries.single.value, unorderedEquals(['v1', 'v2'])); + }); + + test('should map from returned map view', () { + var mmap = new SetMultimap() + ..add('k1', 'v1') + ..add('k1', 'v2'); + Map map = mmap.asMap(); + var newMap = map.map((k, v) => new MapEntry(k, v.join(','))); + expect(newMap, hasLength(1)); + expect(newMap, contains('k1')); + expect(newMap, containsValue('v1,v2')); + }); + test('should reflect clearing of returned map view', () { var mmap = new SetMultimap() ..add('k1', 'v1') From 8ad2873acb94562b135f141b753c369794519233 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Fri, 23 Mar 2018 14:48:35 -0700 Subject: [PATCH 7/7] Add new Dart 2 Map methods to BiMap (#423) --- CHANGELOG.md | 2 + lib/src/collection/bimap.dart | 58 ++++++++------------ test/collection/bimap_test.dart | 93 +++++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d799a2d..25d81268 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ #### Master + * New: BiMap now includes a real implementation of `addEntries`, `get + entries`, `map`, `removeWhere`, `update`, and `updateAll`. * New: DelegatingIterable now includes a real implementation of `followedBy`, and accepts the `orElse` parameter on `singleWhere`. * New: DelegatingList now includes real implementations of `operator +`, diff --git a/lib/src/collection/bimap.dart b/lib/src/collection/bimap.dart index 8ff32c73..2a3f1958 100644 --- a/lib/src/collection/bimap.dart +++ b/lib/src/collection/bimap.dart @@ -70,13 +70,13 @@ class HashBiMap implements BiMap { int get length => _map.length; Iterable get values => _inverse.keys; + BiMap get inverse => _cached ??= new HashBiMap._from(_inverse, _map); + @override - // TODO: Dart 2.0 requires this method to be implemented. - // ignore: override_on_non_overriding_method - void addEntries(Iterable entries) { - // Change Iterable to Iterable> when - // the MapEntry class has been added. - throw new UnimplementedError("addEntries"); + void addEntries(Iterable> entries) { + for (var entry in entries) { + _add(entry.key, entry.value, false); + } } @override @@ -87,29 +87,11 @@ class HashBiMap implements BiMap { } @override - // TODO: Dart 2.0 requires this method to be implemented. - // ignore: override_on_non_overriding_getter - Iterable get entries { - // Change Iterable to Iterable> when - // the MapEntry class has been added. - throw new UnimplementedError("entries"); - } - - BiMap get inverse { - if (_cached == null) { - _cached = new HashBiMap._from(_inverse, _map); - } - return _cached; - } + Iterable> get entries => _map.entries; @override - // TODO: Dart 2.0 requires this method to be implemented. - // ignore: override_on_non_overriding_method - Map map(Object transform(K key, V value)) { - // Change Object to MapEntry when - // the MapEntry class has been added. - throw new UnimplementedError("map"); - } + Map map(MapEntry transform(K key, V value)) => + _map.map(transform); V putIfAbsent(K key, V ifAbsent()) { var value = _map[key]; @@ -124,10 +106,9 @@ class HashBiMap implements BiMap { } @override - // TODO: Dart 2.0 requires this method to be implemented. - // ignore: override_on_non_overriding_method void removeWhere(bool test(K key, V value)) { - throw new UnimplementedError("removeWhere"); + _inverse.removeWhere((v, k) => test(k, v)); + _map.removeWhere(test); } @override @@ -138,17 +119,22 @@ class HashBiMap implements BiMap { } @override - // TODO: Dart 2.0 requires this method to be implemented. - // ignore: override_on_non_overriding_method V update(K key, V update(V value), {V ifAbsent()}) { - throw new UnimplementedError("update"); + var value = _map[key]; + if (value != null) { + return _add(key, update(value), true); + } else { + if (ifAbsent == null) + throw new ArgumentError.value(key, 'key', 'Key not in map'); + return _add(key, ifAbsent(), false); + } } @override - // TODO: Dart 2.0 requires this method to be implemented. - // ignore: override_on_non_overriding_method void updateAll(V update(K key, V value)) { - throw new UnimplementedError("updateAll"); + for (var key in this.keys) { + _add(key, update(key, _map[key]), true); + } } void clear() { diff --git a/test/collection/bimap_test.dart b/test/collection/bimap_test.dart index 125bc69b..8e693b37 100644 --- a/test/collection/bimap_test.dart +++ b/test/collection/bimap_test.dart @@ -161,6 +161,14 @@ main() { map.remove(k1); expect(map.containsKey(k1), false); expect(map.inverse.containsKey(v1), false); + + map[k1] = v1; + map[k2] = v2; + map.removeWhere((k, v) => v.isOdd); + expect(map.containsKey(k1), false); + expect(map.containsKey(k2), true); + expect(map.inverse.containsKey(v1), false); + expect(map.inverse.containsKey(v2), true); }); test('should not contain mappings removed from its inverse', () { @@ -168,6 +176,48 @@ main() { map.inverse.remove(v1); expect(map.containsKey(k1), false); expect(map.inverse.containsKey(v1), false); + + map[k1] = v1; + map[k2] = v2; + map.inverse.removeWhere((v, k) => v.isOdd); + expect(map.containsKey(k1), false); + expect(map.containsKey(k2), true); + expect(map.inverse.containsKey(v1), false); + expect(map.inverse.containsKey(v2), true); + }); + + test('should update both sides', () { + map[k1] = v1; + map.update(k1, (v) => v + 1); + expect(map[k1], equals(v1 + 1)); + expect(map.inverse[v1 + 1], equals(k1)); + + map[k1] = v1; + map.inverse.update(v1, (k) => '_$k'); + expect(map['_$k1'], equals(v1)); + expect(map.inverse[v1], equals('_$k1')); + }); + + test('should update absent key values', () { + map[k1] = v1; + map.update(k2, (v) => v + 1, ifAbsent: () => 0); + expect(map[k2], equals(0)); + expect(map.inverse[0], equals(k2)); + + map[k1] = v1; + map.inverse.update(v2, (k) => '_$k', ifAbsent: () => '_X'); + expect(map['_X'], equals(v2)); + expect(map.inverse[v2], equals('_X')); + }); + + test('should update all values', () { + map[k1] = v1; + map[k2] = v2; + map.updateAll((k, v) => v + k.length); + expect(map[k1], equals(3)); + expect(map[k2], equals(4)); + expect(map.inverse[3], equals(k1)); + expect(map.inverse[4], equals(k2)); }); test('should be empty after clear', () { @@ -230,6 +280,49 @@ main() { expect(map.inverse.values, unorderedEquals([k1, k2])); }); + test('should add entries', () { + map.addEntries([new MapEntry(k1, v1)]); + expect(map[k1], equals(v1)); + expect(map.inverse[v1], equals(k1)); + + map.inverse.addEntries([new MapEntry(v2, k2)]); + expect(map[k2], equals(v2)); + expect(map.inverse[v2], equals(k2)); + }); + + test('should get entries', () { + map[k1] = v1; + map.inverse[v2] = k2; + + var mapEntries = map.entries; + expect(mapEntries, hasLength(2)); + // MapEntry objects are not equal to each other; cannot use `contains`. :( + expect(mapEntries.singleWhere((e) => e.key == k1).value, equals(v1)); + expect(mapEntries.singleWhere((e) => e.key == k2).value, equals(v2)); + + var inverseEntries = map.inverse.entries; + expect(inverseEntries, hasLength(2)); + expect(inverseEntries.singleWhere((e) => e.key == v1).value, equals(k1)); + expect(inverseEntries.singleWhere((e) => e.key == v2).value, equals(k2)); + }); + + test('should map mappings', () { + map[k1] = v1; + map[k2] = v2; + + var mapped = map.map((k, v) => new MapEntry(k.toUpperCase(), '$k / $v')); + expect(mapped, contains('K1')); + expect(mapped, contains('K2')); + expect(mapped['K1'], equals('k1 / 1')); + expect(mapped['K2'], equals('k2 / 2')); + + var mapped2 = map.inverse.map((v, k) => new MapEntry('$v$v', k.length)); + expect(mapped2, contains('11')); + expect(mapped2, contains('22')); + expect(mapped2['11'], equals(2)); + expect(mapped2['22'], equals(2)); + }); + test('should add mappings via putIfAbsent if absent', () { map.putIfAbsent(k1, () => v1); expect(map[k1], v1);