Bienvenido, les saluda Miguel y en esta ocasión les traigo otro nuevo tutorial.
Antes de hacer un estudio y una práctica dedicados, los iterables me resultaban un poco confusos. Si eres como yo, este artículo es para ti. Resulta que no son tan difíciles.
Explicaré qué son los iterables y en qué se diferencian de los iteradores. También te mostraré un ejemplo real de cómo hacer tu propio iterable.
Índice
¿Qué es un iterable?
Un iterable es un tipo de colección en Dart. Es una colección por la que puede moverse secuencialmente un elemento a la vez. List
y Set
son dos ejemplos comunes de colecciones iterables. Queue
es otro, aunque menos común.
Si miras el código fuente de List
, verá lo siguiente:
abstract class List<E> implements EfficientLengthIterable<E> ...
EfficientLengthIterable
es en sí misma una subclase de Iterable
, una clase de la que aprenderá más más adelante. Entonces, por su propia definición, puede ver que las listas son iterables.
A continuación, verá algunos de los beneficios de ser una colección iterable.
Ser capaz de moverse secuencialmente a través de todos los elementos de una colección es un requisito previo para usar un for-in
lazo.
final myList = [2, 4, 6]; for (var number in myList) print(number);
Ya que List
es iterable, puedes iterar sobre él.
Sin embargo, no todas las colecciones de Dart son iterables. Más destacado, Map
no lo es. Es por eso que no puede usar directamente un for-in
bucle con los elementos de Map
colección.
Si intenta hacer lo siguiente:
final myMap = {'a': 1, 'b':2, 'c':3}; for (var element in myMap) { print(element); }
Recibirás un error:
The type 'Map<String, int>' used in the 'for' loop must implement Iterable.
Sin embargo, los mapas tienen keys
y values
propiedades, que son de tipo Iterable
. Eso significa que puede iterar sobre cualquiera de ellos. A continuación, se muestra un ejemplo de iteración sobre el keys
:
final myMap = 'a': 1, 'b':2, 'c':3; for (var key in myMap.keys) { print('key: $key, value: $myMap[key]'); }
Un iterable te da acceso a muchas otras funciones además de poder usarlas con un for-in
lazo. Por ejemplo, hay bastantes métodos de orden superior disponibles, como map
, where
, fold
y expand
.
He aquí un ejemplo de where
método, que es útil para filtrar ciertos elementos de una colección:
const myList = [1, 2, 3, 4, 5, 6, 7, 8]; final evenNumbers = myList.where((element) => element.isEven); print(evenNumbers);
Esto imprime:
(2, 4, 6, 8)
Hay paréntesis alrededor de la colección en lugar de corchetes porque where
devolvió un objeto de tipo Iterable
más bien que List
. Si realmente quieres un List
específicamente, puede utilizar el toList
método que tienen los iterables:
print(evenNumbers.toList());
Esto da los corchetes esperados:
[2, 4, 6, 8]
Cómo crear tu propio iterable
Como aprendiste arriba, List
, Set
, Queue
, y el keys
y values
de Map
son todos iterables, pero ¿qué sucede si desea crear su propio tipo iterable?
Para crear una clase iterable con todos los beneficios descritos anteriormente, debe crear una iterador. La razón es que un iterable en realidad no sabe cómo iterar sobre sus propios elementos.
Sin embargo, todos los iterables tienen un iterador, y el trabajo del iterador es moverse secuencialmente a través de todos los elementos del iterable.
En el siguiente ejemplo, lo guiaré para crear su propia clase iterable junto con su iterador.
En Flutter puedes mostrar la mayoría de las cadenas fácilmente usando un Text
widget. Sin embargo, si desea hacer una representación de texto de bajo nivel, las cosas se ponen un poco más difíciles.
Desafortunadamente, Flutter oculta la API para el interruptor de línea necesario para saber dónde envolver suavemente las cuerdas largas en la siguiente línea. (Ver Mi primera decepción con Flutter y este problema de GitHub para detalles.)
Un separador de línea toma una cadena larga y le indica en todos los lugares de la cadena que puede comenzar una nueva línea sin cortar una palabra por la mitad. El lugar más natural para romper son los espacios, pero Unicode describe muchos más.
En el siguiente ejemplo, creará un iterable simple cuyos elementos son las líneas de texto entre los puntos donde está bien hacer un salto de línea. Dado que esta es solo una demostración básica, solo usará un carácter de espacio como punto de interrupción.
Por ejemplo, dada la siguiente cadena:
This is a long string that I want to iterate over.
Los |
caracteres muestran ubicaciones en las que estaría bien ajustar la línea en:
This |is |a |long |string |that |I |want |to |iterate |over.
Las subcadenas entre el |
Los personajes representan los elementos de su iterable.
Lo primero que debe hacer al hacer un iterable es extender el Iterable
clase.
class TextRuns extends Iterable<String> { TextRuns(this.text); final String text; @override Iterator<String> get iterator => TextRunIterator(text); {
Podría haberlo llamado LineBreaks
, pero me decidí TextRuns
para enfatizar que los elementos de la colección son cadenas.
Tenga en cuenta que el único requisito para un iterable es que tenga un captador llamado iterator
de tipo Iterator
. Como dije antes, los iterables no saben cómo iterar sobre sus propios elementos. Ese es el trabajo del iterador.
Cuando crea su propio iterable, también debe crear su propio iterador. En el código anterior, puede ver que llamé al iterador TextRunIterator
. Como aún no lo ha hecho, lo hará a continuación.
El iterador es donde se realiza todo el trabajo. Los iteradores básicos solo tienen que implementar la siguiente clase abstracta simple:
abstract class Iterator<E> { E get current; bool moveNext(); }
Los E
representa un tipo genérico y significa elemento. Eso significa que puede tener una colección cuyos elementos sean de cualquier tipo.
Si bien hay iterables bidireccionales (el runes
propiedad de String
, por ejemplo), una llanura Iterator
solo se mueve en una dirección a través de la colección. Cuando, moveNext
se llama el iterador elige el siguiente elemento de la colección. Llama a este elemento current
.
Creando la clase básica
Aquí es un comienzo para TextRunIterator
:
class TextRunIterator implements Iterator<String> { TextRunIterator(this.text); final String text; }
Pasará la cadena de texto en el constructor, que proviene del iterable que ya hizo.
Agregar campos privados para los índices de subcadenas
No has implementado current
o moveNext
todavía, pero primero piense en cómo va a iterar sobre las rupturas en una cadena. Para que el texto se ejecute entre las ubicaciones de las pausas, usará String’s substring
método, que tiene un índice inicial y final. Así que agregue los siguientes campos privados a TextRunIterator
:
int _startIndex = 0; int _endIndex = 0;
Aunque no es un requisito, comenzará desde el principio de la cadena, por lo que puede inicializar los índices con 0
.
Añadiendo el getter actual
A continuación, implementará el current
adquiridor. Agrega las siguientes líneas a tu clase:
String _currentTextRun; @override String get current => _currentTextRun;
Por ahora no has hecho nada realmente. Usted establecerá _currentTextRun
en el moveNext
método en sólo un minite. Si la gente trata de conseguir current
antes de que llamen moveNext
ellos obtendrán un null
.
Añadiendo el método moveNext
Finalmente, implemente moveNext
agregando el siguiente código:
@override bool moveNext() { _startIndex = _endIndex; if (_startIndex == text.length){ _currentTextRun = null; return false; } final next = text.indexOf(breakChar, _startIndex); _endIndex = (next != -1) ? next + 1 : text.length; _currentTextRun = text.substring(_startIndex, _endIndex); return true; } final breakChar = RegExp(' ');
Esto es lo que está pasando:
- Al calcular la subcadena,
_startIndex
es inclusivo mientras_endIndex
es exclusivo. Al comienzo de cada intento de encontrar la siguiente subcadena, moverá el índice de inicio al lugar donde terminó la última subcadena. - Los
moveNext
El método devuelve un booleano. Sifalse
, significa que el iterador no puede moverse al siguiente elemento porque no hay más. Por eso, comience por verificar si_startIndex
ha llegado al final del texto. Regresofalse
si tiene. - Luego, encuentra el índice de la siguiente ubicación de un carácter de salto de línea. El comparador de patrones
breakChar
es una expresión regular que coincide con un carácter de espacio, pero podría hacerlo más sofisticado para que coincida también con caracteres adicionales. - Instrumentos de cuerda
indexOf
devoluciones-1
si no hay coincidencia. En ese caso, solo establecerá_endIndex
hasta el final de la cadena. De lo contrario, configure_endIndex
un carácter después del carácter de ruptura (ya que está incluyendo el carácter de ruptura en la ejecución de texto anterior). - Finalmente, establezca
_currentTextRun
a la subcadena representada por_startIndex
y_endIndex
y luego regresatrue
para indicar que los usuarios aún pueden llamarmoveNext
de nuevo.
Eso completa su iterador, lo que también hace que su iterable sea utilizable.
Ahora puede usar su iterable como lo haría con cualquier otro iterable. Aquí está con un for-in
lazo:
const myString = 'This is a long string that I want to iterate over.'; final myIterable = TextRuns(myString); for (var textRun in myIterable) { print(textRun); }
Ejecute eso y verá lo siguiente:
This is a long string that I want to iterate over.
¡Felicidades! ¡Lo hiciste!
Aquí está el código fuente completo. Tú también puedes juega con él en DartPad.
Gracias por leer este artículo.
Añadir comentario