DartのListについて調べる

公式リファレンスのListクラスのページを読んでいく
動作確認にはDartPadを使う

サブクラスには固定長と可変長のものがある
多分Unmodifiedから始まるのは可変長だろう

とりあえず使ってみる
void main() {
final fixedLengthList = List<int>.filled(5, 0);
print(fixedLengthList); // [0, 0, 0, 0, 0]
fixedLengthList.add(0); // Uncaught Error: Unsupported operation: add
}
filledコンストラクタで作った場合は追加できない

[]
を使うと可変にできる。
void main() {
final growableList = [0, 0, 0, 0, 0];
growableList.add(0);
print(growableList); // [0, 0, 0, 0, 0, 0]
}

あまりないだろうけどforループなどの中でリストに要素を追加するとConcurrentModificationError例外が発生するようだ

Listクラスには下記の7つのコンストラクタがある
- List
- empty
- filled
- from
- generate
- of
- unmodifiable
一番最初のListは非推奨、代わりにリストリテラル[]
やfilledを使おうとのこと
emptyは空のリストを作成する、省略可能なgrowableオプションを受け取る、デフォルトはfalse
void main() {
final list = List<int>.empty(growable: true);
list.add(1);
print(list); // [1]
}
filledは先ほど見た通り
fromはイテレーターを受け取ってリストを作成する、イテレーターの型は任意
generateは長さとgenerator
関数からリストを作成する、generatorの引数はint
型のindex
ofはfromと似ているがイテレーターの型がリストの型と同じである必要がある
unmodifiableはfromと同様に任意の型のイテレーターを受け取る、長さや内容を一切変更できないリストを作成する

Listクラスには10個のプロパティがある、それぞれ見てみる
void main() {
final list = [1, 2, 3, 4, 5];
print(list.first);
print(list.hashCode);
print(list.isEmpty);
print(list.isNotEmpty);
print(list.iterator);
print(list.last);
print(list.length);
print(list.reversed);
print(list.runtimeType);
print(list.single);
}
実行結果
1
795075260
false
true
Instance of 'ArrayIterator<int>'
5
5
(5, 4, 3, 2, 1)
JSArray<int>
Uncaught Error: Bad state: Too many elements
reservedはリストと思いきや[]
でないので違うようだ
runtimeTypeはJSArray<int>
、List<int>
ではない
singleは要素が一つならその要素そのものを返すが、それ以外の場合はStateError例外を発生させる

addメソッドを使うと要素を追加できる
void main() {
final list = [1];
list.add(2);
print(list); // [1, 2]
}

addAllメソッドを使うと複数の要素を追加できる、引数は同じ型のイテレータ
void main() {
final list = [1];
list.addAll([2, 3]);
print(list); // [1, 2, 3]
}

anyメソッドを使うと指定した条件を満たす要素が含まれるかをチェックできる
void main() {
final list = [1, 2, 3];
print(list.any((n) => n == 3)); // true
}

asMapメソッドを使うとリストをマップに変換できる、インデックスがキーとなる
void main() {
final list = [1, 2, 3];
print(list.asMap()); // {0: 1, 1: 2, 2: 3}
}

cast<R>メソッドを使うとList<R>にキャストできる、要素がRのインスタンスである必要がある
例えば下記のようなのはダメ
void main() {
final list = [1, 2, 3];
print(list.cast<String>()); // Uncaught Error: TypeError: 1: type 'JSInt' is not a subtype of type 'String'
}

clearメソッドを使うとリストを空にできる
void main() {
final list = [1, 2, 3];
list.clear();
print(list); // []
}

containsメソッドを使うとある要素が含まれるかをチェックできる
void main() {
final list = [1, 2, 3];
print(list.contains(2)); // true
}

elementAtを使うとn番目の要素を取得できる、nは0以上でリストの長さより小さい必要がある
void main() {
final list = [1, 2, 3];
print(list.elementAt(2)); // 3
}

everyメソッドを使うとリスト内の全ての要素が指定の条件を満たすか否かをチェックできる
void main() {
final list = [1, 2, 3];
print(list.every((n) => 1 <= n && n <= 3)); // true
}

