En programación informática , un iterador es un objeto que proporciona acceso progresivamente a cada elemento de una colección , en orden. [1] [2] [3]
Una colección puede proporcionar múltiples iteradores a través de su interfaz que proporcionan elementos en diferentes órdenes, como hacia adelante y hacia atrás.
Un iterador a menudo se implementa en términos de la estructura subyacente a una implementación de colección y a menudo está estrechamente acoplado a la colección para permitir la semántica operativa del iterador.
Un iterador tiene un comportamiento similar al de un cursor de base de datos .
Los iteradores datan del lenguaje de programación CLU en 1974.
Un iterador proporciona acceso a un elemento de una colección ( acceso a elementos ) y puede cambiar su estado interno para proporcionar acceso al siguiente elemento ( recorrido de elementos ). [4] También permite la creación e inicialización de un primer elemento e indica si se han recorrido todos los elementos. En algunos contextos de programación, un iterador proporciona una funcionalidad adicional.
Un iterador permite a un consumidor procesar cada elemento de una colección mientras aísla al consumidor de la estructura interna de la colección. [2] La colección puede almacenar elementos de cualquier manera mientras que el consumidor puede acceder a ellos como una secuencia.
En la programación orientada a objetos, una clase iteradora suele diseñarse en estrecha coordinación con la clase de colección correspondiente. Normalmente, la colección proporciona los métodos para crear iteradores.
A veces, a un contador de bucle también se lo denomina iterador de bucle. Sin embargo, un contador de bucle solo proporciona la funcionalidad de recorrido y no la funcionalidad de acceso a elementos.
Una forma de implementar un iterador es a través de una forma restringida de corrutina , conocida como generador . A diferencia de una subrutina , una corrutina generadora puede generar valores para su invocador varias veces, en lugar de devolverlos solo una vez. La mayoría de los iteradores se pueden expresar naturalmente como generadores, pero debido a que los generadores conservan su estado local entre invocaciones, son particularmente adecuados para iteradores complicados y con estado, como los traversers de árboles . Existen diferencias y distinciones sutiles en el uso de los términos "generador" e "iterador", que varían entre autores e idiomas. [5] En Python , un generador es un constructor de iteradores : una función que devuelve un iterador. A continuación, se muestra un ejemplo de un generador de Python que devuelve un iterador para los números de Fibonacci utilizando la declaración de Python yield
:
def fibonacci ( límite ): a , b = 0 , 1 para _ en rango ( límite ): rendimiento a a , b = b , a + bpara el número en Fibonacci ( 100 ): # El generador construye un iterador print ( número )
Un iterador interno es una función de orden superior (que suele adoptar funciones anónimas ) que recorre una colección mientras aplica una función a cada elemento. Por ejemplo, map
la función de Python aplica una función definida por el llamador a cada elemento:
dígitos = [ 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ]dígitos_cuadrados = map ( lambda x : x ** 2 , dígitos ) # Iterar sobre este iterador daría como resultado 0, 1, 4, 9, 16, ..., 81.
Algunos lenguajes orientados a objetos como C# , C++ (versiones posteriores), Delphi (versiones posteriores), Go , Java (versiones posteriores), Lua , Perl , Python y Ruby proporcionan una forma intrínseca de iterar a través de los elementos de una colección sin un iterador explícito. Un objeto iterador puede existir, pero no está representado en el código fuente. [4] [6]
Un iterador implícito a menudo se manifiesta en la sintaxis del lenguaje como foreach
.
En Python, un objeto de colección se puede iterar directamente:
para valor en iterable : imprimir ( valor )
En Ruby, la iteración requiere acceder a una propiedad del iterador:
iterable . cada uno hace | valor | pone valor fin
Este estilo de iteración a veces se denomina "iteración interna" porque su código se ejecuta completamente dentro del contexto del objeto iterable (que controla todos los aspectos de la iteración) y el programador solo proporciona la operación a ejecutar en cada paso (utilizando una función anónima ).
Los lenguajes que admiten comprensiones de listas o construcciones similares también pueden hacer uso de iteradores implícitos durante la construcción de la lista de resultados, como en Python:
nombres = [ persona . nombre de la persona en la lista si persona . masculino ]
A veces, la naturaleza oculta implícita es solo parcial. El lenguaje C++ tiene algunas plantillas de funciones para iteración implícita, como for_each()
. Estas funciones aún requieren objetos iteradores explícitos como entrada inicial, pero la iteración posterior no expone un objeto iterador al usuario.
Los iteradores son una abstracción útil de los flujos de entrada : proporcionan un objeto iterable (pero no necesariamente indexable) potencialmente infinito. Varios lenguajes, como Perl y Python, implementan flujos como iteradores. En Python, los iteradores son objetos que representan flujos de datos. [7] Las implementaciones alternativas de flujo incluyen lenguajes basados en datos , como AWK y sed .
En lugar de utilizar un iterador, muchos lenguajes permiten el uso de un operador de subíndice y un contador de bucle para acceder a cada elemento. Aunque se puede utilizar la indexación con colecciones, el uso de iteradores puede tener ventajas como: [8]
La capacidad de modificar una colección mientras se itera a través de sus elementos se ha vuelto necesaria en la programación orientada a objetos moderna , donde las interrelaciones entre los objetos y los efectos de las operaciones pueden no ser obvios. Al usar un iterador, uno se aísla de este tipo de consecuencias. Sin embargo, esta afirmación debe tomarse con pinzas, porque la mayoría de las veces, por razones de eficiencia, la implementación del iterador está tan estrechamente ligada a la colección que impide la modificación de la colección subyacente sin invalidarse a sí misma.
En el caso de las colecciones que pueden mover sus datos en la memoria, la única forma de no invalidar el iterador es, en el caso de la colección, llevar un registro de todos los iteradores activos en ese momento y actualizarlos sobre la marcha. Dado que la cantidad de iteradores en un momento dado puede ser arbitrariamente grande en comparación con el tamaño de la colección vinculada, actualizarlos todos perjudicará drásticamente la garantía de complejidad de las operaciones de la colección.
Una forma alternativa de mantener el número de actualizaciones limitado en relación con el tamaño de la colección sería utilizar un tipo de mecanismo de identificador, es decir, una colección de punteros indirectos a los elementos de la colección que deben actualizarse con la colección, y dejar que los iteradores apunten a estos identificadores en lugar de directamente a los elementos de datos. Pero este enfoque afectará negativamente el rendimiento del iterador, ya que debe efectuar un doble puntero a continuación para acceder al elemento de datos real. Esto generalmente no es deseable, porque muchos algoritmos que utilizan los iteradores invocan la operación de acceso a datos de los iteradores con más frecuencia que el método avanzado. Por lo tanto, es especialmente importante tener iteradores con un acceso a datos muy eficiente.
En definitiva, siempre se trata de un equilibrio entre la seguridad (los iteradores siguen siendo siempre válidos) y la eficiencia. La mayoría de las veces, la seguridad añadida no compensa el precio que hay que pagar por ella en términos de eficiencia. Si se necesita la estabilidad de los iteradores, sería mejor utilizar una colección alternativa (por ejemplo, una lista enlazada simple en lugar de un vector) (globalmente más eficiente).
Los iteradores se pueden clasificar según su funcionalidad. A continuación se muestra una lista (no exhaustiva) de categorías de iteradores: [9] [10]
Los tipos de iteradores se definen en distintos lenguajes o bibliotecas que se utilizan con estos lenguajes. Algunos de ellos son [12]
Los iteradores en .NET Framework (es decir, C#) se denominan "enumeradores" y se representan mediante la IEnumerator
interfaz. [15] : 189–190, 344 [16] : 53–54 IEnumerator
proporciona un MoveNext()
método, que avanza al siguiente elemento e indica si se ha alcanzado el final de la colección; [15] : 344 [16] : 55–56 [17] : 89 una Current
propiedad, para obtener el valor del elemento al que se apunta actualmente. [15] : 344 [16] : 56 [17] : 89 Reset()
y un método opcional , [15] : 344 para rebobinar el enumerador a su posición inicial. El enumerador apunta inicialmente a un valor especial antes del primer elemento, por lo que MoveNext()
se requiere una llamada a para comenzar a iterar.
Los enumeradores se obtienen típicamente al llamar al GetEnumerator()
método de un objeto que implementa la IEnumerable
interfaz. [16] : 54–56 [17] : 54–56 una Current
propiedad, para obtener el valor del elemento al que se apunta actualmente; [15] : 344 [16] : 56 [17] : 89 Las clases contenedoras típicamente implementan esta interfaz. Sin embargo, la declaración foreach en C# puede operar en cualquier objeto que proporcione dicho método, incluso si no implementa IEnumerable
( tipado pato ). [17] : 89 Ambas interfaces se expandieron a versiones genéricas en .NET 2.0 .
A continuación se muestra un uso simple de iteradores en C# 2.0:
// versión explícita IEnumerator < MyType > iter = list . GetEnumerator (); mientras ( iter . MoveNext ()) Console . WriteLine ( iter . Current ); // versión implícita foreach ( MyType valor en lista ) Console . WriteLine ( valor );
C# 2.0 también admite generadores: un método que se declara como de retorno IEnumerator
(o IEnumerable
), pero utiliza la yield return
declaración " " para producir una secuencia de elementos en lugar de devolver una instancia de objeto, será transformado por el compilador en una nueva clase que implementa la interfaz adecuada.
El lenguaje C++ hace un amplio uso de iteradores en su biblioteca estándar y describe varias categorías de iteradores que difieren en el repertorio de operaciones que permiten. Estas incluyen iteradores hacia adelante , iteradores bidireccionales e iteradores de acceso aleatorio , en orden de posibilidades crecientes. Todos los tipos de plantilla de contenedor estándar proporcionan iteradores de una de estas categorías. Los iteradores generalizan punteros a elementos de una matriz (que de hecho se pueden usar como iteradores), y su sintaxis está diseñada para parecerse a la de la aritmética de punteros de C , donde los operadores y se utilizan para hacer referencia al elemento al que apunta el iterador y los operadores de aritmética de punteros como se utilizan para modificar iteradores en el recorrido de un contenedor.*
->
++
El recorrido mediante iteradores suele implicar un único iterador variable y dos iteradores fijos que sirven para delimitar un rango que se va a recorrer. La distancia entre los iteradores limitadores, en términos del número de aplicaciones del operador ++
necesarias para transformar el límite inferior en el superior, es igual al número de elementos en el rango designado; el número de valores de iterador distintos involucrados es uno más que eso. Por convención, el iterador limitador inferior "apunta" al primer elemento del rango, mientras que el iterador limitador superior no apunta a ningún elemento del rango, sino más bien justo más allá del final del rango. Para el recorrido de un contenedor completo, el begin()
método proporciona el límite inferior y end()
el límite superior. Este último no hace referencia a ningún elemento del contenedor en absoluto, pero es un valor de iterador válido con el que se puede comparar.
El siguiente ejemplo muestra un uso típico de un iterador.
std :: vector < int > items ; items.push_back ( 5 ); // Añade el valor entero '5' al vector 'items'. items.push_back ( 2 ) ; // Añade el valor entero '2' al vector 'items'. items.push_back (9 ) ; // Añade el valor entero '9' al vector 'items'. for ( auto it = items . begin (), end = items . end (); it != end ; ++ it ) { // Iterar a través de 'items'. std :: cout << * it ; // E imprimir el valor de 'items' para el índice actual. } // En C++11, se puede hacer lo mismo sin usar ningún iterador: for ( auto x : items ) { std :: cout << x ; // Imprimir el valor de cada elemento 'x' de 'items'. } // Ambos bucles for imprimen "529".
Los tipos de iteradores son independientes de los tipos de contenedores con los que se utilizan, aunque a menudo se utilizan ambos en conjunto. La categoría del iterador (y, por lo tanto, las operaciones definidas para él) normalmente depende del tipo de contenedor; por ejemplo, las matrices o los vectores proporcionan iteradores de acceso aleatorio, pero los conjuntos (que utilizan una estructura enlazada como implementación) solo proporcionan iteradores bidireccionales. Un mismo tipo de contenedor puede tener más de un tipo de iterador asociado; por ejemplo, el std::vector<T>
tipo de contenedor permite el recorrido utilizando punteros (sin procesar) a sus elementos (de tipo *<T>
), o valores de un tipo especial std::vector<T>::iterator
, y se proporciona otro tipo para los "iteradores inversos", cuyas operaciones se definen de tal manera que un algoritmo que realice un recorrido habitual (hacia adelante) en realidad hará el recorrido en orden inverso cuando se lo llame con iteradores inversos. La mayoría de los contenedores también proporcionan un const_iterator
tipo independiente, para el cual las operaciones que permitirían cambiar los valores a los que se apunta no se definen intencionalmente.
El recorrido simple de un objeto contenedor o un rango de sus elementos (incluida la modificación de esos elementos a menos que const_iterator
se utilice a) se puede realizar utilizando solo iteradores. Pero los tipos de contenedor también pueden proporcionar métodos como insert
o erase
que modifican la estructura del contenedor en sí; estos son métodos de la clase contenedora, pero además requieren uno o más valores de iterador para especificar la operación deseada. Si bien es posible tener múltiples iteradores que apunten al mismo contenedor simultáneamente, las operaciones de modificación de la estructura pueden invalidar ciertos valores de iterador (el estándar especifica para cada caso si esto puede ser así); usar un iterador invalidado es un error que conducirá a un comportamiento indefinido, y dichos errores no necesitan ser señalados por el sistema de tiempo de ejecución.
La iteración implícita también es parcialmente compatible con C++ mediante el uso de plantillas de funciones estándar, como std::for_each()
, std::copy()
y std::accumulate()
.
Cuando se utilizan, deben inicializarse con iteradores existentes, generalmente begin
y end
, que definen el rango en el que se produce la iteración. Pero no se expone ningún objeto iterador explícito a medida que avanza la iteración. Este ejemplo muestra el uso de for_each
.
ContainerType < ItemType > c ; // Cualquier tipo de contenedor estándar de elementos ItemType. void ProcessItem ( const ItemType & i ) { // Función que procesará cada elemento de la colección. std :: cout << i << std :: endl ; } std :: for_each ( c.begin ( ), c.end (), ProcessItem ) ; // Un bucle de iteración for-each .
Lo mismo se puede lograr usando std::copy
, pasando un std::ostream_iterator
valor como tercer iterador:
std :: copiar ( c.begin () , c.end ( ) , std :: ostream_iterator <ItemType> ( std :: cout , " \ n " ) ) ;
Desde C++11 , se puede utilizar la sintaxis de la función lambda para especificar la operación que se va a iterar en línea, lo que evita la necesidad de definir una función con nombre. A continuación, se muestra un ejemplo de iteración for-each con una función lambda:
ContainerType < ItemType > c ; // Cualquier tipo de contenedor estándar de elementos ItemType. // Un bucle de iteración for-each con una función lambda. std :: for_each ( c.begin ( ), c.end (), [ ] ( const ItemType & i ) { std :: cout << i << std :: endl ; }) ;
Introducida en la versión Java JDK 1.2, la java.util.Iterator
interfaz permite la iteración de clases contenedoras. Cada una Iterator
proporciona un método next()
y , [18] : 294–295 y puede admitir opcionalmente un método [18] : 262, 266. Los iteradores son creados por la clase contenedora correspondiente, normalmente por un método llamado . [19] [18] : 99 [18] : 217 hasNext()
remove()
iterator()
El next()
método avanza el iterador y devuelve el valor al que apunta el iterador. El primer elemento se obtiene en la primera llamada a next()
. [18] : 294–295 Para determinar cuándo se han visitado todos los elementos del contenedor hasNext()
se utiliza el método de prueba. [18] : 262 El siguiente ejemplo muestra un uso simple de iteradores:
Iterador iter = list . iterator ( ); // Iterador<MyType> iter = list.iterator(); // en J2SE 5.0 while ( iter . hasNext ( )) { System . print ( iter . next ()); if ( iter . hasNext ( )) System . print ( " , " ); }
Para demostrar que hasNext()
se puede llamar repetidamente, lo usamos para insertar comas entre los elementos pero no después del último elemento.
Este enfoque no separa adecuadamente la operación de avance del acceso real a los datos. Si el elemento de datos debe usarse más de una vez para cada avance, debe almacenarse en una variable temporal. Cuando se necesita un avance sin acceso a los datos (es decir, para omitir un elemento de datos determinado), el acceso se realiza de todos modos, aunque en este caso se ignora el valor devuelto.
Para los tipos de colección que lo admiten, el remove()
método del iterador elimina el elemento visitado más recientemente del contenedor mientras mantiene el iterador utilizable. Agregar o eliminar elementos llamando a los métodos del contenedor (también desde el mismo hilo ) hace que el iterador sea inutilizable. Un intento de obtener el siguiente elemento lanza la excepción. También se lanza una excepción si no quedan más elementos ( hasNext()
previamente devolvió false).
Además, java.util.List
existe una java.util.ListIterator
con una API similar pero que permite la iteración hacia adelante y hacia atrás, proporciona su índice actual en la lista y permite configurar el elemento de la lista en su posición.
La versión J2SE 5.0 de Java introdujo la Iterable
interfaz para admitir un bucle mejorado for
( foreach ) para iterar sobre colecciones y matrices. Iterable
define el iterator()
método que devuelve un Iterator
. [18] : 266 Usando el bucle mejorado for
, el ejemplo anterior se puede reescribir como
para ( MiTipo obj : lista ) { Sistema . out . print ( obj ); }
Algunos contenedores también utilizan la Enumeration
clase anterior (a partir de la versión 1.0). Esta proporciona métodos hasMoreElements()
y nextElement()
, pero no tiene métodos para modificar el contenedor.
En Scala , los iteradores tienen un amplio conjunto de métodos similares a las colecciones y se pueden usar directamente en bucles for. De hecho, tanto los iteradores como las colecciones heredan de un rasgo base común: scala.collection.TraversableOnce
. Sin embargo, debido al amplio conjunto de métodos disponibles en la biblioteca de colecciones de Scala, como map
, collect
, filter
etc., a menudo no es necesario tratar con iteradores directamente al programar en Scala.
Los iteradores y colecciones de Java se pueden convertir automáticamente en iteradores y colecciones de Scala, respectivamente, simplemente agregando una sola línea
importar scala . colección . JavaConversions . _
al archivo. El JavaConversions
objeto proporciona conversiones implícitas para hacer esto. Las conversiones implícitas son una característica de Scala: métodos que, cuando son visibles en el ámbito actual, insertan automáticamente llamadas a sí mismos en expresiones relevantes en el lugar apropiado para que se verifique su tipo cuando de otra manera no lo harían.
MATLAB admite iteraciones implícitas internas y externas mediante matrices o cell
arreglos "nativos". En el caso de la iteración externa, donde la responsabilidad de avanzar en el recorrido y solicitar los siguientes elementos recae en el usuario, se puede definir un conjunto de elementos dentro de una estructura de almacenamiento de matriz y recorrer los elementos mediante la for
construcción -loop. Por ejemplo,
% Define una matriz de números enteros myArray = [ 1 , 3 , 5 , 7 , 11 , 13 ]; para n = myArray % ... hacer algo con n disp ( n ) % Echo entero a la ventana de comandos fin
recorre una matriz de números enteros utilizando la for
palabra clave.
En el caso de la iteración interna, donde el usuario puede proporcionar una operación al iterador para que la realice sobre cada elemento de una colección, muchos operadores integrados y funciones de MATLAB se sobrecargan para ejecutarse sobre cada elemento de una matriz y devolver una matriz de salida correspondiente de manera implícita. Además, las funciones arrayfun
y cellfun
se pueden aprovechar para realizar operaciones personalizadas o definidas por el usuario sobre matrices y matrices "nativas" cell
, respectivamente. Por ejemplo,
función simpleFun % Define una matriz de números enteros myArray = [ 1 , 3 , 5 , 7 , 11 , 13 ]; % Realizar una operación personalizada sobre cada elemento myNewArray = arrayfun (@( a ) myCustomFun ( a ), myArray ); % Echo de la matriz resultante a la ventana de comandos myNewArrayfunción outScalar = myCustomFun ( inScalar ) % Simplemente multiplica por 2 outScalar = 2 * inScalar ;
define una función principal simpleFun
que aplica implícitamente una subfunción personalizada myCustomFun
a cada elemento de una matriz mediante la función incorporada arrayfun
.
Como alternativa, puede ser deseable abstraer los mecanismos del contenedor de almacenamiento de matriz del usuario mediante la definición de una implementación personalizada de MATLAB orientada a objetos del Patrón Iterador. Una implementación de este tipo que admita la iteración externa se demuestra en el Patrón de diseño de elementos de MATLAB Central File Exchange: Iterador (conductual). Está escrito en la nueva sintaxis de definición de clase introducida con la versión 7.6 (R2008a) del software MATLAB y presenta una cell
realización de matriz unidimensional del Tipo de datos abstractos de lista (ADT) como mecanismo para almacenar un conjunto heterogéneo (en tipo de datos) de elementos. Proporciona la funcionalidad para el recorrido explícito de la lista hacia adelante con los hasNext()
métodos next()
y reset()
para su uso en un while
bucle.
foreach
El bucle de PHP se introdujo en la versión 4.0 y se hizo compatible con objetos como valores en 4.0 Beta 4. [20] Sin embargo, el soporte para iteradores se agregó en PHP 5 a través de la introducción de la interfaz interna [21] . [22] Las dos interfaces principales para la implementación en scripts PHP que permiten que los objetos se iteren a través del bucle son y . El último no requiere que la clase implementadora declare todos los métodos requeridos, en su lugar implementa un método de acceso ( ) que devuelve una instancia de . La biblioteca estándar de PHP proporciona varias clases para trabajar con iteradores especiales. [23] PHP también admite generadores desde 5.5. [24]Traversable
foreach
Iterator
IteratorAggregate
getIterator
Traversable
La implementación más simple es envolver una matriz, esto puede ser útil para sugerencias de tipos y ocultación de información .
espacio de nombres Wikipedia\Iterator ; clase final ArrayIterator extiende \Iterator { matriz privada $array ; función pública __construct ( matriz $matriz ) { $this -> matriz = $matriz ; } función pública rebobinar () : void { echo 'rebobinando' , PHP_EOL ; restablecer ( $this -> array ); } función pública actual () { $valor = actual ( $este -> matriz ); echo "actual: { $valor } " , PHP_EOL ; devolver $valor ; } función pública clave () { $clave = clave ( $this -> array ); echo "clave: { $clave } " , PHP_EOL ; return $clave ; } función pública siguiente () { $valor = siguiente ( $este -> matriz ); echo "siguiente: { $valor } " , PHP_EOL ; devolver $valor ; } función pública válida () : bool { $válido = $este -> actual () !== falso ; echo 'válido: ' , ( $válido ? 'verdadero' : 'falso' ), PHP_EOL ; devolver $válido ; } }
Todos los métodos de la clase de ejemplo se utilizan durante la ejecución de un bucle foreach completo ( foreach ($iterator as $key => $current) {}
). Los métodos del iterador se ejecutan en el siguiente orden:
$iterator->rewind()
asegura que la estructura interna comience desde el principio.$iterator->valid()
devuelve verdadero en este ejemplo.$iterator->current()
El valor devuelto se almacena en $value
.$iterator->key()
El valor devuelto se almacena en $key
.$iterator->next()
avanza al siguiente elemento en la estructura interna.$iterator->valid()
devuelve falso y se cancela el bucle.El siguiente ejemplo ilustra una clase PHP que implementa la Traversable
interfaz, que podría incluirse en una IteratorIterator
clase para actuar sobre los datos antes de que se devuelvan al foreach
bucle. El uso junto con la MYSQLI_USE_RESULT
constante permite que los scripts PHP iteren conjuntos de resultados con miles de millones de filas con muy poco uso de memoria. Estas características no son exclusivas de PHP ni de sus implementaciones de clase MySQL (por ejemplo, la clase también PDOStatement
implementa la interfaz).Traversable
mysqli_report ( MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT ); $mysqli = new \mysqli ( 'host.example.com' , 'nombre de usuario' , 'contraseña' , 'nombre_de_base_de_datos' );// La clase \mysqli_result que se devuelve mediante la llamada al método implementa la interfaz interna Traversable. foreach ( $mysqli -> query ( 'SELECT `a`, `b`, `c` FROM `table`' , MYSQLI_USE_RESULT ) as $row ) { // Actúa sobre la fila devuelta, que es una matriz asociativa. }
Los iteradores en Python son una parte fundamental del lenguaje y en muchos casos pasan desapercibidos ya que se utilizan de forma implícita en la declaración for
( foreach ), en las comprensiones de listas y en las expresiones de generador . Todos los tipos de colección integrados estándar de Python admiten la iteración, así como muchas clases que forman parte de la biblioteca estándar. El siguiente ejemplo muestra una iteración implícita típica sobre una secuencia:
para valor en secuencia : imprimir ( valor )
Los diccionarios de Python (una forma de matriz asociativa ) también se pueden iterar directamente cuando se devuelven las claves del diccionario; o items()
se puede iterar el método de un diccionario donde produce pares clave-valor correspondientes como una tupla:
para clave en diccionario : valor = diccionario [ clave ] print ( clave , valor )
para clave , valor en el diccionario.items () : print ( clave , valor )
Sin embargo, los iteradores se pueden utilizar y definir explícitamente. Para cualquier tipo o clase de secuencia iterable, se utiliza la función incorporada iter()
para crear un objeto iterador. El objeto iterador se puede iterar luego con la next()
función, que utiliza el __next__()
método internamente, que devuelve el siguiente elemento en el contenedor. (La declaración anterior se aplica a Python 3.x. En Python 2.x, el next()
método es equivalente). StopIteration
Se generará una excepción cuando no queden más elementos. El siguiente ejemplo muestra una iteración equivalente sobre una secuencia utilizando iteradores explícitos:
it = iter ( secuencia ) mientras True : try : valor = it . next () # en Python 2.x valor = next ( it ) # en Python 3.x excepto StopIteration : break print ( valor )
Cualquier clase definida por el usuario puede admitir la iteración estándar (ya sea implícita o explícita) definiendo un __iter__()
método que devuelva un objeto iterador. El objeto iterador debe definir un __next__()
método que devuelva el siguiente elemento.
Los generadores de Python implementan este protocolo de iteración .
Los iteradores en Raku son una parte fundamental del lenguaje, aunque normalmente los usuarios no tienen que preocuparse por ellos. Su uso está oculto tras API de iteración como la for
sentencia, map
, grep
, la indexación de listas con .[$idx]
, etc.
El siguiente ejemplo muestra una iteración implícita típica sobre una colección de valores:
mis @valores = 1 , 2 , 3 ; para @valores -> $valor { digamos $valor}# SALIDA: # 1 # 2 # 3
Los hashes de Raku también se pueden iterar directamente; esto produce Pair
objetos clave-valor. kv
Se puede invocar el método en el hash para iterar sobre la clave y los valores; el keys
método para iterar sobre las claves del hash; y el values
método para iterar sobre los valores del hash.
mi %palabra-a-numero = 'uno' => 1 , 'dos' => 2 , 'tres' => 3 ; para %palabra-a-numero -> $par { decir $par ;}# SALIDA: # tres => 3 # uno => 1 # dos => 2para %palabra-a-número . kv -> $clave , $valor { decir "$clave: $valor" }# SALIDA: # tres: 3 # uno: 1 # dos: 2para %palabra-a-número .keys -> $key { decir " $key => " ~ %palabra-a-número { $key }; }# SALIDA: # tres => 3 # uno => 1 # dos => 2
Sin embargo, los iteradores se pueden utilizar y definir explícitamente. Para cualquier tipo iterable, existen varios métodos que controlan diferentes aspectos del proceso de iteración. Por ejemplo, iterator
se supone que el método debe devolver un Iterator
objeto y pull-one
que debe producir y devolver el siguiente valor si es posible, o devolver el valor centinela IterationEnd
si no se pueden producir más valores. El siguiente ejemplo muestra una iteración equivalente sobre una colección utilizando iteradores explícitos:
mis @values = 1 , 2 , 3 ; mi $it := @values . iterator ; # toma el iterador para @valuesloop { my $value := $it . pull-one ; # toma el siguiente valor de la iteración last if $value =:= IterationEnd ; # para si llegamos al final de la iteración say $value ;}# SALIDA: # 1 # 2 # 3
Todos los tipos iterables en Raku componen el Iterable
rol, Iterator
el rol o ambos. El Iterable
es bastante simple y solo requiere iterator
que el sea implementado por la clase que lo compone. El Iterator
es más complejo y proporciona una serie de métodos como pull-one
, que permite una operación más fina de iteración en varios contextos, como agregar o eliminar elementos, o saltearlos para acceder a otros elementos. Por lo tanto, cualquier clase definida por el usuario puede admitir la iteración estándar al componer estos roles e implementar los métodos iterator
y/o pull-one
.
La DNA
clase representa una cadena de ADN y la implementa iterator
mediante la composición de la Iterable
función. La cadena de ADN se divide en un grupo de trinucleótidos cuando se itera sobre ella:
subconjunto Hebra de Str donde { . match ( /^^ <[ACGT]>+ $$/ ) y . chars %% 3 }; clase DNA hace Iterable { tiene $.chain ; método new ( Hebra:D $chain ) { self . bless: : $chain } método iterador ( DNA:D: ){ $.chain . comb . rotor ( 3 ). iterator }};para el ADN . nuevo ( 'GATTACATA' ) { . decir}# SALIDA: # (GAT) # (TAC) # (ATA)decir ADN . nuevo ( 'GATTACATA' ). mapa (*. unirse ). unirse ( '-' ); # SALIDA: # GAT-TAC-ATA
La Repeater
clase se compone tanto de los roles Iterable
como Iterator
de:
clase Repeater hace Iterable hace Iterator { tiene Any $.item es obligatorio ; tiene Int $.times es obligatorio ; tiene Int $!count = 1 ; método múltiple nuevo ( $item , $times ) { self . bless: : $item , : $times ; } método iterador { self } método pull-one (--> Mu ){ if $!count <= $!times { $!count += 1 ; return $!item } de lo contrario { devolver IteraciónFin } }}para repetidor . new ( "Hola" , 3 ) { . decir}# SALIDA: # Hola # Hola # Hola
Ruby implementa los iteradores de una manera bastante diferente; todas las iteraciones se realizan mediante el paso de cierres de devolución de llamada a los métodos del contenedor; de esta manera, Ruby no solo implementa la iteración básica, sino también varios patrones de iteración, como el mapeo de funciones, los filtros y la reducción. Ruby también admite una sintaxis alternativa para el método de iteración básico each
; los siguientes tres ejemplos son equivalentes:
( 0 ... 42 ) . cada do | n | pone n fin
...y...
para n en 0 ... 42 pone n al final
o incluso más corto
42. veces hace | n | pone n fin
Ruby también puede iterar sobre listas fijas usando Enumerator
s y llamando a su #next
método o haciendo un for each en ellas, como se indicó anteriormente.
Rust utiliza iteradores externos en toda la biblioteca estándar, incluso en su for
bucle, que llama implícitamente al next()
método de un iterador hasta que se consume. El for
bucle más básico, por ejemplo, itera sobre un Range
tipo:
for i in 0 .. 42 { println! ( "{}" , i ); } // Imprime los números del 0 al 41
En concreto, el for
bucle llamará al into_iter()
método de un valor, que devuelve un iterador que, a su vez, devuelve los elementos al bucle. El for
bucle (o, de hecho, cualquier método que consuma el iterador) continúa hasta que el next()
método devuelva un None
valor (las iteraciones que devuelven elementos devuelven un Some(T)
valor, donde T
es el tipo de elemento).
Todas las colecciones proporcionadas por la biblioteca estándar implementan el IntoIterator
atributo (es decir, definen el into_iter()
método). Los iteradores implementan el Iterator
atributo, lo que requiere definir el next()
método. Además, a cualquier tipo que lo implemente Iterator
se le proporciona automáticamente una implementación IntoIterator
que lo retorna.
Los iteradores admiten varios adaptadores ( map()
, filter()
, skip()
, take()
, etc.) como métodos proporcionados automáticamente por el Iterator
rasgo.
Los usuarios pueden crear iteradores personalizados creando un tipo que implemente el Iterator
atributo. Las colecciones personalizadas pueden implementar el IntoIterator
atributo y devolver un tipo de iterador asociado para sus elementos, lo que permite su uso directo en for
bucles. A continuación, el Fibonacci
tipo implementa un iterador personalizado sin límites:
estructura Fibonacci ( u64 , u64 ); impl Fibonacci { pub fn new () -> Self { Self ( 0 , 1 ) } } impl Iterador para Fibonacci { tipo Item = u64 ; fn next ( & mut self ) - > Opción < Self :: Elemento > { let next = self.0 ; self.0 = self.1 ; self.1 = self.0 + next ; Algunos ( siguiente ) } } sea fib = Fibonacci :: new (); para n en fib.skip ( 1 ) .step_by ( 2 ) .take ( 4 ) { println! ( "{n}" ); } // Imprime 1, 2, 5 y 13
Un iterador definido por el usuario suele adoptar la forma de una referencia de código que, cuando se ejecuta, calcula el siguiente elemento de una lista y lo devuelve. Cuando el iterador llega al final de la lista, devuelve un valor acordado.
Los iteradores se introdujeron como construcciones para permitir la realización de bucles sobre estructuras de datos abstractas sin revelar su representación interna.
Puede pensar en un iterador como si apuntara a un elemento que forma parte de un contenedor más grande de elementos.
Un iterador interno se implementa mediante las funciones miembro de la clase que tiene la lógica de iteración. Un iterador externo se implementa mediante una clase separada que se puede adjuntar al objeto que tiene la lógica de iteración. La ventaja del iterador externo es que se pueden activar muchos iteradores simultáneamente en el mismo objeto o en el objeto existente.
{{cite web}}
: CS1 maint: bot: estado de URL original desconocido ( enlace )Algunos autores utilizan el término iterador y otros el término generador. Algunos hacen distinciones sutiles entre ambos.
{{cite web}}
: CS1 maint: bot: estado de URL original desconocido ( enlace )Un índice solo se puede utilizar para contenedores que (de manera eficiente) admiten acceso aleatorio (es decir, acceso directo a un elemento en una posición dada). Un iterador es un concepto más general. Los iteradores ofrecen un recorrido eficiente de listas enlazadas, archivos y una serie de otras estructuras de datos. A menudo conduce a la generación de código más eficiente.
{{cite web}}
: CS1 maint: bot: estado de URL original desconocido ( enlace ){{cite web}}
: CS1 maint: bot: estado de URL original desconocido ( enlace )•Los tipos de iterador iterator y const_iterator pertenecen a la categoría de iterador hacia adelante.