📜

Iterable collections①

2024/05/02に公開

📋What is Iterable & collections?

https://dart.dev/codelabs/iterables#what-are-collections

公式によると

Iterable は、あらゆる種類の Dart アプリケーションの基本的な構成要素であり、おそらく気付かずにすでに使用しているでしょう。

What are collections?

コレクションは空にすることも、多くの要素を含めることもできます。目的に応じて、コレクションはさまざまな構造と実装を持つことができます。以下に、最も一般的なコレクションの種類の一部を示します。

  1. List: インデックスによって要素を読み取るために使用されます。
  2. Set: 1 回だけ出現できる要素を含めるために使用されます。
  3. Map: キーを使用して要素を読み取るために使用されます。

What is an Iterable?

Iterable は、順番にアクセスできる要素のコレクションです。

List と Set は両方とも Iterable であるため、Iterable クラスと同じメソッドとプロパティを持ちます。

Map は、その実装に応じて、内部的に異なるデータ構造を使用します。たとえば、HashMap は、キーを使用して要素 (値とも呼ばれます) を取得するハッシュ テーブルを使用します。マップの要素は、マップのエントリまたは値プロパティを使用して Iterable オブジェクトとして読み取ることもできます。

この例は、int のリストを示しています。これは int の Iterable でもあります。

void main() {
  Iterable<int> iterable = [1, 2, 3];
  print(iterable);// [1, 2, 3]
}

List との違いは、Iterable ではインデックスによる要素の読み取りが効率的であることを保証できないことです。 List とは対照的に、Iterable には [] 演算子がありません。

たとえば、次のコードは無効であると考えてください。

// bad
void main() {
  Iterable<int> iterable = [1, 2, 3];
  int value = iterable[1];
}

[] を含む要素を読み取ると、コンパイラーは演算子 '[]' が Iterable クラスに定義されていないことを通知します。これは、この場合は [index] を使用できないことを意味します。

代わりに、elementAt() を使用して要素を読み取ることができます。これにより、その位置に到達するまで反復可能オブジェクトの要素がステップ実行されます。

void main() {
  Iterable<int> iterable = [1, 2, 3];
  int value = iterable.elementAt(1);
  print(value); // 2
}

要素の読み取り

for-in ループを使用して、反復可能オブジェクトの要素を順番に読み取ることができます。

for-in ループの使用

次の例は、for-in ループを使用して要素を読み取る方法を示しています。

void main() {
  const iterable = ['Salad', 'Popcorn', 'Toast'];
  for (final element in iterable) {
    print(element);// Salad, Popcorn, Toast
  }
}

[普通のfor文]

void main() {
  const iterable = ['Salad', 'Popcorn', 'Toast'];
  for (var i = 0; i < iterable.length; i++) {
    print('Index $i: ${iterable[i]}');
  }
}

[log]

Index 0: Salad
Index 1: Popcorn
Index 2: Toast

Exited.

[詳細]

[重要な用語]

例: first と last の使用

場合によっては、Iterable の最初または最後の要素のみにアクセスしたいことがあります。

Iterable クラスでは要素に直接アクセスできないため、iterable[0] を呼び出して最初の要素にアクセスすることはできません。代わりに、first を使用すると、最初の要素が取得されます。

また、Iterable クラスでは、演算子 [] を使用して最後の要素にアクセスすることはできませんが、last プロパティは使用できます。

void main() {
  Iterable<String> iterable = const ['Salad', 'Popcorn', 'Toast'];
  print('The first element is ${iterable.first}');
  print('The last element is ${iterable.last}');
}

[log]

The first element is Salad
The last element is Toast

Exited.

[Iterableが空の場合 ]

この例では、first と last を使用して Iterable の最初と最後の要素を取得する方法を説明しました。条件を満たす最初の要素を見つけることもできます。次のセクションでは、firstWhere() というメソッドを使用してこれを行う方法を示します。

例: firstWhere() の使用

Iterable の要素に順番にアクセスでき、最初または最後の要素を簡単に取得できることはすでに説明しました。

次に、firstWhere() を使用して、特定の条件を満たす最初の要素を検索する方法を学びます。このメソッドでは、入力が特定の条件を満たす場合に true を返す関数である述語を渡す必要があります。

void main() {
// 要素が5個。つまり5文字のKotlinが出力される。
  List<String> iterable = ['Dart', 'Java', 'Kotlin', 'Swift', 'Objective-C'];
  String element = iterable.firstWhere((element) => element.length > 5);
  print(element);
}// Output: Kotlin

たとえば、5 文字を超える最初の String を検索する場合は、要素サイズが 5 を超える場合に true を返す述語を渡す必要があります。

次の例を実行して、firstWhere() がどのように機能するかを確認します。すべての関数が同じ結果をもたらすと思いますか?

bool predicate(String item) {
  return item.length > 5;
}

void main() {
  const items = ['Salad', 'Popcorn', 'Toast', 'Lasagne'];

  // You can find with a simple expression:
  var foundItem1 = items.firstWhere((item) => item.length > 5);
  print(foundItem1);

  // Or try using a function block:
  var foundItem2 = items.firstWhere((item) {
    return item.length > 5;
  });
  print(foundItem2);

  // Or even pass in a function reference:
  var foundItem3 = items.firstWhere(predicate);
  print(foundItem3);

  // You can also use an `orElse` function in case no value is found!
  var foundItem4 = items.firstWhere(
    (item) => item.length > 10,
    orElse: () => '10以上じゃないよ!',
  );
  print(foundItem4);
}

