メインコンテンツにスキップ

Iterable コレクション

このチュートリアルでは、Iterable クラスを実装するコレクション、例えば ListSet を使用する方法を学びます。Iterable は、あらゆる種類の Dart アプリケーションの基本的な構成要素であり、おそらく気づかずにすでに使用しているはずです。このチュートリアルでは、それらを最大限に活用する方法を説明します。

組み込みの DartPad エディターを使用して、例のコードを実行し、演習を完了することで、知識をテストできます。

このチュートリアルを最大限に活用するには、Dart 構文の基本的な知識が必要です。

このチュートリアルでは、以下の内容を扱います

  • Iterable の要素を読み取る方法。
  • Iterable の要素が条件を満たすかどうかをチェックする方法。
  • Iterable の内容をフィルタリングする方法。
  • Iterable の内容を別の値にマッピングする方法。

このチュートリアルを完了するための推定時間:60 分。

このチュートリアルの演習には、部分的に完了したコードスニペットが含まれています。DartPad を使用してコードを完成させ、Run ボタンをクリックすることで、知識をテストできます。main 関数内またはそれ以降のテストコードは編集しないでください

ヘルプが必要な場合は、各演習の後に Hint または Solution ドロップダウンを展開してください。

コレクションとは?

#

コレクションとは、要素と呼ばれるオブジェクトのグループを表すオブジェクトです。Iterable は一種のコレクションです。

コレクションは空であることも、多くの要素を含むこともできます。目的によっては、コレクションは異なる構造と実装を持つことができます。これらは最も一般的なコレクションタイプの一部です

  • List: インデックスによって要素を読み取るために使用されます。
  • Set: 一度しか出現しない要素を含むために使用されます。
  • Map: キーを使用して要素を読み取るために使用されます。

Iterableとは?

#

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

Dart では、Iterable は抽象クラスであり、直接インスタンス化することはできません。しかし、新しい List または Set を作成することで、新しい Iterable を作成できます。

ListSet の両方が Iterable であるため、Iterable クラスと同じメソッドとプロパティを持っています。

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

この例は、intList を示しており、これも intIterable です。

dart
Iterable<int> iterable = [1, 2, 3];

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

たとえば、次のコードを考えてみてください。これは無効です。

baddart
Iterable<int> iterable = [1, 2, 3];
int value = iterable[1];

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

代わりに elementAt() で要素を読み取ることができます。これは、イテラブルのアイテムをステップ実行して、その位置にあるアイテムに到達します。

dart
Iterable<int> iterable = [1, 2, 3];
int value = iterable.elementAt(1);

イテラブルの実装とアイテム数によっては、elementAt は線形時間複雑度を持ち、コストがかかる可能性があります。特定のアイテムに繰り返しアクセスする予定がある場合は、イテラブルに対して .toList() を呼び出して一度リストに変換し、[] 演算子を使用することを検討してください。

dart
final items = veryLargeIterable().toList();

final tenthItem = items[9];
final hundredthItem = items[99];
final thousandthItem = items[999];
final lastItem = items.last;

Iterable の要素にアクセスする方法の詳細については、次のセクションに進んでください。

要素の読み取り

#

for-in ループを使用して、Iterable の要素を順次読み取ることができます。

例:for-inループの使用

#

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

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

例: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}');
}

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

例:firstWhere() の使用

#

Iterable の要素を順次アクセスできること、および最初の要素や最後の要素を簡単に取得できることはすでに確認しました。

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

dart
String element = iterable.firstWhere((element) => element.length > 5);

たとえば、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: () => 'None!',
  );
  print(foundItem4);
}

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

  • 式として: テストコードには、アロー構文 (=>) を使用する行が 1 つあります。
  • ブロックとして: テストコードには、ブラケット間の複数行と return ステートメントがあります。
  • 関数として: テストコードは、firstWhere() メソッドにパラメーターとして渡される外部関数にあります。

正しい方法も間違った方法もありません。あなたにとって最適な方法、そしてコードを読みやすく理解しやすくする方法を使用してください。

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

演習:テスト述語の作成練習

#

次の演習は、部分的に完了したコードスニペットを含む失敗した単体テストです。演習を完了するには、テストをパスさせるコードを記述します。main() を実装する必要はありません。

