Menu
Coddy logo textTech

Iterable и Iterator

Часть раздела Объектно-ориентированное программирование путешествия по Dart на Coddy — урок 68 из 110.

Каждый раз, когда вы используете цикл for-in для List, Set или другой коллекции, вы работаете с Iterable и Iterator «под капотом». Понимание этих концепций позволяет вам создавать собственные классы, которые можно обходить в цикле.

Iterable — это любой объект, который может генерировать последовательность значений. У него есть одно ключевое требование: геттер iterator, который возвращает Iterator. Iterator — это то, что на самом деле перебирает элементы один за другим:

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
  }
}

Теперь Countdown работает с циклами for-in и всеми методами Iterable, такими как map, where и fold. Этот паттерн важен при создании пользовательских коллекций, которые бесшовно интегрируются с экосистемой коллекций Dart.

challenge icon

Задание

Легко

Давайте создадим собственный генератор диапазона, который можно перебирать с помощью цикла for-in! Вы создадите свои собственные классы Iterable и Iterator для генерации последовательности чисел в заданном диапазоне.

Вы организуете свой код в двух файлах:

  • range.dart: Создайте два класса, которые работают вместе для создания последовательности чисел:
    • Класс RangeIterator, который реализует Iterator<int>. Он должен отслеживать текущую позицию и знать, когда остановиться. Итератор начинается перед первым элементом, поэтому moveNext() должен быть вызван перед обращением к current.
    • Класс Range, который расширяет Iterable<int>. Он принимает значения start и end в своем конструкторе и предоставляет геттер iterator, который возвращает RangeIterator.
    Диапазон должен включать как start, так и end. Например, Range(1, 5) должен выдавать: 1, 2, 3, 4, 5.
  • main.dart: Импортируйте ваш файл range и продемонстрируйте работу вашего итерируемого объекта:
    • Создайте Range(1, 5) и используйте цикл for-in, чтобы вывести каждое число на отдельной строке
    • Выведите пустую строку
    • Создайте Range(10, 13) и используйте метод map, чтобы удвоить каждое значение, затем выведите полученный список

Поскольку ваш класс Range расширяет Iterable, он автоматически получает доступ ко всем мощным методам коллекций, таким как map, where и fold — в этом и заключается прелесть реализации интерфейса 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, затем 2, затем 3
}

Чтобы сделать пользовательский класс итерируемым, реализуйте интерфейс 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 и автоматически получают доступ ко всем методам Iterable, таким как map, where и fold.

Попробуйте сами

import 'range.dart';

void main() {
  // TODO: Создайте Range(1, 5) и используйте цикл for-in для вывода каждого числа
  
  
  // TODO: Выведите пустую строку
  
  
  // TODO: Создайте Range(10, 13), используйте map для удвоения каждого значения и выведите список
  
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование