From 6fd19ed4eb2cd9adabd678a76e49982907eca9ab Mon Sep 17 00:00:00 2001 From: Marc Plano-Lesay Date: Fri, 3 Jun 2022 08:46:11 +1000 Subject: [PATCH] Introduce a new lastBy() function (dart-lang/collection#223) Similar to groupBy(), except that it only keeps the latest value corresponding to a given key. Prior art: [Kotlin](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/associate-by.html) --- pkgs/collection/CHANGELOG.md | 4 ++++ pkgs/collection/lib/src/functions.dart | 7 +++++++ .../lib/src/iterable_extensions.dart | 7 +++++++ pkgs/collection/test/extensions_test.dart | 19 ++++++++++++++++++ pkgs/collection/test/functions_test.dart | 20 +++++++++++++++++++ 5 files changed, 57 insertions(+) diff --git a/pkgs/collection/CHANGELOG.md b/pkgs/collection/CHANGELOG.md index 806a8b0b..f36e987f 100644 --- a/pkgs/collection/CHANGELOG.md +++ b/pkgs/collection/CHANGELOG.md @@ -1,5 +1,9 @@ ## 1.16.1-dev +* Add a top-level `lastBy()` function that converts an `Iterable` to a `Map` by + grouping its elements using a function, keeping the last element for each + computed key. Also available as an extension method on `Iterable`. + ## 1.16.0 * Add an `Iterable.slices` extension method. diff --git a/pkgs/collection/lib/src/functions.dart b/pkgs/collection/lib/src/functions.dart index 64a38eec..8f60b265 100644 --- a/pkgs/collection/lib/src/functions.dart +++ b/pkgs/collection/lib/src/functions.dart @@ -41,6 +41,13 @@ Map mergeMaps(Map map1, Map map2, return result; } +/// Associates the elements in [values] by the value returned by [key]. +/// +/// Returns a map from keys computed by [key] to the last value for which [key] +/// returns that key. +Map lastBy(Iterable values, T Function(S) key) => + {for (var element in values) key(element): element}; + /// Groups the elements in [values] by the value returned by [key]. /// /// Returns a map from keys computed by [key] to a list of all values for which diff --git a/pkgs/collection/lib/src/iterable_extensions.dart b/pkgs/collection/lib/src/iterable_extensions.dart index ac821121..88f320dd 100644 --- a/pkgs/collection/lib/src/iterable_extensions.dart +++ b/pkgs/collection/lib/src/iterable_extensions.dart @@ -7,6 +7,7 @@ import 'dart:math' show Random; import 'package:collection/src/utils.dart'; import 'algorithms.dart'; +import 'functions.dart' as functions; /// Extensions that apply to all iterables. /// @@ -353,6 +354,12 @@ extension IterableExtension on Iterable { return null; } + /// Associates the elements in [this] by the value returned by [key]. + /// + /// Returns a map from keys computed by [key] to the last value for which [key] + /// returns that key. + Map lastBy(K Function(T) key) => functions.lastBy(this, key); + /// Groups elements by [keyOf] then folds the elements in each group. /// /// A key is found for each element using [keyOf]. diff --git a/pkgs/collection/test/extensions_test.dart b/pkgs/collection/test/extensions_test.dart index 1ccebde2..ce4b2e92 100644 --- a/pkgs/collection/test/extensions_test.dart +++ b/pkgs/collection/test/extensions_test.dart @@ -520,6 +520,25 @@ void main() { expect(iterable([1, 3, 5]).singleOrNull, null); }); }); + group('.lastBy', () { + test('empty', () { + expect(iterable([]).lastBy((dynamic _) {}), {}); + }); + test('single', () { + expect(iterable([1]).lastBy(toString), { + '1': 1, + }); + }); + test('multiple', () { + expect( + iterable([1, 2, 3, 4, 5]).lastBy((x) => x.isEven), + { + false: 5, + true: 4, + }, + ); + }); + }); group('.groupFoldBy', () { test('empty', () { expect(iterable([]).groupFoldBy(unreachable, unreachable), {}); diff --git a/pkgs/collection/test/functions_test.dart b/pkgs/collection/test/functions_test.dart index 97bb4abb..9d41625e 100644 --- a/pkgs/collection/test/functions_test.dart +++ b/pkgs/collection/test/functions_test.dart @@ -79,6 +79,26 @@ void main() { }); }); + group('lastBy()', () { + test('returns an empty map for an empty iterable', () { + expect( + lastBy([], (_) => fail("Must not be called for empty input")), + isEmpty, + ); + }); + + test("keeps the latest element for the function's return value", () { + expect( + lastBy(['foo', 'bar', 'baz', 'bop', 'qux'], + (String string) => string[1]), + equals({ + 'o': 'bop', + 'a': 'baz', + 'u': 'qux', + })); + }); + }); + group('groupBy()', () { test('returns an empty map for an empty iterable', () { expect(groupBy([], expectAsync1((dynamic _) {}, count: 0)), isEmpty);