この演習では singleWhere() を紹介します。このメソッドは firstWhere() と似ていますが、この場合は述語を満たす Iterable の要素が 1 つだけであることを期待します。Iterable 内で 1 つ以上の要素または 0 個の要素が述語条件を満たさない場合、メソッドは StateError 例外をスローします。

目標は、次の条件を満たす singleWhere() の述語を実装することです。

  • 要素に文字 'a' が含まれている。
  • 要素が文字 'M' で始まる。

テストデータ内のすべての要素は 文字列です。ヘルプについては、クラスのドキュメントを確認してください。

// Implement the predicate of singleWhere
// with the following conditions
// * The element contains the character `'a'`
// * The element starts with the character `'M'`
String singleWhere(Iterable<String> items) {
  return items.singleWhere(TODO('Implement the outlined predicate.'));
}

// The following code is used to provide feedback on your solution.
// There is no need to read or modify it.
void main() {
  const items = [
    'Salad',
    'Popcorn',
    'Milk',
    'Toast',
    'Sugar',
    'Mozzarella',
    'Tomato',
    'Egg',
    'Water',
  ];

  try {
    final str = singleWhere(items);
    if (str == 'Mozzarella') {
      print('Success. All tests passed!');
    } else {
      print(
        'Tried calling singleWhere, but received $str instead of '
        'the expected value \'Mozzarella\'',
      );
    }
  } on StateError catch (stateError) {
    print(
      'Tried calling singleWhere, but received a StateError: ${stateError.message}. '
      'singleWhere will fail if 0 or many elements match the predicate.',
    );
  } on UnimplementedError {
    print(
      'Tried running `singleWhere`, but received an error. '
      'Did you implement the function?',
    );
  } catch (e) {
    print('Tried calling singleWhere, but received an exception: $e');
  }
}
ヒント

ソリューションでは、String クラスの contains および startsWith メソッドを利用する場合があります。

解答
dart
String singleWhere(Iterable<String> items) {
  return items.singleWhere(
          (element) => element.startsWith('M') && element.contains('a'));
}

条件のチェック

#

Iterable を扱う場合、コレクションのすべての要素がある条件を満たすことを確認する必要がある場合があります。

次のような for-in ループを使用したソリューションを記述したくなるかもしれません。

baddart
for (final item in items) {
  if (item.length < 5) {
    return false;
  }
}
return true;

しかし、every() メソッドを使用して同じことを達成できます。

dart
return items.every((item) => item.length >= 5);

every() メソッドを使用すると、コードがより読みやすく、コンパクトで、エラーが発生しにくくなります。

例:any() と every() の使用

#

Iterable クラスには、条件を検証するために使用できる 2 つのメソッドがあります。

  • any(): 少なくとも 1 つの要素が条件を満たす場合に true を返します。
  • every(): すべての要素が条件を満たす場合に true を返します。

この演習を実行して、それらがどのように機能するかを確認してください。

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

  if (items.any((item) => item.contains('a'))) {
    print('At least one item contains "a"');
  }

  if (items.every((item) => item.length >= 5)) {
    print('All items have length >= 5');
  }
}

例では、any() は少なくとも 1 つの要素に文字 a が含まれていることを検証し、every() はすべての要素の長さが 5 以上であることを検証します。

コードを実行した後、any() の述語を変更して false を返すようにしてみてください。

dart
if (items.any((item) => item.contains('Z'))) {
  print('At least one item contains "Z"');
} else {
  print('No item contains "Z"');
}

any() を使用して、Iterable のどの要素も特定の条件を満たさないことを確認することもできます。

演習:Iterable が条件を満たすことの検証

#

次の演習では、前の例で説明した any() および every() メソッドの使用練習を提供します。この場合、User オブジェクトによって表されるユーザーのグループを扱います。これらのオブジェクトには age というメンバーフィールドがあります。

any() および every() を使用して、2 つの関数を実装してください。

  • パート 1: anyUserUnder18() を実装する。
    • 少なくとも 1 人のユーザーが 17 歳以下であれば true を返します。
  • パート 2: everyUserOver13() を実装する。
    • すべてのユーザーが 14 歳以上であれば true を返します。
bool anyUserUnder18(Iterable<User> users) {
  // TODO: Implement the anyUserUnder18 function.
}

