Skip to content

Commit

Permalink
Introduce IterableMapEntryExtension for use with Map.entries. (#715)
Browse files Browse the repository at this point in the history
* Introduce `IterableMapEntryExtension` for use with `Map.entries`.

**Example**:
```dart
final myMap = {
  'foo': 42,
  'bar': -1,
  'foobar': 21,
};

// myMap without negative values
myMap.entries.whereValue((v) => v >= 0).toMap();

// myMap, but only keys that start with 'foo'
myMap.entries.whereKey((k) => k.startsWith('foo')).toMap();
```
  • Loading branch information
jonasfj authored Nov 5, 2024
1 parent 4b62792 commit 9e37915
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 1 deletion.
4 changes: 4 additions & 0 deletions pkgs/collection/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 1.19.1-wip
- Add `IterableMapEntryExtension` for working on `Map` as a list of pairs, using
`Map.entries`.

## 1.19.1

- Move to `dart-lang/core` monorepo.
Expand Down
37 changes: 37 additions & 0 deletions pkgs/collection/lib/src/iterable_extensions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -914,6 +914,43 @@ extension IterableIterableExtension<T> on Iterable<Iterable<T>> {
};
}

/// Extension on iterables of [MapEntry].
///
/// An [Iterable<MapEntry>] is obtained using [Map.entries]. These extensions
/// facilitates working directly on the entries of a [Map].
extension IterableMapEntryExtension<K, V> on Iterable<MapEntry<K, V>> {
/// The elements whose [MapEntry.key] values satisfy [test].
///
/// The resulting iterable is lazily computing its elements
/// based on the elements this iterable.
Iterable<MapEntry<K, V>> whereKey(bool Function(K) test) =>
where((e) => test(e.key));

/// The elements whose [MapEntry.value] values satisfy [test].
///
/// The resulting iterable is lazily computing its elements
/// based on the elements this iterable.
Iterable<MapEntry<K, V>> whereValue(bool Function(V) test) =>
where((e) => test(e.value));

/// A new lazy [Iterable] of the [MapEntry.key]s of these entries.
///
/// Do not use this getter as `map.entries.keys`, just use `map.keys`
/// directly.
Iterable<K> get keys => map((e) => e.key);

/// A new lazy [Iterable] of the [MapEntry.value]s of these entries.
///
/// Do not use this getter as `map.entries.values`, just use `map.values`
/// directly.
Iterable<V> get values => map((e) => e.value);

/// Create a [Map<K, V>] from all elements.
///
/// This is a short-hand for [Map.fromEntries].
Map<K, V> toMap() => Map<K, V>.fromEntries(this);
}

