第3章 コレクション

List(リスト)

Listは順序付けられた要素の集合です。同じ要素を複数回含むことができ、インデックスでアクセスできます。

Listの基本操作

void main() {
  // Listの作成
  List<int> numbers = [1, 2, 3, 4, 5];
  var fruits = ['りんご', 'バナナ', 'オレンジ'];  // 型推論
  
  // 空のList
  List<String> emptyList = [];
  var anotherEmpty = <String>[];
  
  // 要素へのアクセス
  print('最初の要素: ${fruits[0]}');        // りんご
  print('最後の要素: ${fruits[fruits.length - 1]}');  // オレンジ
  print('最後の要素: ${fruits.last}');      // オレンジ
  print('最初の要素: ${fruits.first}');     // りんご
  
  // 要素数
  print('要素数: ${fruits.length}');
  
  // 空かどうか
  print('空?: ${fruits.isEmpty}');         // false
  print('空でない?: ${fruits.isNotEmpty}'); // true
}

Listへの要素の追加・削除

void main() {
  var fruits = ['りんご', 'バナナ'];
  
  // 末尾に追加
  fruits.add('オレンジ');
  print(fruits);  // [りんご, バナナ, オレンジ]
  
  // 複数要素を追加
  fruits.addAll(['ぶどう', 'いちご']);
  print(fruits);  // [りんご, バナナ, オレンジ, ぶどう, いちご]
  
  // 指定位置に挿入
  fruits.insert(1, 'メロン');
  print(fruits);  // [りんご, メロン, バナナ, オレンジ, ぶどう, いちご]
  
  // 指定位置に複数挿入
  fruits.insertAll(2, ['桃', '梨']);
  print(fruits);  // [りんご, メロン, 桃, 梨, バナナ, オレンジ, ぶどう, いちご]
  
  // 要素の削除
  fruits.remove('バナナ');  // 最初に見つかった要素を削除
  print(fruits);
  
  // インデックスで削除
  fruits.removeAt(0);  // りんごを削除
  print(fruits);
  
  // 最後の要素を削除
  fruits.removeLast();
  print(fruits);
  
  // 条件に合う要素を削除
  fruits.removeWhere((fruit) => fruit.length > 3);
  print(fruits);
  
  // すべて削除
  fruits.clear();
  print('クリア後: $fruits');  // []
}

Listの操作

void main() {
  var numbers = [5, 2, 8, 1, 9, 3];
  
  // ソート
  numbers.sort();
  print('昇順: $numbers');  // [1, 2, 3, 5, 8, 9]
  
  numbers.sort((a, b) => b.compareTo(a));  // 降順
  print('降順: $numbers');  // [9, 8, 5, 3, 2, 1]
  
  // 反転
  var reversed = numbers.reversed.toList();
  print('反転: $reversed');
  
  // 部分リスト
  var sublist = numbers.sublist(1, 4);  // インデックス1から3まで
  print('部分リスト: $sublist');
  
  // 要素の検索
  print('5を含む?: ${numbers.contains(5)}');
  print('5のインデックス: ${numbers.indexOf(5)}');
  print('10のインデックス: ${numbers.indexOf(10)}');  // -1(見つからない)
  
  // リストの結合
  var list1 = [1, 2, 3];
  var list2 = [4, 5, 6];
  var combined = [...list1, ...list2];
  print('結合: $combined');
  
  // 範囲指定
  var range = List.generate(5, (index) => index * 2);
  print('生成: $range');  // [0, 2, 4, 6, 8]
}

固定長Listと成長可能なList

void main() {
  // 成長可能なList(デフォルト)
  var growable = [1, 2, 3];
  growable.add(4);  // OK
  print('成長可能: $growable');
  
  // 固定長List
  var fixed = List.filled(3, 0);  // [0, 0, 0]
  print('固定長: $fixed');
  fixed[0] = 1;  // 要素の変更はOK
  // fixed.add(4);  // エラー: 固定長Listには追加できない
  
  // 固定長Listを成長可能に変換
  var converted = fixed.toList(growable: true);
  converted.add(4);  // OK
  print('変換後: $converted');
  
  // 変更不可能なList
  var unmodifiable = List.unmodifiable([1, 2, 3]);
  // unmodifiable[0] = 5;  // エラー: 変更不可
  // unmodifiable.add(4);   // エラー: 追加不可
}

