Dart distinctBy оператор для коллекций
Необходимо выбрать из коллекции только уникальные элементы. Обычно в Dart используется следующий подход (не используя сторонние библиотеки):
final collection = [1, 3, 5, 1, 5];
final unique = collection.toSet().toList();
Этот подход хорошо подходит для простых типов, но не работает если нужно отфильтровать по определенным полям класса, поскольку Set вычисляет дубликаты по equals и, к тому же, не гарантирует порядок элементов в коллекции.
Например:
class KeyValuePair {
final String key;
final int value;
const KeyValuePair(this.key, this.value);
@override
String toString() => "[$key, $value]";
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is KeyValuePair &&
runtimeType == other.runtimeType &&
key == other.key &&
value == other.value;
@override
int get hashCode => key.hashCode ^ value.hashCode;
}
void main() {
final collection = [
KeyValuePair("A", 1),
KeyValuePair("B", 3),
KeyValuePair("C", 5),
KeyValuePair("D", 1),
KeyValuePair("E", 5),
];
// [A, 1], [B, 3], [C, 5], [D, 1], [E, 5]
print(collection.toSet().toList().join(", "));
}
Ответы (1 шт):
Автор решения: Ziens
→ Ссылка
Решением может быть функция-расширение для Iterable:
extension IteratorExt<T> on Iterable<T> {
/// Возвращает [Iterable] который фильтрует повторяющиеся элементы.
/// Элементы сравниваются по [keySelector].
/// Если какой-то из элементов повторяется, то вызывается [onDuplicate] с помощью которого можно проигнорировать дубликат
/// (если [onDuplicate] параметр опущен или возвращает `null`)
/// или заменить на новое значение.
/// Если элемент возвращаемый [onDuplicate] эквивалентен оригиналу (по equals) или
/// соответствующий ему ключ уже существует, то он будет проигнорирован.
Iterable<T> distinctBy(Object Function(T) keySelector,
{T Function(T old, T current) onDuplicate}) =>
_DistinctIterable(this, keySelector, onDuplicate);
}
class _DistinctIterable<T> extends Iterable<T> {
final Iterable<T> _iterable;
final Object Function(T) _keySelector;
final T Function(T old, T current) _onDuplicate;
final Map<Object, T> uniqueElements = {};
_DistinctIterable(
this._iterable,
this._keySelector,
T Function(T, T) onDuplicate,
) : _onDuplicate = onDuplicate ?? ((old, curr) => null);
Iterator<T> get iterator {
_iterable.forEach((element) {
final key = _keySelector(element);
final old = uniqueElements[key];
if (old != null) {
final value = _onDuplicate(old, element);
if (value != null) uniqueElements[_keySelector(value)] = value;
} else
uniqueElements[key] = element;
});
return uniqueElements.values.iterator;
}
}
Примеры использования:
void main() {
// 1, 3, 5
print([1, 3, 5, 1, 5].distinctBy((i) => i).join(", "));
// 1, 3, 5, 2, 10
print(
[1, 3, 5, 1, 5].distinctBy((i) => i, onDuplicate: (old, current ) => old + current).join(", "));
final collection = [
KeyValuePair("A", 1),
KeyValuePair("B", 3),
KeyValuePair("C", 5),
KeyValuePair("D", 1),
KeyValuePair("E", 5),
];
// [A, 1], [B, 3], [C, 5]
print(collection.distinctBy((obj) => obj.value).join(", "));
// [A, 1], [B, 3], [C, 5], [D (duplicated from A), 2], [E (duplicated from C), 10]
print(collection
.distinctBy((obj) => obj.value,
onDuplicate: (old, current) => KeyValuePair(
"${current.key} (duplicated from ${old.key})",
old.value + current.value,
))
.join(", "));
}
class KeyValuePair {
final String key;
final int value;
const KeyValuePair(this.key, this.value);
@override
String toString() => "[$key, $value]";
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is KeyValuePair &&
runtimeType == other.runtimeType &&
key == other.key &&
value == other.value;
@override
int get hashCode => key.hashCode ^ value.hashCode;
}