Menu
Coddy logo textTech

Iterable e Iterator

Parte de la sección Programación Orientada a Objetos del Journey de Dart de Coddy — lección 68 de 110.

Cada vez que usas un bucle for-in en una List, Set u otra colección, estás trabajando con Iterable e Iterator internamente. Comprender estos conceptos te permite crear tus propias clases sobre las que se puede iterar.

Un Iterable es cualquier objeto que pueda producir una secuencia de valores. Tiene un requisito clave: un getter iterator que devuelve un Iterator. El Iterator es lo que realmente se desplaza a través de los elementos uno por uno:

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

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

El método moveNext() avanza al siguiente elemento y devuelve true si hay uno, o false cuando termina. La propiedad current le proporciona el elemento actual.

Para que tu propia clase sea iterable, implementa la interfaz 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
  }
}

Ahora Countdown funciona con bucles for-in y todos los métodos de Iterable como map, where y fold. Este patrón es esencial al crear colecciones personalizadas que se integran perfectamente con el ecosistema de colecciones de Dart.

challenge icon

Desafío

Fácil

¡Vamos a construir un generador de rangos personalizado que se pueda iterar usando un bucle for-in! Crearás tus propias clases Iterable e Iterator para generar una secuencia de números dentro de un rango especificado.

Organizarás tu código en dos archivos:

  • range.dart: Crea dos clases que trabajen juntas para producir una secuencia de números:
    • Una clase RangeIterator que implemente Iterator<int>. Debe rastrear la posición actual y saber cuándo detenerse. El iterador comienza antes del primer elemento, por lo que se debe llamar a moveNext() antes de acceder a current.
    • Una clase Range que extienda Iterable<int>. Toma un valor start y end en su constructor y proporciona un getter iterator que devuelve un RangeIterator.
    El rango debe incluir tanto start como end. Por ejemplo, Range(1, 5) debería producir: 1, 2, 3, 4, 5.
  • main.dart: Importa tu archivo de rango y demuestra tu iterable personalizado:
    • Crea un Range(1, 5) y usa un bucle for-in para imprimir cada número en su propia línea
    • Imprime una línea vacía
    • Crea un Range(10, 13) y usa el método map para duplicar cada valor, luego imprime la lista resultante

Debido a que tu clase Range extiende Iterable, automáticamente obtiene acceso a todos los potentes métodos de colección como map, where y fold; ¡esa es la belleza de implementar la interfaz Iterable!

Salida esperada:

1
2
3
4
5

[20, 22, 24, 26]

Hoja de referencia

Un Iterable es cualquier objeto que puede producir una secuencia de valores. Requiere un getter iterator que devuelva un Iterator.

Un Iterator se desplaza a través de los elementos uno por uno utilizando:

  • moveNext(): avanza al siguiente elemento y devuelve true si hay uno, o false cuando ha terminado.
  • current: propiedad que proporciona el elemento actual.

Uso manual de un iterador:

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

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

Para hacer que una clase personalizada sea iterable, implemente la interfaz Iterable y cree una clase Iterator correspondiente:

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

Las clases que extienden Iterable funcionan con bucles for-in y obtienen automáticamente acceso a todos los métodos de Iterable como map, where y fold.

Pruébalo tú mismo

import 'range.dart';

void main() {
  // TODO: Crear un Range(1, 5) y usar un bucle for-in para imprimir cada número
  
  
  // TODO: Imprimir una línea vacía
  
  
  // TODO: Crear un Range(10, 13), usar map para duplicar cada valor y imprimir la lista
  
}
quiz iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Programación Orientada a Objetos