目錄

可迭代的集合

本教學課程教您如何使用實作 Iterable 類別的集合,例如 ListSet。 Iterable 是各種 Dart 應用程式的基本建構區塊,您可能已經在使用它們,甚至沒有注意到。本教學課程可協助您充分利用它們。

使用內嵌的 DartPad 編輯器,您可以透過執行範例程式碼並完成練習來測試您的知識。

為了充分利用本教學課程,您應該具備 Dart 語法的基本知識。

本教學課程涵蓋以下內容

  • 如何讀取 Iterable 的元素。
  • 如何檢查 Iterable 的元素是否滿足條件。
  • 如何篩選 Iterable 的內容。
  • 如何將 Iterable 的內容映射到不同的值。

完成本教學課程的預計時間:60 分鐘。

本教學課程中的練習有部分完成的程式碼片段。您可以使用 DartPad 透過完成程式碼並按一下 執行 按鈕來測試您的知識。請勿編輯 main 函式或下方的測試程式碼

如果您需要協助,請在每個練習後展開 提示解決方案 下拉式選單。

什麼是集合?

#

集合是一個代表一組物件的物件,這些物件稱為元素。Iterable 是一種集合。

集合可以是空的,也可以包含許多元素。根據用途,集合可以具有不同的結構和實作。以下是一些最常見的集合型別

  • List: 用於按索引讀取元素。
  • Set: 用於包含只能出現一次的元素。
  • Map: 用於使用索引鍵讀取元素。

什麼是 Iterable?

#

Iterable 是一個可以循序存取元素的集合。

在 Dart 中,Iterable 是一個抽象類別,這表示您無法直接實例化它。但是,您可以透過建立新的 ListSet 來建立新的 Iterable

ListSet 都是 Iterable,因此它們具有與 Iterable 類別相同的方法和屬性。

Map 在內部使用不同的資料結構,具體取決於其實作。例如,HashMap 使用雜湊表,其中使用索引鍵取得元素(也稱為)。Map 的元素也可以透過使用 map 的 entriesvalues 屬性讀取為 Iterable 物件。

此範例顯示一個 intList,它也是一個 intIterable

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

List 的不同之處在於,使用 Iterable,您無法保證按索引讀取元素是有效率的。與 List 不同,Iterable 沒有 [] 運算子。

例如,請考慮以下程式碼,它是無效的

baddart
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);
}

在這個範例中,您可以看到撰寫謂詞的三種不同方式

  • 作為表達式: 測試程式碼有一行使用箭頭語法 (=>)。
  • 作為區塊: 測試程式碼在方括號之間有多行程式碼和一個 return 陳述式。
  • 作為函數: 測試程式碼位於一個外部函數中,該函數作為參數傳遞給 firstWhere() 方法。

沒有對錯之分。使用最適合您的方式,並讓您的程式碼更容易閱讀和理解。

最後一個範例使用選用的具名參數 orElse 呼叫 firstWhere(),當找不到元素時,它會提供替代方案。在這種情況下,由於沒有元素滿足提供的條件,因此會傳回文字 'None!'

練習:練習撰寫測試述詞

#

以下練習是一個失敗的單元測試,其中包含部分完成的程式碼片段。您的任務是完成練習,撰寫程式碼讓測試通過。您不需要實作 main()

這個練習介紹了 singleWhere()。此方法的運作方式與 firstWhere() 類似,但在這種情況下,它預期 Iterable 中只有一個元素滿足謂詞。如果 Iterable 中有多個或沒有元素滿足謂詞條件,則該方法會擲回 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 類別中的 containsstartsWith 方法。

解決方案
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 類別提供兩種方法,您可以使用它們來驗證條件

  • any():如果至少有一個元素滿足條件,則傳回 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() 會驗證是否至少有一個元素包含字元 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() 方法的練習,如先前的範例所述。在這種情況下,您會處理一群使用者,這些使用者由具有成員欄位 ageUser 物件表示。

使用 any()every() 來實作兩個函數

  • 第一部分:實作 anyUserUnder18()
    • 如果至少有一個使用者為 17 歲或以下,則傳回 true
  • 第二部分:實作 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 類別中的 anyevery 方法。如需有關使用這些方法的協助和範例,請參閱先前對它們的討論

解決方案
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() 來實作兩個函數

  • 第一部分:實作 filterOutUnder21()
    • 傳回包含所有 21 歲或以上使用者的 Iterable
  • 第二部分:實作 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() 方法對 Iterables 進行對應,可讓您將函數套用至每個元素,並將每個元素替換為新的元素。

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

在此範例中,Iterable 數字的每個元素都乘以 10。

您也可以使用 map() 將元素轉換為不同的物件—例如,將所有 int 轉換為 String,如下列範例所示

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

在此練習中,您的程式碼會取得 UserIterable,而且您需要傳回一個 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,該類別具有一個接受字串的建構函式。另一個提供的函數是 isValidEmailAddress(),它會測試電子郵件地址是否有效。

建構函式/函數類型簽名描述
EmailAddress()EmailAddress(String address)為指定的地址建立 EmailAddress
isValidEmailAddress()bool isValidEmailAddress(EmailAddress)如果提供的 EmailAddress 有效,則返回 true

編寫以下程式碼

第一部分:實作 parseEmailAddresses()

  • 編寫函數 parseEmailAddresses(),它接收一個包含電子郵件地址的 Iterable<String>,並返回一個 Iterable<EmailAddress>
  • 使用 map() 方法將 String 映射到 EmailAddress
  • 使用建構函式 EmailAddress(String) 建立 EmailAddress 物件。

第二部分:實作 anyInvalidEmailAddress()

  • 編寫函數 anyInvalidEmailAddress(),它接收一個 Iterable<EmailAddress>,如果 Iterable 中有任何 EmailAddress 無效,則返回 true
  • any() 方法與提供的函數 isValidEmailAddress() 一起使用。

第三部分:實作 validEmailAddresses()

  • 編寫函數 validEmailAddresses(),它接收一個 Iterable<EmailAddress>,並返回另一個僅包含有效地址的 Iterable<EmailAddress>
  • 使用 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));
}

下一步是什麼

#

恭喜,您已完成本教學課程!如果您想瞭解更多資訊,以下是一些建議的下一步方向