Skip to content

Commit

Permalink
Merge branch 'master' into core-libs-dart-4
Browse files Browse the repository at this point in the history
* master:
  Add new Dart 2 Map methods to BiMap (google#423)
  Add new Dart 2 Map, Iterable, and List methods to multimap stuff (google#425)
  Add .dart_tool to gitignore (google#431)
  Update changelog for LruMap fix (google#430)
  Ensure that LruMap linkage is preserved (google#429)
  Update CHANGELOG.md (google#428)
  Update CHANGELOG/pubspec to include 0.28.1 (google#427)
  • Loading branch information
srawlins committed Mar 23, 2018
2 parents 6c84324 + 8ad2873 commit 9271c09
Show file tree
Hide file tree
Showing 10 changed files with 199 additions and 70 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
packages
pubspec.lock
build/
.dart_tool/
.pub
.packages

Expand Down
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,30 @@
#### 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 +`,
`indexWhere`, and `lastIndexWhere`.
* New: LruMap now includes a real implementation of `addEntries`, `get
entries`, `removeWhere`, `update`, and `updateAll`.
* 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.

#### 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

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
58 changes: 22 additions & 36 deletions lib/src/collection/bimap.dart
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,13 @@ class HashBiMap<K, V> implements BiMap<K, V> {
int get length => _map.length;
Iterable<V> get values => _inverse.keys;

BiMap<V, K> 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<Object> entries) {
// Change Iterable<Object> to Iterable<MapEntry<K, V>> when
// the MapEntry class has been added.
throw new UnimplementedError("addEntries");
void addEntries(Iterable<MapEntry<K, V>> entries) {
for (var entry in entries) {
_add(entry.key, entry.value, false);
}
}

@override
Expand All @@ -87,29 +87,11 @@ class HashBiMap<K, V> implements BiMap<K, V> {
}

@override
// TODO: Dart 2.0 requires this method to be implemented.
// ignore: override_on_non_overriding_getter
Iterable<Null> get entries {
// Change Iterable<Null> to Iterable<MapEntry<K, V>> when
// the MapEntry class has been added.
throw new UnimplementedError("entries");
}

BiMap<V, K> get inverse {
if (_cached == null) {
_cached = new HashBiMap._from(_inverse, _map);
}
return _cached;
}
Iterable<MapEntry<K, V>> get entries => _map.entries;

@override
// TODO: Dart 2.0 requires this method to be implemented.
// ignore: override_on_non_overriding_method
Map<K2, V2> map<K2, V2>(Object transform(K key, V value)) {
// Change Object to MapEntry<K2, V2> when
// the MapEntry class has been added.
throw new UnimplementedError("map");
}
Map<K2, V2> map<K2, V2>(MapEntry<K2, V2> transform(K key, V value)) =>
_map.map(transform);

V putIfAbsent(K key, V ifAbsent()) {
var value = _map[key];
Expand All @@ -124,10 +106,9 @@ class HashBiMap<K, V> implements BiMap<K, V> {
}

@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
Expand All @@ -138,17 +119,22 @@ class HashBiMap<K, V> implements BiMap<K, V> {
}

@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() {
Expand Down
4 changes: 3 additions & 1 deletion lib/src/collection/lru_map.dart
Original file line number Diff line number Diff line change
Expand Up @@ -227,11 +227,13 @@ class LinkedLruHashMap<K, V> implements LruMap<K, V> {
_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;
}
Expand Down
45 changes: 14 additions & 31 deletions lib/src/collection/multimap.dart
Original file line number Diff line number Diff line change
Expand Up @@ -309,51 +309,36 @@ class _WrappedMap<K, V, C extends Iterable<V>> implements Map<K, C> {
}

@override
// TODO: Dart 2.0 requires this method to be implemented.
// ignore: override_on_non_overriding_getter
Iterable<Null> get entries {
// Change Iterable<Null> to Iterable<MapEntry<K, V>> when
// the MapEntry class has been added.
throw new UnimplementedError("entries");
}
Iterable<MapEntry<K, C>> 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<Object> entries) {
// Change Iterable<Object> to Iterable<MapEntry<K, V>> when
// the MapEntry class has been added.
throw new UnimplementedError("addEntries");
void addEntries(Iterable<MapEntry<K, C>> 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<K2, V2> map<K2, V2>(Object transform(K key, C value)) {
// Change Object to MapEntry<K2, V2> when
// the MapEntry class has been added.
throw new UnimplementedError("map");
}
Map<K2, C2> map<K2, C2>(MapEntry<K2, C2> 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 = <K>[];
for (var key in keys) {
if (test(key, this[key])) keysToRemove.add(key);
}
for (var key in keysToRemove) {
_multimap.removeAll(key);
}
}
}

Expand Down Expand Up @@ -606,8 +591,6 @@ class _WrappedList<K, V> extends _WrappedIterable<K, V, List<V>>
}

@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;
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: quiver
version: 0.28.0
version: 0.28.1
authors:
- Justin Fagnani <justinfagnani@google.com>
- Yegor Jbanov <yjbanov@google.com>
Expand Down
93 changes: 93 additions & 0 deletions test/collection/bimap_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -161,13 +161,63 @@ 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', () {
map[k1] = v1;
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', () {
Expand Down Expand Up @@ -230,6 +280,49 @@ main() {
expect(map.inverse.values, unorderedEquals([k1, k2]));
});

test('should add entries', () {
map.addEntries([new MapEntry<String, int>(k1, v1)]);
expect(map[k1], equals(v1));
expect(map.inverse[v1], equals(k1));

map.inverse.addEntries([new MapEntry<int, String>(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);
Expand Down
9 changes: 9 additions & 0 deletions test/collection/lru_map_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,15 @@ void main() {
lruMap.removeWhere((k, v) => v.contains('A'));
expect(lruMap.keys.toList(), ['C', 'B']);
});

test('linkage correctly preserved on remove', () {
lruMap.remove('B');
lruMap['A'];

final keys = <String>[];
lruMap.forEach((String k, String v) => keys.add(k));
expect(keys, ['A', 'C']);
});
});

test('the linked list is mutated when promoting an item in the middle', () {
Expand Down
Loading

0 comments on commit 9271c09

Please sign in to comment.