Set(セット)

Setは順序を持たない、重複のない要素の集合です。

Setの基本操作

void main() {
  // Setの作成
  Set<int> numbers = {1, 2, 3, 4, 5};
  var fruits = {'りんご', 'バナナ', 'オレンジ'};
  
  // 重複は自動的に削除される
  var duplicates = {1, 2, 2, 3, 3, 3};
  print('重複削除: $duplicates');  // {1, 2, 3}
  
  // 空のSet
  Set<String> emptySet = {};
  var anotherEmpty = <String>{};
  
  // 要素数
  print('要素数: ${fruits.length}');
  
  // 要素の存在チェック
  print('りんごを含む?: ${fruits.contains('りんご')}');
  
  // すべての要素
  print('すべての要素: $fruits');
}

Setへの要素の追加・削除

void main() {
  var fruits = {'りんご', 'バナナ'};
  
  // 要素の追加
  fruits.add('オレンジ');
  print(fruits);
  
  // 重複した要素を追加しようとしても無視される
  var added = fruits.add('りんご');
  print('追加成功?: $added');  // false
  print(fruits);  // 変化なし
  
  // 複数要素を追加
  fruits.addAll(['ぶどう', 'いちご', 'りんご']);
  print(fruits);  // りんごは重複しないので追加されない
  
  // 要素の削除
  fruits.remove('バナナ');
  print(fruits);
  
  // 条件に合う要素を削除
  fruits.removeWhere((fruit) => fruit.length > 4);
  print(fruits);
  
  // すべて削除
  fruits.clear();
  print('クリア後: $fruits');
}

Set演算

void main() {
  var set1 = {1, 2, 3, 4, 5};
  var set2 = {4, 5, 6, 7, 8};
  
  // 和集合(union)
  var union = set1.union(set2);
  print('和集合: $union');  // {1, 2, 3, 4, 5, 6, 7, 8}
  
  // 積集合(intersection)
  var intersection = set1.intersection(set2);
  print('積集合: $intersection');  // {4, 5}
  
  // 差集合(difference)
  var difference = set1.difference(set2);
  print('差集合: $difference');  // {1, 2, 3}
  
  // 部分集合かどうか
  var subset = {1, 2};
  print('部分集合?: ${subset.difference(set1).isEmpty}');  // true
  
  // Listから重複を削除
  var listWithDuplicates = [1, 2, 2, 3, 3, 3, 4, 5, 5];
  var uniqueSet = listWithDuplicates.toSet();
  print('重複削除: $uniqueSet');  // {1, 2, 3, 4, 5}
  
  // SetをListに変換
  var backToList = uniqueSet.toList();
  print('List化: $backToList');
}

Map(マップ)

Mapはキーと値のペアを格納するコレクションです。他の言語では辞書(Dictionary)やハッシュマップと呼ばれます。

Mapの基本操作

void main() {
  // Mapの作成
  Map<String, int> scores = {
    '太郎': 85,
    '花子': 92,
    '次郎': 78
  };
  
  // 型推論
  var capitals = {
    '日本': '東京',
    'アメリカ': 'ワシントンD.C.',
    'フランス': 'パリ'
  };
  
  // 空のMap
  Map<String, String> emptyMap = {};
  var anotherEmpty = <String, int>{};
  
  // 値へのアクセス
  print('太郎の点数: ${scores['太郎']}');
  print('日本の首都: ${capitals['日本']}');
  
  // 存在しないキーはnullを返す
  print('三郎の点数: ${scores['三郎']}');  // null
  
  // キーの存在チェック
  print('太郎が存在?: ${scores.containsKey('太郎')}');
  print('値92が存在?: ${scores.containsValue(92)}');
  
  // 要素数
  print('要素数: ${scores.length}');
  
  // すべてのキー
  print('キー: ${scores.keys}');
  
  // すべての値
  print('値: ${scores.values}');
}

Mapへの要素の追加・更新・削除