[log]

Connecting to VM Service at ws://127.0.0.1:54075/5SA8Uw0mEA0=/ws
Popcorn
Popcorn
Popcorn
10以上じゃないよ!

この例では、述語を記述する 3 つの異なる方法を示します。

式として: テスト コードには、矢印構文 (=>) を使用する 1 行があります。
ブロックとして: テスト コードには、括弧と return ステートメントの間に複数の行があります。
関数として: テスト コードは、パラメーターとして firstWhere() メソッドに渡される外部関数内にあります。
正しい方法も間違った方法もありません。自分にとって最適な方法を使用すると、コードが読みやすく、理解しやすくなります。

最後の例では、オプションの名前付きパラメーター orElse を指定して firstWhere() を呼び出します。これは、要素が見つからない場合に代替手段を提供します。この場合、「なし!」というテキストが表示されます。指定された条件を満たす要素がないため、「」が返されます。

🔖Use Flutter Widget Example

Iterableの文法をFlutterで使ってみましょう。といっても大したことしてないですが....

Iterableの変数から、 Widgetを取得して表示してみましょう。 変数の値をforでループして取り出してみましょう。UIに優先順位を表す星とテキストが表示されるはずです。

import 'package:flutter/material.dart';

class IterableView extends StatelessWidget {
  const IterableView({super.key});

  
  Widget build(BuildContext context) {
    // Data Type Widget
    Iterable<Widget> colorIcon = [
      const Icon(Icons.star, color: Colors.red),
      const Icon(Icons.star, color: Colors.green),
      const Icon(Icons.star, color: Colors.blue),
    ];

    Iterable<Widget> listTitle = [
      const Text('優先事項'),
      const Text('普通'),
      const Text('今やらてなくもいいよ'),
    ];

    // Data Type Map (key-value pair)
    final listState = Map.fromIterables(colorIcon, listTitle);

    return Scaffold(
      appBar: AppBar(
        title: const Text('Iterable View'),
      ),
      body: Center(
        child: Column(
          children: [
            for(var i in listState.entries)
            Row(
              children: [
                i.key,
                i.value,
              ],
            ),
          ],
        ),
      ),
    );
  }
}

[run code]

import 'package:flutter/material.dart';
import 'package:widget_cookbook/record_list/iterable_view.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: IterableView(),
    );
  }
}

同じ時間が格納されているIterableをSetに追加して、ボタンを押すと、addAllでリストに追加して、UIに表示する例を作ってみました。同じ時間が変数に入ってますが、 集合のSetを使うと、重複する値、つまり同じ値は取り除かれます。

import 'package:flutter/material.dart';

class IterableView extends StatefulWidget {
  const IterableView({super.key});

  
  State<IterableView> createState() => _IterableViewState();
}

class _IterableViewState extends State<IterableView> {
    // Set is a collection of objects in which each object can appear only once.
    Set<DateTime> taskList = {};

    // Iterable is a collection of objects that can be accessed sequentially.
    Iterable<DateTime> date = [
      // 2024/01/01/12:00
      DateTime(2024, 1, 1),
      // 2024/01/02/6:00
      DateTime(2024, 1, 2),
      // 2024/01/03/18:00
      DateTime(2024, 1, 3),
      DateTime(2024, 1, 3),
      DateTime(2024, 1, 3),
    ];
    
    // addTask() function is used to add the elements of date to taskList
    void _addTask() {
      setState(() {
        // all the elements of date will be added to taskList
        taskList.addAll(date);
      });
    }    

  
  Widget build(BuildContext context) {


    return Scaffold(
      appBar: AppBar(
        title: const Text('Iterable View'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _addTask,
        child: const Icon(Icons.add),
      ),
      body: ListView.builder(
        itemCount: taskList.length,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text('${taskList.elementAt(index)}'),
          );
        },
      ),
    );
  }
}

Iterableが使えなかった例

Listを使用しないと、値が取得できないのと、switchで分岐処理ができなかった。7文字以上の文字である Objective-Cを表示できなかった。Listがからの時は、No data found!のテキストを表示する。

import 'package:flutter/material.dart';

class IterableView extends StatefulWidget {
  const IterableView({Key? key}) : super(key: key);

  
  State<IterableView> createState() => _IterableViewState();
}

class _IterableViewState extends State<IterableView> {
  List<String> iterable = ['PHP', 'Ruby','Dart', 'Java', 'Kotlin', 'Swift', 'Objective-C'];
  // List<String> iterable = [];
  void langageFilter() {
  setState(() {
    iterable = iterable.where((element) => element.length >= 7).toList();
  });
}

  
  void initState() {
    langageFilter();
    super.initState();
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Iterable View'),
      ),
      body: switch(iterable) {
        [] => const Center(child: Text('No data found!')),
        _ =>
          ListView.builder(
            itemCount: iterable.length,
            itemBuilder: (context, index) {
              return ListTile(
                title: Text(iterable[index]),
              );
            },
          ),
      },
    );
  }
}

まとめ

Iterableについて今回は探求してみました。ただ公式のサンプルを使っているだけだと面白くないので、FlutterのWidgetで使ってみました。

[重要なところは]
List との違いは、Iterable ではインデックスによる要素の読み取りが効率的であることを保証できないことです。 List とは対照的に、Iterable には [] 演算子がありません。

Discussion