expandメソッドを使うとコレクションにMapしたものをJoinできるイメージ
void main() {
final list = [1, 2, 3];
print(list.expand((n) => [n, n])); // (1, 1, 2, 2, 3, 3)
}
どういうときに使うんだろう

fillRange
開始位置から終了位置までに指定値を代入できる
void main() {
final list = List.filled(5, 0);
list.fillRange(1, 4, 1);
print(list); // [0, 1, 1, 1, 0]
}
リストがNullableの場合は第3引数を省略可能、その場合はnullとなる

firstWhere
条件を満たす最初の要素を取得できる
void main() {
final list = [1, 2, 3, 4, 5];
final first = list.firstWhere((n) {
return n >= 3 && n % 2 == 0;
});
print(first); // 4
}
すべてが条件を満たさない場合はIterableElementError.noElement
例外が発生する
第2引数のオプションでorElseを指定することでデフォルト値を指定できる
void main() {
final list = [1, 2, 3, 4, 5];
final first = list.firstWhere((n) {
return n >= 5 && n % 2 == 0;
});
print(first); // Uncaught Error: Bad state: No element
}
void main() {
final list = [1, 2, 3, 4, 5];
final first = list.firstWhere(
(n) {
return n >= 5 && n % 2 == 0;
},
orElse: () => -1,
);
print(first); // -1
}

fold
JavaScriptでいうところのreduce、左畳み込み
void main() {
final list = [1, 2, 3, 4, 5];
final first = list.fold(0, (memo, n) => memo + n);
print(first); // 15
}
ちなみに右畳み込みもあるらしい
実装を見ると無限長のリストとかは扱えなそう
S foldRight<S>(S initial, ReversedAccumulate<S, E> f) {
var acc = initial;
asList().reversed.forEach((e) => acc = f(e, acc));
return acc;
}

followedBy
リストの末尾に指定のイテラブルを連結したイテラブルを作成する
void main() {
final list = [1, 2];
print(list.followedBy([3, 4])); // (1, 2, 3, 4)
}

forEach
リストを走査する
ソースコード
void main() {
final list = [1, 2, 3];
list.forEach((n) => print(n));
}
実行結果
1
2
3

getRange
リスト内の指定範囲からイテラブルを作成する
void main() {
final list = [1, 2, 3, 4, 5];
print(list.getRange(1, 4)); // (2, 3, 4)
}

indexOf
指定値が最初に出現するインデックスを返す、無ければ-1を返す
void main() {
final list = [1, 2, 3, 4, 5];
print(list.indexOf(3)); // 2
print(list.indexOf(6)); // -1
}
第2引数として開始位置を指定できる

indexWhere
指定の条件を満たす最初の要素のインデックスと返す、無ければ-1を返す
void main() {
final list = [1, 2, 3, 4, 5];
print(list.indexWhere((n) => n == 3)); // 2
print(list.indexWhere((n) => n >= 6)); // -1
}
第2引数として開始位置を指定できる

insert
指定位置に要素を挿入する
void main() {
final list = [1, 3];
list.insert(1, 2);
print(list); // [1, 2, 3]
}
指定位置が不正だとRangeError例外が発生する
void main() {
final list = [1, 3];
list.insert(100, 2);
print(list); // Uncaught Error: RangeError: Value not in range: 100
}

insertAll
指定位置にイテラブルを挿入する
void main() {
final list = [1, 4];
list.insertAll(1, [2, 3]);
print(list); // [1, 2, 3, 4]
}

join
JavaScriptのjoinと同様、指定文字列で区切って要素同士を連結する
void main() {
final list = [1, 2, 3];
print(list.join(",")); // 1,2,3
}
区切り文字列に何も指定しない場合は空文字列""
が指定されたものとみなされる

lastIndexOf, lastIndexWhere, lastWhere
それぞれindexOf, indexWhere, firstWhereの末尾からバージョンなので割愛
第2引数は末尾からのインデックスではなく、先頭からのインデックスなので注意