void main() {
  var scores = {'太郎': 85, '花子': 92};
  
  // 要素の追加
  scores['次郎'] = 78;
  print(scores);
  
  // 要素の更新
  scores['太郎'] = 90;
  print(scores);
  
  // 複数要素を追加
  scores.addAll({
    '四郎': 88,
    '五郎': 95
  });
  print(scores);
  
  // キーが存在しない場合のみ追加
  scores.putIfAbsent('太郎', () => 100);  // 既に存在するので追加されない
  scores.putIfAbsent('六郎', () => 80);   // 追加される
  print(scores);
  
  // 要素の削除
  scores.remove('次郎');
  print(scores);
  
  // 条件に合う要素を削除
  scores.removeWhere((key, value) => value < 85);
  print(scores);
  
  // すべて削除
  scores.clear();
  print('クリア後: $scores');
}

Mapの操作

void main() {
  var scores = {
    '太郎': 85,
    '花子': 92,
    '次郎': 78,
    '四郎': 95
  };
  
  // すべてのエントリを処理
  scores.forEach((name, score) {
    print('$name: $score点');
  });
  
  // キーでソート
  var sortedKeys = scores.keys.toList()..sort();
  print('\nキーでソート:');
  for (var key in sortedKeys) {
    print('$key: ${scores[key]}');
  }
  
  // 値でソート
  var sortedEntries = scores.entries.toList()
    ..sort((a, b) => b.value.compareTo(a.value));
  print('\n値でソート(降順):');
  for (var entry in sortedEntries) {
    print('${entry.key}: ${entry.value}');
  }
  
  // デフォルト値を使ったアクセス
  print('\n五郎の点数: ${scores['五郎'] ?? 0}');  // 存在しないので0
  
  // Mapの変換
  var doubled = scores.map((key, value) => MapEntry(key, value * 2));
  print('\n2倍: $doubled');
}

コレクション操作とメソッド

関数型メソッド

void main() {
  var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
  
  // map - 各要素を変換
  var squared = numbers.map((n) => n * n).toList();
  print('2乗: $squared');
  
  // where - 条件に合う要素をフィルタリング
  var evens = numbers.where((n) => n % 2 == 0).toList();
  print('偶数: $evens');
  
  // firstWhere - 条件に合う最初の要素
  var firstEven = numbers.firstWhere((n) => n % 2 == 0);
  print('最初の偶数: $firstEven');
  
  // firstWhere(見つからない場合のデフォルト値)
  var bigNumber = numbers.firstWhere(
    (n) => n > 100,
    orElse: () => -1
  );
  print('100より大きい数: $bigNumber');  // -1
  
  // lastWhere - 条件に合う最後の要素
  var lastEven = numbers.lastWhere((n) => n % 2 == 0);
  print('最後の偶数: $lastEven');
  
  // any - 条件に合う要素が1つでもあるか
  var hasEven = numbers.any((n) => n % 2 == 0);
  print('偶数が含まれる?: $hasEven');
  
  // every - すべての要素が条件に合うか
  var allPositive = numbers.every((n) => n > 0);
  print('すべて正数?: $allPositive');
  
  // skip - 最初のN個をスキップ
  var skipped = numbers.skip(3).toList();
  print('最初の3個をスキップ: $skipped');
  
  // take - 最初のN個を取得
  var taken = numbers.take(5).toList();
  print('最初の5個: $taken');
  
  // reduce - 累積処理
  var sum = numbers.reduce((a, b) => a + b);
  print('合計: $sum');
  
  // fold - 初期値を指定した累積処理
  var product = numbers.fold(1, (prev, element) => prev * element);
  print('積: $product');
}

より高度な操作

void main() {
  var students = [
    {'name': '太郎', 'score': 85},
    {'name': '花子', 'score': 92},
    {'name': '次郎', 'score': 78},
    {'name': '四郎', 'score': 95},
    {'name': '五郎', 'score': 88}
  ];
  
  // 条件に合う要素の数
  var highScorers = students.where((s) => s['score']! as int > 85).length;
  print('85点以上の人数: $highScorers');
  
  // 最大値
  var maxScore = students.map((s) => s['score'] as int).reduce((a, b) => a > b ? a : b);
  print('最高点: $maxScore');
  
  // 平均値
  var total = students.fold(0, (sum, s) => sum + (s['score'] as int));
  var average = total / students.length;
  print('平均点: ${average.toStringAsFixed(1)}');
  
  // グループ化(手動実装)
  var grouped = <String, List<Map<String, Object>>>{};
  for (var student in students) {
    var score = student['score'] as int;
    var grade = score >= 90 ? 'A' : score >= 80 ? 'B' : 'C';
    grouped.putIfAbsent(grade, () => []).add(student);
  }
  print('\n成績別グループ:');
  grouped.forEach((grade, studentList) {
    print('$grade: ${studentList.map((s) => s['name']).join(', ')}');
  });
  
  // 複数の操作を連鎖
  var topThreeNames = students
      .where((s) => s['score']! as int >= 85)
      .map((s) => s['name'] as String)
      .take(3)
      .toList();
  print('\n上位3名: $topThreeNames');
}

