Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce IterableMapEntryExtension for use with Map.entries. #715

Merged
merged 2 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Too bad where is taken, so it can't be where({bool Function(K)? key, bool Function(V)? value}))

Copy link
Member Author

@jonasfj jonasfj Nov 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I feel the same with .map.

But I wouldn't make where({bool Function(K)? key, bool Function(V)? value}).
I would make: where(bool Function(K, V) test), ignoring a parameter in a closure is not that bad.

Maybe, we should consider adding:

  • wherePair(bool Function(K, V) test)
  • mapPair<T>(T Function(K, V) toElement)

We could consider adding such extension methods to:

  • Iterable<MapEntry<K, V>>, and,
  • Iterable<(A, B)>.

But that seems outside the scope of this PR.

And it's entirely possible that a language feature providing pattern matching in the signature of a closure is better.

Copy link
Member

@lrhn lrhn Nov 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe whereEntry/mapEntry/anyEntry/everyEntry with an X Function(K, V) argument. (Don't say "pair", it's not — just — a pair! It's a key-and-value.)
Some of those (not whereEntry) could be extensions to Map<K,V> too. (Not saying they should, but they could.)


/// 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be 1.20.0, unless 1.19.0 hasn't been released yet.
(Adds new feature ⇒ minor version increment.)

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