map
要素に指定関数を適用して新しいイテラブルを作成する
void main() {
final list = [1, 2, 3];
print(list.map((n) => n * n)); // (1, 4, 9)
}

reduce
左畳み込み、foldと似ているが少し違う
- 初期値が最初の要素
- 初期値がスキップされる
void main() {
final list = [1, 2, 3, 4, 5];
print(list.reduce((memo, n) => memo + n)); // 15
}
リファレンスの下記の下記の擬似コードがわかりやすい
E value = iterable.first;
iterable.skip(1).forEach((element) {
value = combine(value, element);
});
return value;

remove系
下記5点のメソッド、すべて可変長かつ非空のリストである必要がある
- remove:指定値と同じである最初の要素を取り除く、成功の場合はtrueを返す
- removeAt:指定位置の要素を取り除く、取り除いた要素を返す
- removeLast:末尾の要素を取り除く、取り除いた要素を返す
- removeRange:指定範囲のすべての要素を取り除く、何も返さない
- removeWhere:指定の条件を満たすすべての要素を取り除く、何も返さない
void main() {
final list = [1, 2, 3, 4, 5];
print(list.remove(1)); // true
print(list); // [2, 3, 4, 5]
print(list.removeAt(0)); // 2
print(list); // [3, 4, 5]
print(list.removeLast()); // 5
print(list); // [3, 4]
list.removeRange(0, 1);
print(list); // [4]
list.removeWhere((n) => n == 4);
print(list); // []
}

replaceRange
指定範囲の要素を取り除いてからイテラブルを挿入する
void main() {
final list = [1, 3, 5];
list.replaceRange(1, 2, [2, 3, 4]);
print(list); // [1, 2, 3, 4, 5]
}
下記と同様だがより効率的らしい
list.removeRange(start, end);
list.insertAll(start, replacements);

retainWhere
指定の条件を満たす要素以外を取り除く、removeWhereの反対
void main() {
final list = [1, 2, 3, 4];
list.retainWhere((n) => n % 2 == 1);
print(list); // [1, 3]
}
ちなみにremoveWhereの場合
void main() {
final list = [1, 2, 3, 4];
list.removeWhere((n) => n % 2 == 1);
print(list); // 2, 4
}

setAll
指定位置から要素を上書きしていく
void main() {
final list = [1, 2, 3, 4];
list.setAll(1, [3, 5, 7]);
print(list); // [1, 3, 5, 7]
}
第2引数のイテラブルの要素数とリストの要素数の大小関係に注意する必要がある
void main() {
final list = [1, 2, 3, 4];
list.setAll(1, [3, 5, 7, 9]);
print(list); // Uncaught Error: RangeError (index): Index out of range: index should be less than 4: 4
}

setRange
指定範囲の要素を上書きする
void main() {
final list = [1, 2, 3, 4];
list.setRange(1, 4, [3, 5, 7, 9]);
print(list); // [1, 3, 5, 7]
}
イテラブルの数がリストの要素数をオーバーしても大丈夫、でも少ないのはダメ
void main() {
final list = [1, 2, 3, 4];
list.setRange(1, 4, [3, 5]);
print(list); // Uncaught Error: Bad state: Too few elements
}
ちなみに第3引数でイテラブルの開始位置を指定できる

shuffle
リストの要素をランダムに並べ替える
void main() {
final list = [1, 2, 3, 4];
list.shuffle();
print(list); // [2, 4, 1, 3]
}

singleWhere
条件を満たす最初の要素を返す、無ければ例外が発生するかorElse
オプションが呼び出される
void main() {
final list = [1, 2, 3, 4];
final first = list.singleWhere((n) => n >= 4);
print(first); // 4
}
firstWhereに似ているが複数の要素が条件を満たすと例外が発生する
void main() {
final list = [1, 2, 3, 4];
final first = list.singleWhere((n) => n >= 3);
print(first); // Uncaught Error: Bad state: Too many elements
}

skip
指定数の要素をスキップしてイテラブルを作成する
void main() {
final list = [1, 2, 3, 4];
print(list.skip(2)); // (3, 4)
}
takeと組み合わせるとJavaScriptのsliceが実現できそう

skipWhile
指定の条件が満たされなくなるまでスキップしてイテラブルを作成する
void main() {
final list = [1, 2, 3, 4];
print(list.skipWhile((n) => n <= 2)); // (3, 4)
}

sort
リスト内の要素を指定の条件で並べ替える
void main() {
final list = [1, 2, 3, 4];
list.sort((a, b) => b - a);
print(list); // [4, 3, 2, 1]
}
BigInt, DateTime, Duration, num, StringなどComperableの場合は条件を省略可能
void main() {
final list = [1, 3, 2, 4];
list.sort();
print(list); // [1, 2, 3, 4]
}

sublist
なんとJavaScriptのsliceのようなものがあるではないか
void main() {
final list = [1, 2, 3, 4];
print(list.sublist(1, 3)); // [2, 3]
}

take
先頭から指定した数の要素を取ってイテラブルを作成する
void main() {
final list = [1, 2, 3, 4];
print(list.take(2)); // (1, 2)
}

takeWhile
先頭から指定した条件を満たす限り要素を取ってイテラブルを作成する
void main() {
final list = [1, 2, 3, 4];
print(list.takeWhile((n) => n <= 2)); // (1, 2)
}

toList
リストに変換する、同じ内容の新しいリストを作成する時などに便利そう
void main() {
final list = [1, 2, 3, 4];
print(list.toList()); // [1, 2, 3, 4]
}
第1引数growableがtrueならリストは可変長となる、デフォルトではtrue

toSet
集合に変換する
void main() {
final list = [1, 2, 3, 4];
print(list.toSet()); // {1, 2, 3, 4}
}

toString
文字列に変換する
void main() {
final list = [1, 2, 3, 4];
print(list.toString()); // [1, 2, 3, 4]
}

where
指定の条件を満たす全ての要素をイテラブルとして返す
void main() {
final list = [1, 2, 3, 4];
print(list.where((n) => n % 2 == 0)); // (2, 4)
}
retainWhereと似ているが戻り値がvoidではない点が異なる

whereType
指定の型を持つ全ての要素をイテラブルとして返す
void main() {
final list = [1, "2", 3, "4"];
print(list.whereType<String>()); // (2, 4)
}

operator +
リストを連結する
void main() {
final list = [1, 2] + [3, 4];
print(list); // [1, 2, 3, 4]
}

operator ==
同じリストかどうかをする、同じ要素を含んでいても同一とは見なされない
void main() {
final list1 = [1, 2];
final list2 = [1, 2];
final list3 = const [1, 2];
final list4 = const [1, 2];
print(list1 == list2); // false
print(list3 == list4); // true
}

operator []
指定インデックスの要素を取得する、elementAtと同じ気がする
void main() {
final list = [1, 2];
print(list[0]); // 1
}

operator []=
指定インデックスの要素に代入する
void main() {
final list = [1, 2];
list[1] = 3;
print(list); // [1, 3]
}

List.castFrom<S, T>
指定リストの要素の型をSからTにキャストする、T is-a Sの関係が必要
void main() {
final list = [1, 2];
print(List.castFrom<num, String>(list)); // Uncaught Error: TypeError: 1: type 'JSInt' is not a subtype of type 'String'
}

List.copyRange
リストの指定位置に別のリストの要素をコピーする
void main() {
final list = [1, 2, 3, 4, 5];
List.copyRange(list, 1, [-1, -2, -3]);
print(list); // [1, -1, -2, -3, 5]
}
第4引数と第5引数とコピーする範囲を指定できる

List.writeIterable
リストの指定位置にイテラブルの要素をコピーする
void main() {
final list = [1, 2, 3, 4, 5];
List.writeIterable(list, 1, {-1, -2, -3});
print(list); // [1, -1, -2, -3, 5]
}
第2引数がリストの場合はList.copyRangeを使った方が効率的

以上をもって一旦クローズとする
次はMapについて調べよう