Menu
Coddy logo textTech

Iterable と Iterator

CoddyのDartジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 68/110。

List、Set、またはその他のコレクションに対して for-in ループを使用するたびに、内部的には IterableIterator を操作しています。これらの概念を理解することで、ループ処理が可能な独自のクラスを作成できるようになります。

Iterable は、値のシーケンスを生成できる任意のオブジェクトです。これには1つの重要な要件があります。それは、Iterator を返す iterator ゲッターです。Iterator は、実際に要素を1つずつ移動するものです:

var numbers = [1, 2, 3];
var iterator = numbers.iterator;

while (iterator.moveNext()) {
  print(iterator.current);  // 1、次に 2、次に 3
}

moveNext() メソッドは次の要素に進み、要素がある場合は true を、終了した場合は false を返します。current プロパティは現在の要素を返します。

独自のクラスを反復可能にするには、Iterableインターフェースを実装します:

class Countdown extends Iterable<int> {
  final int start;
  Countdown(this.start);
  
  @override
  Iterator<int> get iterator => CountdownIterator(start);
}

class CountdownIterator implements Iterator<int> {
  int _current;
  
  CountdownIterator(int start) : _current = start + 1;
  
  @override
  int get current => _current;
  
  @override
  bool moveNext() {
    if (_current > 0) {
      _current--;
      return true;
    }
    return false;
  }
}

void main() {
  for (var n in Countdown(3)) {
    print(n);  // 3, 2, 1, 0
  }
}

これで、Countdownfor-inループや、mapwherefoldといったすべてのIterableメソッドで動作するようになります。このパターンは、Dartのコレクションエコシステムとシームレスに統合されるカスタムコレクションを構築する際に不可欠です。

challenge icon

チャレンジ

簡単

for-in ループを使用して反復処理できるカスタム範囲ジェネレータを作成しましょう!指定された範囲内の数値シーケンスを生成するために、独自の Iterable クラスと Iterator クラスを作成します。

コードを2つのファイルに整理します:

  • range.dart: 数値のシーケンスを生成するために連携する2つのクラスを作成します:
    • Iterator<int> を実装する RangeIterator クラス。現在の位置を追跡し、いつ停止するかを認識する必要があります。イテレータは最初の要素の「前」から開始するため、current にアクセスする前に moveNext() を呼び出す必要があります。
    • Iterable<int> を継承する Range クラス。コンストラクタで startend の値を受け取り、RangeIterator を返す iterator ゲッターを提供します。
    範囲は startend の両方を含む(inclusive)必要があります。例えば、Range(1, 5) は 1, 2, 3, 4, 5 を生成する必要があります。
  • main.dart: 作成した range ファイルをインポートし、カスタム反復可能オブジェクトを実演します:
    • Range(1, 5) を作成し、for-in ループを使用して各数値をそれぞれの行に出力します。
    • 空行を1行出力します。
    • Range(10, 13) を作成し、map メソッドを使用して各値を2倍にし、結果のリストを出力します。

Range クラスは Iterable を継承しているため、mapwherefold といった強力なコレクションメソッドに自動的にアクセスできるようになります。これが Iterable インターフェースを実装する素晴らしさです!

期待される出力:

1
2
3
4
5

[20, 22, 24, 26]

チートシート

Iterable は、値のシーケンスを生成できる任意のオブジェクトです。これには、Iterator を返す iterator ゲッターが必要です。

Iterator は、以下を使用して要素を一つずつ移動します:

  • moveNext() - 次の要素に進み、要素がある場合は true を、終了した場合は false を返します
  • current - 現在の要素を取得するプロパティ

イテレータを手動で使用する場合:

var numbers = [1, 2, 3];
var iterator = numbers.iterator;

while (iterator.moveNext()) {
  print(iterator.current);  // 1, then 2, then 3
}

カスタムクラスを反復可能(iterable)にするには、Iterable インターフェースを実装し、対応する Iterator クラスを作成します:

class Countdown extends Iterable<int> {
  final int start;
  Countdown(this.start);
  
  @override
  Iterator<int> get iterator => CountdownIterator(start);
}

class CountdownIterator implements Iterator<int> {
  int _current;
  
  CountdownIterator(int start) : _current = start + 1;
  
  @override
  int get current => _current;
  
  @override
  bool moveNext() {
    if (_current > 0) {
      _current--;
      return true;
    }
    return false;
  }
}

void main() {
  for (var n in Countdown(3)) {
    print(n);  // 3, 2, 1, 0
  }
}

Iterable を継承するクラスは、for-in ループで使用でき、mapwherefold などのすべての Iterable メソッドに自動的にアクセスできるようになります。

自分で試してみよう

import 'range.dart';

void main() {
  // TODO: Range(1, 5)を作成し、for-inループを使用して各数値を出力してください
  
  
  // TODO: 空行を出力してください
  
  
  // TODO: Range(10, 13)を作成し、mapを使用して各値を2倍にし、そのリストを出力してください
  
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン