Skip to content

Commit

Permalink
Introduce a new lastBy() function (#223)
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
Kernald committed Jun 2, 2022
1 parent 69766da commit f9b433d
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 0 deletions.
4 changes: 4 additions & 0 deletions 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.
Expand Down
7 changes: 7 additions & 0 deletions lib/src/functions.dart
Expand Up @@ -41,6 +41,13 @@ Map<K, V> mergeMaps<K, V>(Map<K, V> map1, Map<K, V> 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<T, S> lastBy<S, T>(Iterable<S> 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
Expand Down
7 changes: 7 additions & 0 deletions lib/src/iterable_extensions.dart
Expand Up @@ -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.
///
Expand Down Expand Up @@ -353,6 +354,12 @@ extension IterableExtension<T> on Iterable<T> {
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<K, T> lastBy<K>(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].
Expand Down
19 changes: 19 additions & 0 deletions test/extensions_test.dart
Expand Up @@ -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), {});
Expand Down
20 changes: 20 additions & 0 deletions test/functions_test.dart
Expand Up @@ -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);
Expand Down

0 comments on commit f9b433d

Please sign in to comment.