目次

反復可能なコレクション

このチュートリアルでは、Iterableクラスを実装するコレクション(例:ListSet)の使用方法を学びます。Iterableはあらゆる種類のDartアプリケーションの基本的な構成要素であり、気づかないうちに既に使用している可能性があります。このチュートリアルは、それらを最大限に活用するのに役立ちます。

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

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

このチュートリアルでは、次の内容について説明します。

  • Iterableの要素を読み取る方法。
  • Iterableの要素が条件を満たしているかどうかを確認する方法。
  • Iterableの内容をフィルタリングする方法。
  • Iterableの内容を異なる値にマップする方法。

このチュートリアルの推定完了時間:60分。

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

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

コレクションとは?

#

コレクションは、要素と呼ばれるオブジェクトのグループを表すオブジェクトです。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では、インデックスによる要素の読み取りが効率的であるとは保証できないことです。Listとは異なり、Iterableには[]演算子はありません。

たとえば、次のコードは無効です。

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

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

代わりにelementAt()を使用して要素を読み取ることができます。これは、その位置に達するまでiterableの要素をステップ実行します。

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

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つあります。
  • ブロックとして:テストコードには、角括弧で囲まれた複数行と戻り値のステートメントがあります。
  • 関数として:テストコードは、firstWhere() メソッドにパラメーターとして渡される外部関数内にあります。

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

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

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

#

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

この演習では、singleWhere() を紹介します。このメソッドは firstWhere() と同様に機能しますが、この場合は Iterable の要素が1つだけ述語を満たすことを期待します。Iterable 内の複数の要素、または要素が1つも述語条件を満たしていない場合、このメソッドは 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ループを使用して解決策を作成しようとすることがあります。

無効dart
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() メソッドを使用する練習を行います。この場合、メンバーフィールド age を持つ User オブジェクトで表されるユーザーグループを操作します。

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 でした。

この演習では、ユーザーの 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 の使用例については、前の説明を参照してください。

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

解答
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() を記述します。
  • 提供された関数 isValidEmailAddress() とともに any() メソッドを使用します。

パート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));
}

次のステップ

#

チュートリアルを完了しました!さらに学習したい場合は、次に進むためのいくつかの提案を以下に示します。