bool everyUserOver13(Iterable<User> users) {
  // TODO: Implement the everyUserOver13 function.
}

class User {
  final String name;
  final int age;

  User(
    this.name,
    this.age,
  );
}

// The following code is used to provide feedback on your solution.
// There is no need to read or modify it.
void main() {
  final users = [
    User('Alice', 21),
    User('Bob', 17),
    User('Claire', 52),
    User('David', 14),
  ];

  try {
    final out = anyUserUnder18(users);
    if (!out) {
      print('Looks like `anyUserUnder18` is wrong. Keep trying!');
      return;
    }
  } on UnimplementedError {
    print(
      'Tried running `anyUserUnder18`, but received an error. '
      'Did you implement the function?',
    );
    return;
  } catch (e) {
    print('Tried running `anyUserUnder18`, but received an exception: $e');
    return;
  }

  try {
    // with only one user older than 18, should be false
    final out = anyUserUnder18([User('Alice', 21)]);
    if (out) {
      print(
          'Looks like `anyUserUnder18` is wrong. What if all users are over 18?');
      return;
    }
  } on UnimplementedError {
    print(
      'Tried running `anyUserUnder18`, but received an error. '
      'Did you implement the function?',
    );
    return;
  } catch (e) {
    print(
      'Tried running `anyUserUnder18([User("Alice", 21)])`, '
      'but received an exception: $e',
    );
    return;
  }

  try {
    final out = everyUserOver13(users);
    if (!out) {
      print(
        'Looks like `everyUserOver13` is wrong. '
        'There are no users under 13!',
      );
      return;
    }
  } on UnimplementedError {
    print(
      'Tried running `everyUserOver13`, but received an error. '
      'Did you implement the function?',
    );
    return;
  } catch (e) {
    print(
      'Tried running `everyUserOver13`, '
      'but received an exception: $e',
    );
    return;
  }

  try {
    final out = everyUserOver13([User('Dan', 12)]);
    if (out) {
      print(
        'Looks like `everyUserOver13` is wrong. '
        'There is at least one user under 13!',
      );
      return;
    }
  } on UnimplementedError {
    print(
      'Tried running `everyUserOver13`, but received an error. '
      'Did you implement the function?',
    );
    return;
  } catch (e) {
    print(
      'Tried running `everyUserOver13([User(\'Dan\', 12)])`, '
      'but received an exception: $e',
    );
    return;
  }

  print('Success. All tests passed!');
}
ヒント

Iterable クラスの any および every メソッドを使用することを忘れないでください。これらのメソッドの使用方法に関するヘルプや例については、それらの前の説明を参照してください。

解答
dart
bool anyUserUnder18(Iterable<User> users) {
  return users.any((user) => user.age < 18);
}

bool everyUserOver13(Iterable<User> users) {
  return users.every((user) => user.age > 13);
}

フィルタリング

#

前のセクションでは、特定の述語を満たす要素を見つけるのに役立つ firstWhere()singleWhere() のようなメソッドを扱いました。

しかし、特定の条件を満たすすべての要素を見つけたい場合はどうでしょうか?where() メソッドを使用してそれを達成できます。

dart
var evenNumbers = numbers.where((number) => number.isEven);

この例では、numbers に複数の int 値を含む Iterable があり、where() は偶数のすべての数値を見つけます。

where() の出力は別の Iterable であり、それをイテレートしたり、他の Iterable メソッドを適用したりするために、そのまま使用できます。次の例では、where() の出力が for-in ループ内で直接使用されています。

dart
var evenNumbers = numbers.where((number) => number.isEven);
for (final number in evenNumbers) {
  print('$number is even');
}

例:where() の使用

#

この例を実行して、where()any() のような他のメソッドとどのように組み合わせて使用できるかを確認してください。

void main() {
  var evenNumbers = const [1, -2, 3, 42].where((number) => number.isEven);

  for (final number in evenNumbers) {
    print('$number is even.');
  }

  if (evenNumbers.any((number) => number.isNegative)) {
    print('evenNumbers contains negative numbers.');
  }

  // If no element satisfies the predicate, the output is empty.
  var largeNumbers = evenNumbers.where((number) => number > 1000);
  if (largeNumbers.isEmpty) {
    print('largeNumbers is empty!');
  }
}

この例では、where() を使用して偶数のすべての数値を見つけ、次に any() を使用して結果に負の数があるかどうかをチェックします。

例の後半では、where() が再度使用されて 1000 より大きいすべての数値を見つけます。それらはないため、結果は空の Iterable になります。

例:takeWhile の使用

#

takeWhile() および skipWhile() メソッドも、Iterable から要素をフィルタリングするのに役立ちます。

この例を実行して、takeWhile() および skipWhile() が数値を含む Iterable を分割する方法を確認してください。

void main() {
  const numbers = [1, 3, -2, 0, 4, 5];

  var numbersUntilZero = numbers.takeWhile((number) => number != 0);
  print('Numbers until 0: $numbersUntilZero');

  var numbersStartingAtZero = numbers.skipWhile((number) => number != 0);
  print('Numbers starting at 0: $numbersStartingAtZero');
}

この例では、takeWhile() は、述語を満たす要素の前のすべての要素を含む Iterable を返します。一方、skipWhile() は、述語を満たさない最初の要素以降のすべての要素を含む Iterable を返します。

例を実行した後、takeWhile() を変更して、最初の負の数に到達するまで要素を取得するようにしてください。

dart
var numbersUntilNegative = numbers.takeWhile(
  (number) => !number.isNegative,
);

条件 number.isNegative! で否定されていることに注意してください。

演習:リストからの要素のフィルタリング

#

次の演習では、前の演習の User クラスを使用して where() メソッドの練習を行います。

where() を使用して、2 つの関数を実装してください。

  • パート 1: filterOutUnder21() を実装する。
    • 21 歳以上のすべてのユーザーを含む Iterable を返します。
  • パート 2: findShortNamed() を実装する。
    • 名前の長さが 3 文字以下のすべてのユーザーを含む Iterable を返します。
Iterable<User> filterOutUnder21(Iterable<User> users) {
  // TODO: Implement the filterOutUnder21 function.
}

Iterable<User> findShortNamed(Iterable<User> users) {
  // TODO: Implement the findShortNamed function.
}

class User {
  final String name;
  final int age;

  User(
    this.name,
    this.age,
  );
}

// The following code is used to provide feedback on your solution.
// There is no need to read or modify it.
void main() {
  final users = [
    User('Alice', 21),
    User('Bob', 17),
    User('Claire', 52),
    User('Dan', 12),
  ];

  try {
    final out = filterOutUnder21(users);
    if (out.any((user) => user.age < 21) || out.length != 2) {
      print(
        'Looks like `filterOutUnder21` is wrong, there are '
        'exactly two users with age under 21. Keep trying!',
      );
      return;
    }
  } on UnimplementedError {
    print(
      'Tried running `filterOutUnder21`, but received an error. '
      'Did you implement the function?',
    );
    return;
  } catch (e) {
    print(
      'Tried running `filterOutUnder21`, '
      'but received an exception: ${e.runtimeType}',
    );
    return;
  }

  try {
    final out = findShortNamed(users);
    if (out.any((user) => user.name.length > 3) || out.length != 2) {
      print(
        'Looks like `findShortNamed` is wrong, there are '
        'exactly two users with a three letter name. Keep trying!',
      );
      return;
    }
  } on UnimplementedError {
    print(
      'Tried running `findShortNamed`, but received an error. '
      'Did you implement the function?',
    );
    return;
  } catch (e) {
    print(
      'Tried running `findShortNamed`, '
      'but received an exception: ${e.runtimeType}',
    );
    return;
  }

  print('Success. All tests passed!');
}
ヒント

Iterable クラスの where メソッドを活用することを忘れないでください。where の使用方法に関するヘルプや例については、それらの前の説明を参照してください。

解答
dart
Iterable<User> filterOutUnder21(Iterable<User> users) {
  return users.where((user) => user.age >= 21);
}

Iterable<User> findShortNamed(Iterable<User> users) {
  return users.where((user) => user.name.length <= 3);
}

マッピング

#

map() メソッドで Iterable をマッピングすると、各要素に関数を適用して、各要素を新しい要素に置き換えることができます。

dart
Iterable<int> output = numbers.map((number) => number * 10);

この例では、Iterable numbers の各要素が 10 倍されます。

map() を使用して、要素を別のオブジェクトに変換することもできます。たとえば、すべての intString に変換するなど、次の例で確認できます。