コレクションのコピー

void main() {
  var original = [1, 2, 3];
  
  // 浅いコピー
  var shallowCopy = [...original];
  shallowCopy[0] = 999;
  print('元のリスト: $original');       // [1, 2, 3]
  print('コピー: $shallowCopy');        // [999, 2, 3]
  
  // ネストされたリストの場合
  var nested = [[1, 2], [3, 4]];
  var nestedCopy = [...nested];
  nestedCopy[0][0] = 999;  // 内側のリストは共有される
  print('元のネストリスト: $nested');   // [[999, 2], [3, 4]]
  print('コピー: $nestedCopy');          // [[999, 2], [3, 4]]
  
  // 深いコピー(手動)
  var deepCopy = nested.map((list) => [...list]).toList();
  deepCopy[0][0] = 111;
  print('元のネストリスト: $nested');   // [[999, 2], [3, 4]]
  print('深いコピー: $deepCopy');        // [[111, 2], [3, 4]]
}

Spread演算子とCollection if/for

Spread演算子(…)

void main() {
  // リストの展開
  var list1 = [1, 2, 3];
  var list2 = [4, 5, 6];
  var combined = [...list1, ...list2];
  print('結合: $combined');  // [1, 2, 3, 4, 5, 6]
  
  // 途中に要素を挟む
  var mixed = [...list1, 99, ...list2];
  print('混合: $mixed');  // [1, 2, 3, 99, 4, 5, 6]
  
  // nullセーフなSpread演算子(...?)
  List<int>? nullableList;
  var safe = [1, 2, ...?nullableList, 3];
  print('nullセーフ: $safe');  // [1, 2, 3]
  
  nullableList = [10, 20];
  safe = [1, 2, ...?nullableList, 3];
  print('nullセーフ: $safe');  // [1, 2, 10, 20, 3]
  
  // Setでの使用
  var set1 = {1, 2, 3};
  var set2 = {3, 4, 5};
  var combinedSet = {...set1, ...set2};
  print('Set結合: $combinedSet');  // {1, 2, 3, 4, 5}
  
  // Mapでの使用
  var map1 = {'a': 1, 'b': 2};
  var map2 = {'c': 3, 'd': 4};
  var combinedMap = {...map1, ...map2};
  print('Map結合: $combinedMap');
  
  // 値の上書き
  var overwritten = {...map1, 'b': 99, ...map2};
  print('上書き: $overwritten');  // {a: 1, b: 99, c: 3, d: 4}
}

Collection if

void main() {
  bool includeZero = true;
  var numbers = [
    if (includeZero) 0,
    1,
    2,
    3
  ];
  print('0を含む: $numbers');
  
  includeZero = false;
  numbers = [
    if (includeZero) 0,
    1,
    2,
    3
  ];
  print('0を含まない: $numbers');
  
  // if-else
  bool isEven = true;
  var list = [
    1,
    if (isEven) 2 else 3,
    4
  ];
  print('条件分岐: $list');
  
  // 複数要素の条件付き追加
  bool includeExtras = true;
  var items = [
    'りんご',
    'バナナ',
    if (includeExtras) ...[
      'オレンジ',
      'ぶどう',
      'いちご'
    ]
  ];
  print('アイテム: $items');
  
  // Setでの使用
  var roles = {
    'user',
    if (true) 'admin',
    if (false) 'superadmin'
  };
  print('役割: $roles');
  
  // Mapでの使用
  var config = {
    'name': 'MyApp',
    'version': '1.0',
    if (true) 'debug': true,
    if (false) 'test': true
  };
  print('設定: $config');
}

Collection for

