Chapter 09

  List操作とMap操作

heyhey1028
heyhey1028
2023.02.20に更新

この章では Flutter 開発でもよく使うことになる List と Map を操作するメソッドについて学んでいきます。

List/Map 操作のメソッドは数が多いので、よく使われる代表的なメソッドをピックアップしてご紹介していきます。それでも数が非常に多いので、サラッと読み流し、実際に使う時にまた戻ってくるという感じで読んでみていただければと思います。

List 操作

List 操作については追加、削除、判定、検索、並べ替え、展開、変換、合体、コピーといった操作のメソッドを紹介していきます。

より詳しく学びたい方はこちらを参考にしてみてください
https://api.flutter.dev/flutter/dart-core/List-class.html#instance-methods

追加する

add

単一の要素を配列の最後尾に追加します

void main(){
  // Create a list
  List<String> colors = ["red", "green"];

  // Add a new item to the list
  colors.add("blue");

  // The list now contains three elements
  print(colors); // ["red", "green", "blue"]
}

addAll

同じ型を格納する配列の中身を全て配列の最後尾に追加します

void main(){
  // Create a list
  List<String> colors = ["red", "green"];

  // Create another list
  List<String> moreColors = ["yellow", "orange"];

  // Add the items from the second list to the first list
  colors.addAll(moreColors);

  // The list now contains four elements
  print(colors); // ["red", "green", "yellow", "orange"]
}

削除する

remove

指定した要素を配列から削除します

void main(){
  List<String> myList = ['a', 'b', 'c'];
  myList.remove('b');

  print(myList); // output: [a, c]
}

removeWhere

removeWhereに渡された条件式に合致する要素を配列を削除します

void main(){
  List<int> myList = [1,300,345,501,1000];
  // ex. 500よりも小さい要素を削除
  myList.removeWhere((int el)=> el < 500);

  print(myList); // output: [501, 1000]
}

展開して判定する

contains

配列内に指定の値を持つ要素が存在するかを判定します

void main() {
   List<int> list = [1, 2, 3, 4, 5];

   print(list.contains(3));  // true
   print(list.contains(6));  // false
}

any

any()に渡された条件に合致する要素が存在するかを判定します

void main() {
   List<int> list = [1, 2, 3, 4, 5];

   print(list.any((element) => element > 4));  // true
   print(list.any((element) => element < 0));  // false
}

every

every()に渡された条件に全ての要素が合致するかを判定します

void main() {
   List<int> list = [1, 2, 3, 4, 5];

   print(list.every((element) => element < 6)); // true
   print(list.every((element) => element > 1)); // false
}

検索する

where

where()に渡された条件に合致する要素を抽出し、後述のtoList()と併せて使うことで新しい配列を返します。条件に合致する要素がない場合は空の配列が生成されます。

void main(){
  final List<int> list = [1, 2, 3, 4, 5];
  final List<int> filteredList = list.where((i) => i >= 3).toList();
  print(filteredList); // [3, 4, 5]
}

firstWhere

firstWhere()に渡された条件に合致する最初の要素を返します。orElseには条件に合致する要素がない場合の処理を記述します。

void main(){
  final List<int> list = [1, 2, 3, 4, 5];
  int firstFilteredItem = list.firstWhere((i) => i >= 3, orElse: () => -1);
  print(firstFilteredItem); // 3

  int secondFilteredItem = list.firstWhere((i) => i >100, orElse:()=> -1);
  print(secondFilteredItem); // -1
}

sublist

指定した範囲の要素を返します。第一引数だけ指定した場合はその要素以降の要素を全て、第二引数まで指定した場合は第一引数の要素から第二引数の要素の1つ前までを全て返します。

void main(){
  List<String> list = ['monkey', 'elephant', 'tiger','dragon','whale'];
  List<String> subList = list.sublist(1);
  print(subList);
  // output: [elephant, tiger, dragon, whale]

  List<String> subList2 = list.sublist(1,3);
  print(subList2);
  // output: [elephant, tiger]
}

インデックスを検索する

