distinctBy<R> method
Returns a new list containing the first occurrence of each distinct element
as determined by the key returned from keySelector.
Elements are considered duplicates if their keys are equal according to
the provided equals function (or default equality if not specified).
The order of elements in the result preserves their first occurrence.
Optional parameters allow for custom comparison logic:
equals: A custom equality function for comparing keys. Useful for case-insensitive comparisons or complex objects.hashCode: A custom hash code function for generating hash codes for keys. Useful for optimizing performance with specific key characteristics.isValidKey: A custom function to validate keys. Invalid keys are excluded entirely.
If you provide equals, you must also provide hashCode (and vice versa).
-
keySelector: Extracts the comparison key from each element. Elements with equal keys are considered duplicates. -
equals: Optional custom equality function for comparing keys. Must be provided together withhashCode. -
hashCode: Optional custom hash code function for keys. Must be provided together withequalsand be consistent: ifequals(a, b)returns true, thenhashCode(a)must equalhashCode(b). -
isValidKey: Optional predicate to filter which keys are valid. Elements whose keys fail this predicate (returnfalse) are excluded entirely from the result. This is useful for filtering out null keys, empty strings, or other invalid values.
Performance
- Time complexity: O(n) where n is the number of elements
- Space complexity: O(k) where k is the number of unique valid keys
- Uses a HashSet to track unique keys
Examples
Basic usage:
final people = [
Person('Alice', 25),
Person('Bob', 30),
Person('Alice', 28), // Duplicate name
];
final uniquePeople = people.distinctBy((p) => p.name);
// Result: [Person('Alice', 25), Person('Bob', 30)]
Case-insensitive string keys:
final names = ['Alice', 'ALICE', 'Bob', 'alice'];
final uniqueNames = names.distinctBy(
(name) => name,
equals: (a, b) => a.toLowerCase() == b.toLowerCase(),
hashCode: (key) => key.toLowerCase().hashCode,
);
// Result: ['Alice', 'Bob']
This method is efficient, using a native Set for standard equality and a LinkedHashSet when custom equality/hash logic is provided.
Implementation
List<E> distinctBy<R>(
R Function(E) keySelector, {
bool Function(R a, R b)? equals,
int Function(R key)? hashCode,
bool Function(R key)? isValidKey,
}) {
if ((equals == null) != (hashCode == null)) {
throw ArgumentError(
'distinctBy: If you provide `equals` you must also provide `hashCode` '
'(and vice versa). Providing only one can break hashing semantics.',
);
}
final result = <E>[];
if (equals == null && hashCode == null) {
final seenKeys = <R>{};
for (final element in this) {
final key = keySelector(element);
if (isValidKey != null && !isValidKey(key)) continue;
if (seenKeys.add(key)) {
result.add(element);
}
}
return result;
}
final seenKeys = LinkedHashSet<R>(
equals: equals,
hashCode: hashCode,
);
for (final element in this) {
final key = keySelector(element);
if (isValidKey != null && !isValidKey(key)) continue;
if (seenKeys.add(key)) {
result.add(element);
}
}
return result;
}