/// Extensions that apply to iterables of [Comparable] elements.
///
/// These operations can assume that the elements have a natural ordering,
Expand Down
2 changes: 1 addition & 1 deletion pkgs/collection/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: collection
version: 1.19.1
version: 1.19.1-wip
description: >-
Collections and utilities functions and classes related to collections.
repository: https://github.com/dart-lang/core/tree/main/pkgs/collection
Expand Down
174 changes: 174 additions & 0 deletions pkgs/collection/test/extensions_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1122,6 +1122,180 @@ void main() {
});
});
});
group('of MapEntry', () {
group('.whereKey', () {
test('empty', () {
expect(
iterable(<MapEntry<String, int>>[]).whereKey(unreachable),
isEmpty,
);
});
test('single', () {
expect(
iterable([const MapEntry('a', 1)]).whereKey((k) => k == 'a'),
[const MapEntry('a', 1)],
);
expect(
iterable([const MapEntry('a', 1)]).whereKey((k) => k == 'b'),
isEmpty,
);
});
test('multiple', () {
expect(
iterable([
const MapEntry('a', 1),
const MapEntry('b', 2),
]).whereKey((k) => k == 'a'),
[const MapEntry('a', 1)],
);
expect(
iterable([
const MapEntry('a', 1),
const MapEntry('b', 2),
]).whereKey((k) => k == 'b'),
[const MapEntry('b', 2)],
);
expect(
iterable([
const MapEntry('a', 1),
const MapEntry('b', 2),
]).whereKey((k) => k != 'c'),
[const MapEntry('a', 1), const MapEntry('b', 2)],
);
expect(
iterable([
const MapEntry('a', 1),
const MapEntry('b', 2),
const MapEntry('a', 3),
]).whereKey((k) => k == 'a'),
[const MapEntry('a', 1), const MapEntry('a', 3)],
);
});
});
group('.whereValue', () {
test('empty', () {
expect(
iterable(<MapEntry<String, int>>[]).whereValue(unreachable),
isEmpty,
);
});
test('single', () {
expect(
iterable([const MapEntry('a', 1)]).whereValue((v) => v == 1),
[const MapEntry('a', 1)],
);
expect(
iterable([const MapEntry('a', 1)]).whereValue((v) => v == 2),
isEmpty,
);
});
test('multiple', () {
expect(
iterable([
const MapEntry('a', 1),
const MapEntry('b', 2),
]).whereValue((v) => v == 1),
[const MapEntry('a', 1)],
);
expect(
iterable([
const MapEntry('a', 1),
const MapEntry('b', 2),
]).whereValue((v) => v == 2),
[const MapEntry('b', 2)],
);
expect(
iterable([
const MapEntry('a', 1),
const MapEntry('b', 2),
]).whereValue((v) => v != 3),
[const MapEntry('a', 1), const MapEntry('b', 2)],
);
expect(
iterable([
const MapEntry('a', 1),
const MapEntry('b', 2),
const MapEntry('c', 1),
]).whereValue((v) => v == 1),
[const MapEntry('a', 1), const MapEntry('c', 1)],
);
expect(
iterable([
const MapEntry('a', 1),
const MapEntry('b', 2),
const MapEntry('a', 1),
]).whereValue((v) => v == 1),
[const MapEntry('a', 1), const MapEntry('a', 1)],
);
});
});
group('.keys', () {
test('empty', () {
expect(iterable(<MapEntry<String, int>>[]).keys, isEmpty);
});
test('single', () {
expect(iterable([const MapEntry('a', 1)]).keys, ['a']);
});
test('multiple', () {
expect(
iterable([const MapEntry('a', 1), const MapEntry('b', 2)]).keys,
['a', 'b'],
);
expect(
iterable([
const MapEntry('a', 1),
const MapEntry('b', 2),
const MapEntry('a', 3),
]).keys,
['a', 'b', 'a'],
);
});
});
group('.values', () {
test('empty', () {
expect(iterable(<MapEntry<String, int>>[]).values, isEmpty);
});
test('single', () {
expect(iterable([const MapEntry('a', 1)]).values, [1]);
});
test('multiple', () {
expect(
iterable([const MapEntry('a', 1), const MapEntry('b', 2)]).values,
[1, 2],
);
expect(
iterable([
const MapEntry('a', 1),
const MapEntry('b', 2),
const MapEntry('a', 3),
]).values,
[1, 2, 3],
);
});
});
group('.toMap', () {
test('empty', () {
expect(iterable(<MapEntry<String, int>>[]).toMap(), <String, int>{});
});
test('single', () {
expect(iterable([const MapEntry('a', 1)]).toMap(), {'a': 1});
});
test('multiple', () {
expect(
iterable([const MapEntry('a', 1), const MapEntry('b', 2)]).toMap(),
{'a': 1, 'b': 2},
);
expect(
iterable([
const MapEntry('a', 1),
const MapEntry('b', 2),
const MapEntry('a', 3),
]).toMap(),
{'b': 2, 'a': 3},
);
});
});
});
group('of comparable', () {
group('.min', () {
test('empty', () {
Expand Down

0 comments on commit 9e37915

Please sign in to comment.