dartのリストでよく利用する機能のExtension化
筋トレの記録アプリ をFlutter
で実装していてリスト操作することがあったり、リストから最大値を求めるようなことがよくあったりするためIterable
のextension
として👇のようなものを定義してます。
extension IterableExtension<E> on Iterable<E> {
/// Iterableをmapしながら indexも返す
Iterable<T> mapIndex<T>(T Function(int i, E e) action) {
var i = 0;
return map((e) => action(i++, e));
}
/// IterableをforEachでindexも返す
void forEachIndex(Function(int i, E e) action) {
toList().asMap().forEach((i, e) => action(i, e));
}
/// 各要素のmaxを返す。maxの判定は各[element]のgetValueで判定する。
E? max(num Function(E element) getValue) {
if (isEmpty) return null;
return reduce((a, b) {
final aValue = getValue(a);
final bValue = getValue(b);
if (aValue > bValue) return a;
if (aValue < bValue) return b;
return a;
});
}
/// 各要素のmaxをElementWithIndex(elementとindexを持つクラス)で返す。
/// maxの判定は各[element]のgetValueで判定する。
ElementWithIndex<E>? maxWithIndex(num Function(E element) getValue) {
if (isEmpty) return null;
return mapIndex((i, e) => ElementWithIndex<E>(i, e)).reduce((a, b) {
final aValue = getValue(a.element);
final bValue = getValue(b.element);
if (aValue > bValue) return a;
if (aValue < bValue) return b;
return a;
});
}
}
class ElementWithIndex<E> {
const ElementWithIndex(this.index, this.element);
final int index;
final E element;
}
/// 合計(int)
extension IterableExtensionInt on Iterable<int> {
int get total {
return reduce((a, b) => a + b);
}
}
/// 合計(int)
extension IterableExtensionDouble on Iterable<double> {
double get total {
return reduce((a, b) => a + b);
}
}
それぞれなぜ定義しているかを下記してみます。
mapIndex, forEachIndex
👇の記事を参考にさせていただいてます。
example
void main() {
final List<String> names = ['Jone', 'Ken', 'Taro'];
names.forEachIndex((i, e) {
print('${i}: ${e}');
});
/**
0: Jone
1: Ken
2: Taro
*/
final list = names.mapIndex((i, e) => ElementWithIndex(i, e)).toList();
list.forEach((e) {
print('${e.index}: ${e.element}');
});
/**
0: Jone
1: Ken
2: Taro
*/
}
forEachIndex
は toList().asMap().forEach()
でも書けるもののあった方が便利な気はしています。
mapIndex
は、toList().asMap().map(MapEntry<K2, V2> convert(K key, V value))
だと mapのconvertでMapEntryを返さなければならなくて、代替ができないため、あった方が便利だと感じています。
max
dart
には標準で List
や Iterable
に max関数が無い(と思っている)のと、あるクラスの特定のプロパティでのmaxを取得したいと言うのがよくあるため、extensionで定義してます。
max関数自体はdartに標準で存在しているのですが、これは2つの値のうち大きいものを返す、というものです。そのため、list.reduce(max)
みたいな感じで最大値取得を書くケースは良くあると思います。
標準のmax関数 の定義はT max<T extends num>(T a, T b);
であり、標準でlist.reduce(max)
を利用する場合は比較する値が数値である必要があるため、自分は上記の通り要素がクラスでその要素のプロパティでのmaxを取得したい、というのがあるため、max
メソッドをextensionに定義しています。
example
class TrainingSet {
const TrainingSet(this.weight, this.note);
final double weight;
final String note;
}
void main() {
final List<TrainingSet> trainingSets = [
TrainingSet(20, 'ベンチプレス1'),
TrainingSet(30, 'ベンチプレス2'),
TrainingSet(15, 'ベンチプレス3'),
];
final maxSet = trainingSets.max((e) => e.weight);
print(maxSet!.note);
// ベンチプレス2
}
maxWithIndex
リスト内の最大値をもつ要素を取得するのは、👆のextensionで定義した max
でできるのですが、maxを取得した上で、その要素のindexも取得したいこともわたしはよくあります。
これは要素自体を取得できれば List
のindexOfでも取得できるのですが、リストの要素が freezedのクラスになっている場合に、その要素の等価かどうかが 全てのプロパティが一致しているかどうかでの判定になるため、それによる誤動作があったらちょっと嫌だなと言う思いで書いてます。
この最大値取得の処理ではそれ(freezedの等価判定による影響)による誤動作は無いと思うのでindexOfで問題無いとは思うのですが、リストの並べ替えを実装している時にはfreezedの等価判定に依存する問題に直面していました。
それが嫌だったので、maxWithIndex
をextensionに定義しているため、定義したモチベーションとしては独自性が強いと思っています。
freezedのクラスの等価については下記でまとめています。
Example
class TrainingSet {
const TrainingSet(this.weight, this.note);
final double weight;
final String note;
}
void main() {
final List<TrainingSet> trainingSets = [
TrainingSet(20, 'ベンチプレス1'),
TrainingSet(30, 'ベンチプレス2'),
TrainingSet(15, 'ベンチプレス3'),
];
final maxSetWithIndex = trainingSets.maxWithIndex((e) => e.weight)!;
print(maxSetWithIndex.element.note);
print(maxSetWithIndex.index);
// ベンチプレス2
// 1
}
total
int, doubleリストの合計。
これ swiftのwhere句 みたいに上手くできれば良いのですが、やり方がわからず上手くできず...
extension IterableExtensionXXX<E extends XXX> on Iterable<E>
みたいなことはできたのですが、extension IterableExtensionXXX<E extends num> on Iterable<E>
という感じでは上手くできずでした(わかる方いたら教えて欲しいです)
/// 合計(int)
extension IterableExtensionInt on Iterable<int> {
int get total {
return reduce((a, b) => a + b);
}
}
/// 合計(double)
extension IterableExtensionDouble on Iterable<double> {
double get total {
return reduce((a, b) => a + b);
}
}
Example
void main() {
final List<double> doubleNums = [
10.5, 20.1, 30.6
];
final List<int> intNums = [
1, 10, 20
];
print(doubleNums.total);
print(intNums.total);
// 61.2
// 31
}
Discussion