void main() {
  // 基本的な使用
  var numbers = [
    for (var i = 1; i <= 5; i++) i
  ];
  print('1-5: $numbers');
  
  // 変換しながら生成
  var squared = [
    for (var i = 1; i <= 5; i++) i * i
  ];
  print('2乗: $squared');
  
  // 既存のリストから生成
  var fruits = ['りんご', 'バナナ', 'オレンジ'];
  var upperFruits = [
    for (var fruit in fruits) fruit.toUpperCase()
  ];
  print('大文字: $upperFruits');
  
  // 条件付きで生成
  var evenNumbers = [
    for (var i = 1; i <= 10; i++)
      if (i % 2 == 0) i
  ];
  print('偶数: $evenNumbers');
  
  // ネストしたループ
  var pairs = [
    for (var i = 1; i <= 3; i++)
      for (var j = 1; j <= 3; j++)
        '($i, $j)'
  ];
  print('ペア: $pairs');
  
  // Setでの使用
  var uniqueSquares = {
    for (var i = 1; i <= 5; i++) i * i
  };
  print('重複なし2乗: $uniqueSquares');
  
  // Mapでの使用
  var numberMap = {
    for (var i = 1; i <= 5; i++) i: i * i
  };
  print('数値マップ: $numberMap');
}

Collection if/forの組み合わせ

void main() {
  // 複雑な条件での生成
  var products = [
    {'name': 'りんご', 'price': 100},
    {'name': 'バナナ', 'price': 150},
    {'name': 'オレンジ', 'price': 200},
  ];
  
  bool showExpensive = true;
  int minPrice = 120;
  
  var displayList = [
    for (var product in products)
      if (!showExpensive || product['price']! as int >= minPrice)
        '${product['name']}: ${product['price']}円'
  ];
  print('表示リスト: $displayList');
  
  // グリッドの生成
  var grid = [
    for (var row = 0; row < 3; row++)
      [
        for (var col = 0; col < 3; col++)
          row * 3 + col
      ]
  ];
  print('グリッド:');
  for (var row in grid) {
    print(row);
  }
  
  // 複雑なMap生成
  var students = ['太郎', '花子', '次郎'];
  var initialScores = {
    for (var i = 0; i < students.length; i++)
      students[i]: i * 10 + 70
  };
  print('初期スコア: $initialScores');
  
  // 条件付きMap生成
  var config = {
    'appName': 'MyApp',
    for (var env in ['dev', 'prod'])
      if (env == 'dev')
        'debug': true
      else
        'optimize': true
  };
  print('設定: $config');
}

実践的な例

void main() {
  // ユーザーリストの生成
  var userIds = [1, 2, 3, 4, 5];
  var includeAdmin = true;
  
  var users = [
    if (includeAdmin) {'id': 0, 'name': 'Admin', 'role': 'admin'},
    for (var id in userIds)
      if (id % 2 == 1)
        {'id': id, 'name': 'User$id', 'role': 'user'}
  ];
  
  print('ユーザー一覧:');
  for (var user in users) {
    print('  ${user['name']} (${user['role']})');
  }
  
  // 動的なメニュー生成
  bool isLoggedIn = true;
  bool isAdmin = false;
  
  var menuItems = [
    'ホーム',
    if (isLoggedIn) ...[
      'マイページ',
      '設定',
      if (isAdmin) '管理画面'
    ] else ...[
      'ログイン',
      '新規登録'
    ]
  ];
  
  print('\nメニュー:');
  for (var item in menuItems) {
    print('  - $item');
  }
  
  // データの整形
  var rawData = [
    [1, 2, 3],
    null,
    [4, 5, 6],
    [],
    [7, 8, 9]
  ];
  
  var cleanedData = [
    for (var item in rawData)
      if (item != null && item.isNotEmpty)
        ...item
  ];
  
  print('\n整形後データ: $cleanedData');
}

これで第3章「コレクション」の解説は完了です。List、Set、Mapの基本操作から、高度な関数型メソッド、そしてSpread演算子やCollection if/forといった現代的な機能まで学びました。これらのコレクション操作を使いこなすことで、より効率的で読みやすいコードが書けるようになります。次の章では、Dartのクラスとオブジェクト指向プログラミングについて学んでいきましょう!

No responses yet

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です