dart
Iterable<String> output = numbers.map((number) => number.toString());

例:map を使用した要素の変更

#

この例を実行して、map() を使用して Iterable のすべての要素を 2 倍する方法を確認してください。出力は何になると思いますか?

void main() {
  var numbersByTwo = const [1, -2, 3, 42].map((number) => number * 2);
  print('Numbers: $numbersByTwo');
}

演習:別タイプへのマッピング

#

前の例では、Iterable の要素を 2 倍しました。その操作の入力と出力の両方が intIterable でした。

この演習では、コードは User の Iterable を受け取ります。各ユーザーの名前と年齢を含む文字列を含む Iterable を返す必要があります。

Iterable 内の各文字列は、'{name} is {age}' という形式である必要があります。たとえば 'Alice is 21' のようになります。

Iterable<String> getNameAndAges(Iterable<User> users) {
  // TODO: Implement the getNameAndAges function.
}

class User {
  final String name;
  final int age;

  User(
    this.name,
    this.age,
  );
}

// The following code is used to provide feedback on your solution.
// There is no need to read or modify it.
void main() {
  final users = [
    User('Alice', 21),
    User('Bob', 17),
    User('Claire', 52),
  ];

  try {
    final out = getNameAndAges(users).toList();
    if (!_listEquals(out, ['Alice is 21', 'Bob is 17', 'Claire is 52'])) {
      print(
        'Looks like `getNameAndAges` is wrong. Keep trying! '
        'The output was: $out',
      );
      return;
    }
  } on UnimplementedError {
    print(
      'Tried running `getNameAndAges`, but received an error. '
      'Did you implement the function?',
    );
    return;
  } catch (e) {
    print('Tried running the function, but received an exception: $e');
    return;
  }

  print('Success. All tests passed!');
}

bool _listEquals<T>(List<T>? a, List<T>? b) {
  if (a == null) return b == null;
  if (b == null || a.length != b.length) return false;
  for (var index = 0; index < a.length; index += 1) {
    if (a[index] != b[index]) return false;
  }
  return true;
}
ヒント

Iterable クラスの map メソッドを活用することを忘れないでください。map の使用方法に関するヘルプや例については、それらの前の説明を参照してください。

複数の値を単一の文字列に連結するには、文字列補間の使用を検討してください。

解答
dart
Iterable<String> getNameAndAges(Iterable<User> users) {
  return users.map((user) => '${user.name} is ${user.age}');
}

演習:すべてをまとめる

#

最後に、学んだことを練習する演習です。

この演習では、文字列を受け取るコンストラクタを持つ EmailAddress クラスを提供します。もう 1 つ提供される関数は isValidEmailAddress() で、メールアドレスが有効かどうかをテストします。

コンストラクタ/関数型シグネチャ説明
EmailAddress()EmailAddress(String address)指定されたアドレスの EmailAddress を作成します。
isValidEmailAddress()bool isValidEmailAddress(EmailAddress)提供された EmailAddress が有効な場合に true を返します。

次のコードを記述してください。

パート 1: parseEmailAddresses() を実装する。

  • メールアドレスを含む Iterable<String> を受け取り、Iterable<EmailAddress> を返す関数 parseEmailAddresses() を記述してください。
  • map() メソッドを使用して、String から EmailAddress にマッピングします。
  • コンストラクタ EmailAddress(String) を使用して EmailAddress オブジェクトを作成します。

パート 2: anyInvalidEmailAddress() を実装する。

  • Iterable<EmailAddress> を受け取り、Iterable 内のいずれかの EmailAddress が無効な場合に true を返す関数 anyInvalidEmailAddress() を記述してください。
  • any() メソッドと提供された関数 isValidEmailAddress() を一緒に使用します。

パート 3: validEmailAddresses() を実装する。

  • Iterable<EmailAddress> を受け取り、有効なアドレスのみを含む別の Iterable<EmailAddress> を返す関数 validEmailAddresses() を記述してください。
  • where() メソッドを使用して Iterable<EmailAddress> をフィルタリングします。
  • 提供された関数 isValidEmailAddress() を使用して、EmailAddress が有効かどうかを評価します。
Iterable<EmailAddress> parseEmailAddresses(Iterable<String> strings) {
  // TODO: Implement the parseEmailAddresses function.
}

bool anyInvalidEmailAddress(Iterable<EmailAddress> emails) {
  // TODO: Implement the anyInvalidEmailAddress function.
}

Iterable<EmailAddress> validEmailAddresses(Iterable<EmailAddress> emails) {
  // TODO: Implement the validEmailAddresses function.
}

class EmailAddress {
  final String address;

  EmailAddress(this.address);

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is EmailAddress && address == other.address;

  @override
  int get hashCode => address.hashCode;

  @override
  String toString() => 'EmailAddress{address: $address}';
}

// The following code is used to provide feedback on your solution.
// There is no need to read or modify it.
void main() {
  const input = [
    'ali@gmail.com',
    'bobgmail.com',
    'cal@gmail.com',
  ];

  const correctInput = ['dash@gmail.com', 'sparky@gmail.com'];

  bool _listEquals<T>(List<T>? a, List<T>? b) {
    if (a == null) return b == null;
    if (b == null || a.length != b.length) return false;
    for (var index = 0; index < a.length; index += 1) {
      if (a[index] != b[index]) return false;
    }
    return true;
  }

  final Iterable<EmailAddress> emails;
  final Iterable<EmailAddress> correctEmails;
  try {
    emails = parseEmailAddresses(input);
    correctEmails = parseEmailAddresses(correctInput);
    if (emails.isEmpty) {
      print(
        'Tried running `parseEmailAddresses`, but received an empty list.',
      );
      return;
    }
    if (!_listEquals(emails.toList(), [
      EmailAddress('ali@gmail.com'),
      EmailAddress('bobgmail.com'),
      EmailAddress('cal@gmail.com'),
    ])) {
      print('Looks like `parseEmailAddresses` is wrong. Keep trying!');
      return;
    }
  } on UnimplementedError {
    print(
      'Tried running `parseEmailAddresses`, but received an error. '
      'Did you implement the function?',
    );
    return;
  } catch (e) {
    print(
      'Tried running `parseEmailAddresses`, '
      'but received an exception: $e',
    );
    return;
  }

  try {
    final out = anyInvalidEmailAddress(emails);
    if (!out) {
      print(
        'Looks like `anyInvalidEmailAddress` is wrong. Keep trying! '
        'The result should be false with at least one invalid address.',
      );
      return;
    }
    final falseOut = anyInvalidEmailAddress(correctEmails);
    if (falseOut) {
      print(
        'Looks like `anyInvalidEmailAddress` is wrong. Keep trying! '
        'The result should be false with all valid addresses.',
      );
      return;
    }
  } on UnimplementedError {
    print(
      'Tried running `anyInvalidEmailAddress`, but received an error. '
      'Did you implement the function?',
    );
    return;
  } catch (e) {
    print(
        'Tried running `anyInvalidEmailAddress`, but received an exception: $e');
    return;
  }

  try {
    final valid = validEmailAddresses(emails);
    if (emails.isEmpty) {
      print('Tried running `validEmailAddresses`, but received an empty list.');
      return;
    }
    if (!_listEquals(valid.toList(), [
      EmailAddress('ali@gmail.com'),
      EmailAddress('cal@gmail.com'),
    ])) {
      print('Looks like `validEmailAddresses` is wrong. Keep trying!');
      return;
    }
  } on UnimplementedError {
    print(
      'Tried running `validEmailAddresses`, but received an error. '
      'Did you implement the function?',
    );
    return;
  } catch (e) {
    print(
      'Tried running the `validEmailAddresses`, '
      'but received an exception: $e',
    );
    return;
  }

  print('Success. All tests passed!');
}

bool isValidEmailAddress(EmailAddress email) {
  return email.address.contains('@');
}
解答
dart
Iterable<EmailAddress> parseEmailAddresses(Iterable<String> strings) {
  return strings.map((s) => EmailAddress(s));
}

bool anyInvalidEmailAddress(Iterable<EmailAddress> emails) {
  return emails.any((email) => !isValidEmailAddress(email));
}

Iterable<EmailAddress> validEmailAddresses(Iterable<EmailAddress> emails) {
  return emails.where((email) => isValidEmailAddress(email));
}

次に進む

#

おめでとうございます、チュートリアルは終了しました!さらに学習したい場合は、次の場所に進むことをお勧めします。