Colecciones iterables
Este codelab te enseña cómo usar colecciones que implementan la clase Iterable ↗, por ejemplo List ↗ y Set ↗. Los iterables son bloques de construcción básicos para todo tipo de aplicaciones de Dart, y probablemente ya los estés usando, incluso sin darte cuenta. Este codelab te ayuda a aprovecharlos al máximo.
Utilizando los editores integrados de DartPad, puedes probar tus conocimientos ejecutando código de ejemplo y completando ejercicios.
Para aprovechar al máximo este codelab, debes tener conocimientos básicos de la sintaxis de Dart.
Este codelab cubre el siguiente material:
- Cómo leer elementos de un Iterable.
- Cómo comprobar si los elementos de un Iterable cumplen una condición.
- Cómo filtrar el contenido de un Iterable.
- Cómo asignar el contenido de un Iterable a un valor diferente.
Tiempo estimado para completar este codelab: 60 minutos.
Los ejercicios de este codelab tienen fragmentos de código parcialmente completados. Puedes utilizar DartPad para poner a prueba tus conocimientos completando el código y haciendo clic en el botón Run. No edites el código de tests en la función main
o debajo.
Si necesitas ayuda, expande el menú desplegable Sugerencia o Solución después de cada ejercicio.
¿Qué son las colecciones?
Una colección es un objeto que representa un grupo de objetos, los cuales se llaman elementos. Los Iterables son un tipo de colección.
Una colección puede estar vacía o puede contener muchos elementos. Dependiendo del propósito, las colecciones pueden tener diferentes estructuras e implementaciones. Estos son algunos de los tipos de colección más comunes:
- List: ↗ Se utiliza para leer elementos por sus índices.
- Set: ↗ Se utiliza para contener elementos que pueden aparecer solo una vez.
- Map: ↗ Se utiliza para leer elementos usando una clave.
¿Qué es un Iterable
?
Un Iterable
es una colección de elementos a los que se puede acceder de forma secuencial.
En Dart, un Iterable
es una clase abstracta, lo que significa que no puedes crear una instancia de él directamente. Sin embargo, puedes crear un nuevo Iterable
creando una nueva List
o Set
.
Tanto List
como Set
son Iterable
, por lo que tienen los mismos métodos y propiedades que la clase Iterable
.
Un Map
utiliza una estructura de datos diferente internamente, dependiendo de su implementación. Por ejemplo, HashMap ↗ usa una tabla hash en la que los elementos (también llamados valores) se obtienen usando una clave. Los elementos de un Map
también se pueden leer como objetos Iterable
s utilizando la propiedad entries
o values
del map.
Este ejemplo muestra una List
de int
, que también es un Iterable
de int
:
La diferencia con una List
es que con Iterable
, no puedes garantizar que la lectura de elementos por índice sea eficiente. Iterable
, a diferencia de List
, no tiene el operador []
.
Por ejemplo, considera el siguiente código, que es inválido:
Si lees elementos con []
, el compilador te dice que el operador '[]'
no está definido para la clase Iterable
, lo que significa que no puedes utilizar [index]
en este caso.
En su lugar, puedes leer elementos con elementAt()
, que recorre los elementos del iterable hasta llegar a esa posición.
Continúa con la siguiente sección para aprender más sobre cómo acceder a elementos de un Iterable
.
Leyendo elementos
Puedes leer los elementos de un iterable secuencialmente, usando un bucle for-in
.
Ejemplo: Usar un bucle for-in
El siguiente ejemplo te muestra cómo leer elementos usando un bucle for-in
.
Ejemplo: Usando first
y last
En algunos casos, deseas acceder solo al primer o al último elemento de un Iterable
.
Con la clase Iterable
, no puedes acceder a los elementos directamente, por lo que no puedes llamar a iterable[0]
para acceder al primer elemento. En su lugar, puedes usar first
, que obtiene el primer elemento.
Además, con la clase Iterable
, no puedes usar el operador []
para acceder al último elemento, pero puedes usar la propiedad last
.
En este ejemplo viste cómo usar first
y last
para obtener el primer y último elemento de un Iterable
. También es posible encontrar el primer elemento que satisfaga una condición. La siguiente sección muestra cómo hacerlo usando un método llamado firstWhere()
.
Ejemplo: Usando firstWhere()
Ya viste que puedes acceder a los elementos de un Iterable
de forma secuencial, y puedes obtener fácilmente el primer o el último elemento.
Ahora aprenderás a usar firstWhere()
para encontrar el primer elemento que satisfaga ciertas condiciones. Este método requiere que pases un predicado, que es una función que devuelve verdadero si la entrada satisface una determinada condición.
Por ejemplo, si quieres encontrar el primer String
que tenga más de 5 caracteres, debes pasar un predicado que devuelva true
cuando el tamaño del elemento sea mayor que 5.
Ejecuta el siguiente ejemplo para ver cómo funciona firstWhere()
. ¿Crees que todas las funciones darán el mismo resultado?
En este ejemplo, puedes ver tres formas diferentes de escribir un predicado:
- Como expresión: El código de test tiene una línea que usa sintaxis de flecha (
=>
). - Como bloque: El código de test tiene varias líneas entre paréntesis y una declaración de devolución
return
. - Como función: El código de test está en una función externa que se pasa al método
firstWhere()
como parámetro.
No existe un camino correcto o incorrecto. Utiliza la forma que mejor te funcione y que haz que tu código sea más fácil de leer y comprender.
El ejemplo final llama a firstWhere()
con el parámetro opcional denominado orElse
, que proporciona una alternativa cuando no se encuentra un elemento. En este caso, se devuelve el texto 'None!'
porque ningún elemento satisface la condición proporcionada.
Ejercicio: Practica escribir un predicado de test
El siguiente ejercicio es un test unitario fallido que contiene un fragmento de código parcialmente completo. Tu tarea es completar el ejercicio escribiendo código para aprobar los tests. No es necesario implementar main()
.
Este ejercicio presenta singleWhere()
. Este método funciona de manera similar a firstWhere()
, pero en este caso espera que solo un elemento de Iterable
satisfaga el predicado. Si más de uno o ningún elemento en Iterable
satisface la condición de predicado, entonces el método genera una excepción StateError ↗.
Tu objetivo es implementar el predicado para singleWhere()
que satisfaga las siguientes condiciones:
- El elemento contiene el carácter
'a'
. - El elemento comienza con el carácter
'M'
.
Todos los elementos en los datos de test son strings ↗; Puedes consultar la documentación de la clase para obtener ayuda.
Condiciones de verificación
Cuando trabajas con Iterable
, a veces necesitas verificar que todos los elementos de una colección cumplan alguna condición.
Podrías sentirte tentado a escribir una solución usando un bucle for-in
como este:
Sin embargo, puedes lograr lo mismo usando el método every()
:
El uso del método every()
da como resultado un código más legible, compacto y menos propenso a errores.
Ejemplo: Usar any()
y every()
La clase Iterable
proporciona dos métodos que puedes usar para verificar las condiciones:
any()
: Devuelvetrue
si al menos un elemento satisface la condición.every()
: Devuelvetrue
si todos los elementos cumplen la condición.
Ejecuta este ejercicio para verlos en acción.
En el ejemplo, any()
verifica que al menos un elemento contenga el carácter a
, y every()
verifica que todos los elementos tengan una longitud igual o mayor a 5
.
Después de ejecutar el código, intenta cambiar el predicado de any()
para que devuelva false
:
También puedes usar any()
para verificar que ningún elemento de un Iterable
satisfaga una determinada condición.
Ejercicio: Verificar que un Iterable
satisface una condición
El siguiente ejercicio proporciona práctica en el uso de los métodos any()
y every()
, descritos en el ejemplo anterior. En este caso, trabaja con un grupo de usuarios, representado por objetos User
que tienen el campo miembro age
.
Usa any()
y every()
para implementar dos funciones:
- Parte 1: Implementa
anyUserUnder18()
.- Devuelve
true
si al menos un usuario tiene 17 años o menos.
- Devuelve
- Parte 2: Implementa
everyUserOver13()
.- Devuelve
true
si todos los usuarios tienen 14 años o más.
- Devuelve
Filtrado
Las secciones anteriores cubren métodos como firstWhere()
o singleWhere()
que pueden ayudarte a encontrar un elemento que satisfaga un determinado predicado.
¿Pero qué pasa si quieres encontrar todos los elementos que satisfacen una determinada condición? Puedes lograrlo usando el método where()
.
En este ejemplo, numbers
contiene un Iterable
con múltiples valores int
, y where()
encuentra todos los números pares.
La salida de where()
es otro Iterable
, y puedes usarlo como tal para iterar sobre él o aplicar otros métodos Iterable
. En el siguiente ejemplo, la salida de where()
se usa directamente dentro del bucle for-in
.
Ejemplo: Usando where()
Ejecuta este ejemplo para ver cómo se puede usar where()
junto con otros métodos como any()
.
En este ejemplo, where()
se usa para encontrar todos los números pares, luego any()
se usa para verificar si los resultados contienen un número negativo.
Más adelante en el ejemplo, where()
se usa nuevamente para encontrar todos los números mayores que 1000. Como no hay ninguno, el resultado es un Iterable
vacío.
Ejemplo: Usando takeWhile
Los métodos takeWhile()
y skipWhile()
también pueden ayudarte a filtrar elementos de un Iterable
.
Ejecuta este ejemplo para ver cómo takeWhile()
y skipWhile()
pueden dividir un Iterable
que contiene números.
En este ejemplo, takeWhile()
devuelve un Iterable
que contiene todos los elementos anteriores al que satisface el predicado. Por otro lado, skipWhile()
devuelve un Iterable
que contiene todos los elementos posteriores e incluido al primero que no satisfacen el predicado.
Después de ejecutar el ejemplo, cambia takeWhile()
para tomar elementos hasta que alcance el primer número negativo.
Observa que la condición number.isNegative
se niega con !
.
Ejercicio: Filtrar elementos de una lista
El siguiente ejercicio proporciona práctica usando el método where()
con la clase User
del ejercicio anterior.
Usa where()
para implementar dos funciones:
- Parte 1: Implementa
filterOutUnder21()
.- Devuelve un
Iterable
que contiene a todos los usuarios de 21 años o más.
- Devuelve un
- Parte 2: Implementa
findShortNamed()
.- Devuelve un
Iterable
que contiene todos los usuarios con nombres de longitud 3 o menos.
- Devuelve un
Mapping
Mapear Iterables
con el método map()
te permite aplicar una función sobre cada uno de los elementos, reemplazando cada elemento por uno nuevo.
En este ejemplo, cada elemento de los números Iterable
se multiplica por 10
.
También puedes usar map()
para transformar un elemento en un objeto diferente; por ejemplo, para convertir todo int
en String
, como puedes ver en el siguiente ejemplo:
Ejemplo: Usar map
para cambiar elementos
Ejecuta este ejemplo para ver cómo usar map()
para multiplicar todos los elementos de un Iterable
por 2
. ¿Cuál crees que será el resultado?
Ejercicio: Mapeo a un tipo diferente
En el ejemplo anterior, multiplicaste los elementos de un Iterable
por 2
. Tanto la entrada como la salida de esa operación fueron un Iterable
de int
.
En este ejercicio, tu código toma un Iterable
de User
y necesitas devolver un Iterable
que contiene cadenas que contienen el nombre y la edad de cada usuario.
Cada cadena en Iterable
debe seguir este formato: '{name} is {age}'
; por ejemplo, 'Alice is 21'
.
Ejercicio: Poniéndolo todo junto
Es hora de practicar lo aprendido, en un ejercicio final.
Este ejercicio proporciona la clase EmailAddress
, que tiene un constructor que toma una cadena. Otra función proporcionada es isValidEmailAddress()
, que verifica si una dirección de correo electrónico es válida.
Constructor/función | Firma | Descripción |
---|---|---|
EmailAddress() | EmailAddress(String address) | Crea una EmailAddress para la dirección especificada. |
isValidEmailAddress() | bool isValidEmailAddress(EmailAddress) | Devuelve true si la EmailAddress proporcionada es válida. |
Escribe el siguiente código:
Parte 1: Implementar parseEmailAddresses()
.
- Escribe la función
parseEmailAddresses()
, que toma unIterable<String>
que contiene direcciones de correo electrónico y devuelve unIterable<EmailAddress>
. - Utiliza el método
map()
para asignar desde unaString
aEmailAddress
. - Crea los objetos
EmailAddress
usando el constructorEmailAddress(String)
.
Parte 2: Implementar anyInvalidEmailAddress()
.
- Escribe la función
anyInvalidEmailAddress()
, que toma unIterable<EmailAddress>
y devuelvetrue
si algunaEmailAddress
en elIterable
no es válida. - Utiliza el método
any()
junto con la función proporcionadaisValidEmailAddress()
.
Parte 3: Implementar validEmailAddresses()
.
- Escribe la función
validEmailAddresses()
, que toma unIterable<EmailAddress>
y devuelve otroIterable<EmailAddress>
que contiene solo direcciones válidas. - Utiliza el método
where()
para filtrarIterable<EmailAddress>
. - Utiliza la función proporcionada
isValidEmailAddress()
para evaluar si unaEmailAddress
es válida.
¿Qué sigue?
¡Felicitaciones, terminaste el codelab! Si deseas obtener más información, aquí tiene algunas sugerencias sobre dónde ir a continuación:
- Juega con DartPad ↗.
- Pruebe con otro codelab.
- Lee la referencia de API de Iterable ↗ para obtener información sobre los métodos que no cubre este codelab.