indexOf

指定した要素のインデックス番号を返します。もし要素が存在しない場合は-1 が返ってきます。

void main(){
  var list = [1, 2, 3];

  var indexOf2 = list.indexOf(2);
  print(indexOf2); // 1

  final int indexOf4 = list.indexOf(4);
  print(indexOf4); // -1
}

indexWhere

indexWhere()に渡された条件に合致する最初の要素のインデックスを返します。条件に合致する要素が複数あっても返ってくるのは最初の要素のインデックスだけです。

また条件に合致する要素がなければ-1 が返ってきます。

void main(){
  var list = [1, 2, 3];

  var indexWhere2 = list.indexWhere((element) => element == 3);
  print(indexWhere2); // 2

  final int indexOver1 = list.indexWhere((element)=>element >1);
  print(indexOver1); // 1

  final int indexWhere4 = list.indexWhere((element)=>element == 4);
  print(indexWhere4); // -1
}

並べ替える

sort

sort()に渡した条件で配列を並び替えます。コールバックで(最初の要素、次の要素)の値を受け取るので、それらの比較条件を記述し、インデックスを後ろにズラす時は1を、前にズラす時は-1、動かさない時は0を返す事で配列を並び替えます。

同様の処理はcompareTo関数をを使う事でも実現できるのでそちらを使った方がスマートに記述できます。

また条件を渡さずに実行すると昇順に要素を並び替えます。

void main() {
  List numbers = [3, 2, 4, 7, 5, 6, 1];

  // ex.大きい順に並べる(降順)
  numbers.sort((a, b){
    if (a < b) return 1;
    if (a > b) return -1;
    return 0;
  });
  print(numbers); // [7, 6, 5, 4, 3, 2, 1]
  // compareToを使う
  numbers.sort((a,b)=>b.compareTo(a));
  print(numbers);// [7, 6, 5, 4, 3, 2, 1]

  // ex.小さい順に並べる(昇順)
  numbers.sort();
  print(numbers); // [1, 2, 3, 4, 5, 6, 7]
}

shuffle

配列をランダムに並べ替えます

void main() {
  List numbers = [3, 2, 4, 7, 5, 6, 1];

  numbers.shuffle();
  print(numbers); // [4, 2, 7, 6, 1, 5, 3](ランダムの為、出力結果は毎回異なります)
}

展開してコレクションにする

map+toList

配列をイテレータ型という順々に値を返す型に変換します。単体で用いる事は少なく、後述するtoListと一緒に用いることが多いです。

mapを使うことで配列内の要素を1つ1つ取り出し、何かしらの処理を施すことができます。

void main(){
  // list.mapのサンプルコード
  List<int> nums = [1,2,3,4,5];
  List<int> squares = nums.map((n) => n * n).toList();
  print(squares); // [1, 4, 9, 16, 25]
}

expand+toList

expandmapに似ていますが、配列の値を再起的に展開し、何かしらの処理を施し、再度コレクションに変換して返します。

特に有効なのが二次元配列などネストした配列構造を1つの配列にしたい様な場合です。

javascriptが分かる方はArray.flat()をイメージして頂ければ良いと思います。

void main() {
  var a = [
    [1, 2, 3],
    ['a', 'b', 'c'],
    [true, false, true]
  ];

  // ネストした配列の要素を全て展開して、単一の配列に格納
  var flat = a.expand((i) => i).toList();
  print(flat);
  // [1, 2, 3, a, b, c, true, false, true]

  // mapでは再起処理されず、配列のまま配列に格納してしまう
  var flat2 = a.map((i)=>i).toList();
  print(flat2);
  // [[1, 2, 3], [a, b, c], [true, false, true]]
}

変換する

asMap

配列の各要素にキーとしてインデックスをふり、Mapに変換します

void main() {
  List<String> list = ['a', 'b', 'c'];

  Map<int, String> myMap = list.asMap();
  print(myMap); // {0: a, 1: b, 2: c}
}

toList

単体で使う場合は配列を配列に変換することになる為、あまり用いず、前述のwheremapexpandと併用して使う事が多いです。

Setを配列に変換することもできます。(厳密には Set が持つtoListメソッドですが)

void main(){
  Set<String> mySet = {'a','b','c'};
  print(mySet); // {a, b, c}

  List<String> myList = mySet.toList();
  print(myList); // [a, b, c]
}

toSet

配列を重複不可なSetに変換します

void main() {
  List<int> list = [1, 4, 5, 24, 4, 24];

  final Set<int> mySet = list.toSet();
  print(mySet); // {1, 4, 5, 24}
}

合体させる

join

String型の配列の値を引数に渡した文字列で繋いだ1つの文字列にして返します。String型の配列に対してのみ使用できます。

void main() {
  List<String> list = ['Hello', 'World', '!'];

  String result = list.join(',');

  print(result); // Hello,World,!
}

reduce

配列の要素を1つの値に集約します。(現在の集約値,要素のvalue){処理}のコールバック関数を渡します。

void main(){
  List<String> list = ['Hello', 'World', '!'];

  String result = list.reduce((substr, element) {
  return substr + element;
  });

  print(result); // HelloWorld!
}

fold

reduce同様に配列の要素を1つの値に集約します。第一引数に初期値、第二引数では(現在の集約値,要素のvalue){処理}のコールバック関数を渡します。

reduceと非常によく似ていますが、2つの特徴があります

  1. 初期値を設定する事ができる
  2. 元の要素の型とは別の型に変換する事ができる

特に2は場合によっては非常に便利です。

main(){
    List<String> list1 = ['Hello', 'World', '!'];
    String result = list1.fold('', (substr, element) => substr + element);
    print(result); // HelloWorld!

    // ex.文字列の配列から合計の文字数を返します
    List<String> list2 = ['apple', 'orange', 'banana'];
    final int totalLength = list2.fold(0, (int value, String element) => value + element.length);
    print(totalLength); //17
}

コピーする

from

既存の配列を中身そのままでコピーします

void main(){
  List<String> list = ['A', 'B', 'C'];
  print(list); // [A, B, C]

  List<String> newList = List.from(list);
  print(newList); // [A, B, C]
}

Map 操作

Map 操作については追加、削除、更新、検索、展開のメソッドを紹介していきます。

こちらもより詳しく学びたい方は以下を参照してみてください。
https://api.dart.dev/stable/2.18.6/dart-core/Map-class.html

追加する

addAll

指定した連想配列を最後尾に追加します。既に存在しているキーを代入した場合、その値が上書きされます。

void main() {
  Map map1 = {
    'key1': 'value1',
    'key2': 'value2',
  };

  Map map2 = {
    'key2': 'new value2',
    'key3': 'value3',
  };

  map1.addAll(map2);

  print(map1);
  // {key1: value1, key2: new value2, key3: value3}
}

addEntries

addAllが連想配列に連想配列を追加するのに対して、addEntriesは連想配列の要素を追加します。

連想配列の各要素はMapEntryというオブジェクトになっており、このMapEntryの配列を渡すことで連想配列に要素を追加します。

Map.entriesで Map の要素を取得する事も可能です。

またaddAllと同様に既に存在しているキーを代入した場合、その値が上書きされます。

void main() {
  final Map<String, String> map1 = {
    'key1': 'value1',
    'key2': 'value2',
  };

  final Map<String, String> map2 = {
    'key3': 'value3',
    'key4': 'value4',
  };

  map1.addEntries(map2.entries); // Mapの要素を取得
  print(map1);
  // {key1: value1, key2: value2, key3: value3, key4: value4}

  map1.addEntries([
    MapEntry('key5', 'value5'),
    MapEntry('key6', 'value6'),
  ]);
  print(map1);
  // {key1: value1, key2: value2, key3: value3, key4: value4, key5: value5, key6: value6}
}

削除する

remove

指定したキーを持つ要素を連想配列から削除します

void main() {
  final Map<String, int> myMap = {'JA': 81, 'GB': 44, 'US': 1, 'CH':44};

  myMap.remove('GB');
  print(myMap); // {JA: 81, US: 1, CH: 44}
}

removeWhere

指定した条件に合致する要素を全て連想配列から削除します。コールバック関数としてkey,valueどちらも受け取る為、どちらの値も条件判定に使う事ができます。

void main() {
  final Map<String, int> myMap = {'JA': 81, 'GB': 44, 'US': 1, 'AU': 1};

  myMap.removeWhere((key, value) => value == 1);
  print(myMap); // {JA: 81, GB: 44}
}

更新する

update

指定したキーのバリューに任意の処理を施して更新する事ができます。
指定したキーが存在しない場合、ifAbsentで返す値をバリューとして、要素を追加します。

void main() {
  Map<String, int> scores = {
    'Bob': 35,
    'Alice': 42
  };

  scores.update('Bob', (value) => value + 5);
  print(scores);  // {Bob: 40, Alice: 42}

  scores.update('John', (value) => value + 5, ifAbsent:()=> 0);
  print(scores);  // {Bob: 40, Alice: 42, John: 0}
}

updateAll

連想配列内の全ての要素に対して、任意の処理を施し更新します。

void main() {
  Map<String, int> scores = {
    'Bob': 35,
    'Alice': 42
  };

  scores.updateAll((key, value) => value + 10);
  print(scores);  // {Bob: 45, Alice: 52}
}

検索する

containsKey

指定したキーの要素が存在するかを判定します

void main() {
  Map<String, int> students = {
    'John': 12,
    'Bob': 13
  };

  print(students.containsKey('John')); // true
  print(students.containsKey('Jake')); // false
}

containsValue

指定したバリューの要素が存在するかを判定します

void main() {
  Map<String, int> students = {
    'John': 12,
    'Bob': 13
  };

  print(students.containsValue(13)); // true
  print(students.containsValue(15)); // false
}

展開する

map

連想配列の各要素のキーとバリューに対して指定の処理を施した連想配列を返します。処理を施した要素を返す必要がある為、変更を加えたMapEntryを返す必要があります。

void main(){
  Map<String, int> numbers = {
    'one': 1,
    'two': 2,
    'three': 3,
  };

  Map<String, int> numbersPlusOne = numbers.map((key, value) => MapEntry(key, value + 1));
  print(numbersPlusOne); // {one: 2, two: 3, three: 4}
}

forEach

連想配列の各要素を取得し指定の処理を実行する事ができます。mapのように値を返すわけではないので、キーとバリューを用いたなんらかの処理を行うのみです。for inと同等の動きになります。

void main(){
  Map<String, int> numbers = {
    'one': 1,
    'two': 2,
    'three': 3,
  };

  numbers.forEach((key, value) {
    print('key: $key, value: $value');
  });
  // key: one, value: 1
  // key: two, value: 2
  // key: three, value: 3
}

操作を組み合わせる

上記の操作関数は1つのコレクションに対し、複数繋げて実行する事ができます。

expandwhereなどコレクションを返す関数はいくつでも繋げることができますが、toListfoldなど1つの値もしくはオブジェクトを確定する処理は最後尾に一度しか実行する事ができません。

複数繋げることが可能
whereexpandmapなど

一度だけ実行可能
toListtoSetincludeseveryasMapreduceなど

void main() {
  final List<int> myList = [9, 8, 39, 0, 15, 62, 90, 18, 70, 66];

  final List<int> updatedList =
      myList
      .where((element) => 10 < element)
      .map(((e) => e * e))
      .toList();

  print(updatedList); // [1521, 225, 3844, 8100, 324, 4900, 4356]
}

まとめ

非常に多くの操作関数が用意されているので、眩暈がしてしまうかもしれませんが、頭の片隅に置いておいて、いざ操作が必要になった時に、そんな関数があったなと使い方を調べ直す、くらい気持ちで臨みましょう。

次章から、いよいよ Flutter へ 🚀

以上で簡単ではありますがDartの基礎について学んでいきました。次の章からはいよいよFlutterの解説に入っていきます!!