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 la implementación de una 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 cursor de una 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 al elemento ) y puede cambiar su estado interno para proporcionar acceso al siguiente elemento ( recorrido del elemento ). [4] También prevé la creación e inicialización de un primer elemento e indica si se han atravesado todos los elementos. En algunos contextos de programación, un iterador proporciona funcionalidad adicional.
Un iterador permite al consumidor procesar cada elemento de una colección mientras lo aísla de la estructura interna de la colección. [2] La colección puede almacenar elementos de cualquier forma mientras el consumidor puede acceder a ellos como una secuencia.
En la programación orientada a objetos, una clase iteradora generalmente se diseña en estrecha coordinación con la clase de colección correspondiente. Normalmente, la colección proporciona los métodos para crear iteradores.
A veces, un contador de bucle también se denomina iterador de bucle. Sin embargo, un contador de bucle solo proporciona la funcionalidad transversal y no la funcionalidad de acceso al elemento.
Una forma de implementar un iterador es mediante una forma restringida de rutina , conocida como generador . A diferencia de una subrutina , una corrutina generadora puede proporcionar valores a su interlocutor 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 preservan su estado local entre invocaciones, son particularmente adecuados para iteradores complicados y con estado, como los atravesadores 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 iterador : 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 el rango ( límite ): rendimiento a a , b = b , a + bpara número en Fibonacci ( 100 ): # El generador construye un iterador print ( número )
Un iterador interno es una función de orden superior (que a menudo toma funciones anónimas ) que atraviesa una colección mientras aplica una función a cada elemento. Por ejemplo, la función de Python map
aplica una función definida por la persona que llama a cada elemento:
dígitos = [ 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ]squared_digits = map ( lambda x : x ** 2 , digits ) # 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 , Ruby proporcionan una forma intrínseca de iterar a través de los elementos de una colección sin un iterador explícito. Puede existir un objeto iterador, pero no está representado en el código fuente. [4] [6]
Un iterador implícito suele manifestarse 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 final
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 (usando una función anónima ).
Los lenguajes que admiten listas por comprensión o construcciones similares también pueden utilizar 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 es persona . masculino ]
A veces la naturaleza oculta implícita es sólo 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 stream 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 la indexación se puede utilizar con colecciones, el uso de iteradores puede tener ventajas como: [8]
La capacidad de una colección de modificarse mientras se itera a través de sus elementos se ha vuelto necesaria en la programación moderna orientada a objetos , donde las interrelaciones entre los objetos y los efectos de las operaciones pueden no ser obvias. Al utilizar un iterador, uno se aísla de este tipo de consecuencias. Sin embargo, esta afirmación debe tomarse con cautela, 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.
Para las colecciones que pueden moverse alrededor de sus datos en la memoria, la única forma de no invalidar el iterador es, para la colección, realizar de alguna manera un seguimiento de todos los iteradores activos actualmente y actualizarlos sobre la marcha. Dado que el número de iteradores en un momento dado puede ser arbitrariamente grande en comparación con el tamaño de la colección vinculada, actualizarlos todos afectará drásticamente la garantía de complejidad de las operaciones de la colección.
Una forma alternativa de mantener el número de actualizaciones vinculado en relación con el tamaño de la colección sería utilizar una especie de mecanismo de control, 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 realizar un doble puntero para acceder al elemento de datos real. Por lo general, esto no es deseable, porque muchos algoritmos que utilizan iteradores invocan la operación de acceso a datos de los iteradores con más frecuencia que el método avanzado. Por tanto, es especialmente importante tener iteradores con acceso a datos muy eficiente.
Considerándolo todo, esto siempre es una compensación entre seguridad (los iteradores siempre son válidos) y eficiencia. La mayoría de las veces, la seguridad adicional no vale el precio de eficiencia que se paga por ella. Usar una colección alternativa (por ejemplo, una lista enlazada individualmente en lugar de un vector) sería una mejor opción (globalmente más eficiente) si se necesita la estabilidad de los iteradores.
Los iteradores se pueden clasificar según su funcionalidad. Aquí hay una lista (no exhaustiva) de categorías de iteradores: [9] [10]
Los diferentes lenguajes o bibliotecas utilizados con estos lenguajes definen los tipos de iteradores. Algunos de ellos son [12]
Los iteradores en .NET Framework (es decir, C#) se denominan "enumeradores" y están representados por 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 y un Reset()
método opcional, [15] : 344 para rebobinar el enumerador a su posición inicial. El enumerador inicialmente apunta a un valor especial antes del primer elemento, por lo que se requiere una llamada MoveNext()
para comenzar a iterar.
Los enumeradores normalmente se obtienen llamando 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 de contenedor normalmente implementan esta interfaz. Sin embargo, la declaración foreach en C# puede operar en cualquier objeto que proporcione dicho método, incluso si no lo implementa IEnumerable
( tipificación pato ). [17] : 89 Ambas interfaces se ampliaron 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 = lista . ObtenerEnumerador (); while ( iter . MoveNext ()) Consola . WriteLine ( iter . Actual ); // versión implícita de cada uno ( valor MyType en la lista ) Consola . WriteLine ( valor );
C# 2.0 también admite generadores: un método que se declara como de retorno IEnumerator
(o IEnumerable
), pero que utiliza la yield return
instrucció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 implemente 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 se diferencian en el repertorio de operaciones que permiten. Estos incluyen iteradores directos , iteradores bidireccionales e iteradores de acceso aleatorio , en orden de posibilidades crecientes. Todos los tipos de plantillas de contenedores estándar proporcionan iteradores de una de estas categorías. Los iteradores generalizan punteros a elementos de una matriz (que de hecho pueden usarse 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 usan para hacer referencia al elemento al que apunta el iterador y los operadores aritméticos de puntero. Los me gusta se utilizan para modificar iteradores en el recorrido de un contenedor.*
->
++
El recorrido mediante iteradores normalmente implica un único iterador variable y dos iteradores fijos que sirven para delimitar un rango a recorrer. La distancia entre los iteradores limitantes, 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 limitante inferior "apunta" al primer elemento del rango, mientras que el iterador limitante superior no apunta a ningún elemento del rango, sino más bien más allá del final del rango. Para atravesar 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, 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> elementos ; elementos . empujar_hacia atrás ( 5 ); // Agrega el valor entero '5' al vector 'elementos'. elementos . empujar_hacia atrás ( 2 ); // Agrega el valor entero '2' al vector 'elementos'. elementos . empujar_back ( 9 ); // Agrega el valor entero '9' al vector 'elementos'. for ( auto it = elementos . comenzar (); it != elementos . end (); ++ it ) { // Iterar a través de 'elementos'. std :: cout << * eso ; // E imprimir el valor de los 'elementos' para el índice actual. } // En C++11, se puede hacer lo mismo sin utilizar ningún iterador: for ( auto x : items ) { std :: cout << x ; // Imprime el valor de cada elemento 'x' de 'items'. } // Ambos bucles for imprimen "529".
Los tipos de iterador están separados de los tipos de contenedor con los que se usan, aunque los dos a menudo se usan juntos. La categoría del iterador (y por lo tanto las operaciones definidas para él) generalmente depende del tipo de contenedor; por ejemplo, las matrices o los vectores proporcionan iteradores de acceso aleatorio, pero los conjuntos (que utilizan una estructura vinculada 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 ya sea usando punteros (sin formato) a sus elementos (de tipo *<T>
), o valores de un tipo especial std::vector<T>::iterator
, y se proporciona otro tipo más para "iteradores inversos", cuyas operaciones se definen de tal manera que un El algoritmo que realiza un recorrido habitual (hacia adelante) en realidad lo hará en orden inverso cuando se le llame con iteradores inversos. La mayoría de los contenedores también proporcionan un const_iterator
tipo separado, para el cual las operaciones que permitirían cambiar los valores señalados no están definidas 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 use a) se puede realizar usando iteradores únicamente. Pero los tipos de contenedores también pueden proporcionar métodos como insert
o erase
que modifican la estructura del propio contenedor; 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 apuntando al mismo contenedor simultáneamente, las operaciones de modificación de estructura pueden invalidar ciertos valores de iterador (el estándar especifica para cada caso si esto puede ser así); El uso de 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.
C++ también admite parcialmente la iteración implícita mediante el uso de plantillas de funciones estándar,
como std::for_each()
y .std::copy()
std::accumulate()
Cuando se utilizan, deben inicializarse con iteradores existentes, normalmente begin
y end
, que definen el rango en el que se produce la iteración. Pero posteriormente no se expone ningún objeto iterador explícito a medida que avanza la iteración. Este ejemplo muestra el uso de for_each
.
TipoContenedor < TipoArtículo > 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 . comenzar (), c . finalizar (), ProcessItem ); // Un bucle para cada iteración.
Se puede lograr lo mismo usando std::copy
, pasando un std::ostream_iterator
valor como tercer iterador:
std :: copiar ( c . comenzar (), c . terminar (), std :: ostream_iterator < ItemType > ( std :: cout , " \n " ));
Desde C++ 11 , la sintaxis de la función lambda se puede usar para especificar que la operación se itere en línea, evitando la necesidad de definir una función con nombre. A continuación se muestra un ejemplo de iteración para cada uso de una función lambda:
TipoContenedor < TipoArtículo > c ; // Cualquier tipo de contenedor estándar de elementos ItemType. // Un bucle de iteración para cada uno con una función lambda. std :: for_each ( c . comenzar (), 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 de contenedor. Cada uno Iterator
proporciona un método next()
y , [18] : 294–295 y, opcionalmente, puede admitir un método [18] : 262, 266 . Los iteradores son creados por la clase contenedora correspondiente, normalmente mediante un método denominado . [19] [18] : 99 [18] : 217 hasNext()
remove()
iterator()
El next()
método hace avanzar el iterador y devuelve el valor señalado por el iterador. El primer elemento se obtiene con 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 = lista . iterador (); // Iterador<MiTipo> iter = lista.iterador(); // en J2SE 5.0 while ( iter . hasNext ()) { System . afuera . imprimir ( iter . siguiente ()); if ( iter . hasNext ()) Sistema . afuera . imprimir ( ", " ); }
Para mostrar 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 avanzada 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, 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 ) inutiliza el iterador. Un intento de obtener el siguiente elemento genera la excepción. También se lanza una excepción si no quedan más elementos ( hasNext()
anteriormente devolvió falso).
Además, java.util.List
existe una java.util.ListIterator
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 for
bucle mejorado, el ejemplo anterior se puede reescribir como
for ( MiTipo obj : lista ) { System . afuera . imprimir ( obj ); }
Algunos contenedores también utilizan la Enumeration
clase anterior (desde 1.0). Proporciona hasMoreElements()
métodos nextElement()
, pero no tiene métodos para modificar el contenedor.
En Scala , los iteradores tienen un rico 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 rico conjunto de métodos disponibles en la biblioteca de colecciones de Scala, como , map
etc. , a menudo no es necesario tratar directamente con iteradores cuando se programa en Scala.collect
filter
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 escala . recopilación . Conversiones de Java . _
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 alcance actual, insertan automáticamente llamadas a sí mismos en expresiones relevantes en el lugar apropiado para que se escriban cuando de otro modo no lo harían.
MATLAB admite iteraciones implícitas tanto externas como internas utilizando matrices o cell
matrices "nativas". En el caso de una 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 utilizando la for
construcción -loop. Por ejemplo,
% Definir una matriz de números enteros myArray = [ 1 , 3 , 5 , 7 , 11 , 13 ]; for n = myArray %... haz algo con n disp ( n ) % Echo integer hasta el final de la ventana de comandos
atraviesa una matriz de números enteros usando la for
palabra clave.
En el caso de una iteración interna donde el usuario puede proporcionar una operación al iterador para realizarla 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 implícitamente una matriz de salida correspondiente. . Además, las funciones arrayfun
y cellfun
se pueden aprovechar para realizar operaciones personalizadas o definidas por el usuario sobre matrices "nativas" y cell
matrices respectivamente. Por ejemplo,
función simpleFun % Definir 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 ); % Eco de la matriz resultante en 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 utilizando la función incorporada arrayfun
.
Alternativamente, puede ser deseable abstraer del usuario los mecanismos del contenedor de almacenamiento de matriz definiendo una implementación personalizada de MATLAB orientada a objetos del patrón Iterador. Una implementación de este tipo que admite iteración externa se demuestra en Patrón de diseño del elemento de intercambio de archivos central de MATLAB: Iterador (comportamiento). Esto está escrito en la nueva sintaxis de definición de clase introducida con el software MATLAB versión 7.6 (R2008a) y presenta una realización de matriz unidimensional cell
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 un recorrido de lista directo explícito con y métodos para hasNext()
usar en un bucle.next()
reset()
while
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, se agregó soporte para iteradores en PHP 5 mediante 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 . Este último no requiere que la clase implementadora declare todos los métodos requeridos, sino que implementa un método de acceso ( ) que devuelve una instancia de . La biblioteca PHP estándar 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 sencilla es envolver una matriz, lo que puede resultar útil para sugerencias de tipos y ocultación de información .
espacio de nombres Wikipedia\Iterador ; clase final ArrayIterator extiende \Iterator { matriz privada $matriz ; función pública __construct ( matriz $matriz ) { $this -> matriz = $matriz ; } función pública rebobinar () : void { echo 'rebobinado' , PHP_EOL ; restablecer ( $this -> matriz ); } función pública actual () { $valor = actual ( $this -> matriz ); echo "actual: { $valor } " , PHP_EOL ; devolver valor $ ; } tecla de función pública () { $clave = clave ( $this -> matriz ); echo "clave: { $clave } " , PHP_EOL ; devolver clave $ ; } función pública siguiente () { $valor = siguiente ( $this -> matriz ); echo "siguiente: { $valor } " , PHP_EOL ; devolver valor $ ; } función pública válida () : bool { $válida = $this -> 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 comienza 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 de la estructura interna.$iterator->valid()
devuelve falso y el ciclo se cancela.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 clases 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_base_datos' );// La clase \mysqli_result devuelta por 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 usan implícitamente en la declaración for
( foreach ), en listas por comprensión y en expresiones generadoras . Todos los tipos de colecciones integradas 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 el items()
método de un diccionario se puede iterar donde produce los pares clave-valor correspondientes como una tupla:
para clave en el diccionario : valor = diccionario [ clave ] imprimir ( clave , valor )
para clave , valor en el diccionario . elementos (): imprimir ( clave , valor )
Sin embargo, los iteradores se pueden utilizar y definir explícitamente. Para cualquier tipo o clase de secuencia iterable, la función incorporada iter()
se utiliza para crear un objeto iterador. El objeto iterador luego se puede iterar 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 que True : intente : 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. Luego, el objeto iterador necesita 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 los iteradores. Su uso está oculto detrás de las API de iteración, como la for
declaración 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 { diga $valor}# SALIDA: # 1 # 2 # 3
Los hashes de Raku también se pueden iterar directamente; esto produce Pair
objetos clave-valor. El kv
método se puede invocar 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-número = 'uno' => 1 , 'dos' => 2 , 'tres' => 3 ; para % palabra a número -> $par { diga $par ;}# SALIDA: # tres => 3 # uno => 1 # dos => 2para %palabra a número . kv -> $clave , $valor { diga "$clave: $valor" }# SALIDA: # tres: 3 # uno: 1 # dos: 2para %palabra a número . claves -> $clave { decir "$clave => " ~ %palabra a número { $clave };}# 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
se supone 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 @valores = 1 , 2 , 3 ; mi $it := @values . iterador ; # agarrar iterador para @valuesbucle { mi $valor := $it . tirar uno ; # tomar el siguiente valor de la iteración al final si $value =:= IterationEnd ; # detenernos si llegamos al final de la iteración, digamos $valor ;}# SALIDA: # 1 # 2 # 3
Todos los tipos iterables en Raku componen el Iterable
rol, Iterator
el rol o ambos. Es Iterable
bastante simple y solo requiere que iterator
la clase que lo compone lo implemente. Es Iterator
más complejo y proporciona una serie de métodos como pull-one
, que permite una operación de iteración más precisa en varios contextos, como agregar o eliminar elementos, u omitirlos para acceder a otros elementos. Por lo tanto, cualquier clase definida por el usuario puede soportar la iteración estándar componiendo estos roles e implementando los métodos iterator
y/o pull-one
.
La DNA
clase representa una cadena de ADN y la implementa iterator
componiendo el Iterable
rol. La cadena de ADN se divide en un grupo de trinucleótidos cuando se repite:
subconjunto Hebra de Str donde { . partido ( /^^ <[ACGT]>+ $$/ ) y . caracteres %% 3 }; la clase ADN hace Iterable { tiene $.chain ; método nuevo ( Strand:D $cadena ) { self . bendecir: : $cadena } iterador del método ( DNA:D: ){ $.chain . peine . rotor ( 3 ). iterador }};para el ADN . nuevo ( 'GATTACATA' ) { . decir}# SALIDA: # (GAT) # (TAC) # (ATA)digamos ADN . nuevo ( 'GATTACATA' ). mapa (*. unirse ). unirse ( '-' ); # SALIDA: # GAT-TAC-ATA
La Repeater
clase se compone de los roles Iterable
y Iterator
:
clase Repetidor hace Iterable hace Iterador { tiene Cualquier $.item es requerido ; tiene Int $.times es obligatorio ; tiene Int $!count = 1 ; método múltiple nuevo ( $elemento , $veces ) { self . bendecir: : $artículo , : $veces ; } iterador del método { self } método pull-one (--> Mu ){ if $!count <= $!times { $!count += 1 ; devolver $!artículo } else { retornar fin de iteración } }}para repetidor . nuevo ( "Hola" , 3 ) { . decir}# SALIDA: # Hola # Hola # Hola
Ruby implementa iteradores de manera bastante diferente; todas las iteraciones se realizan pasando cierres de devolución de llamada a métodos contenedores; de esta manera Ruby no solo implementa la iteración básica sino también varios patrones de iteración como mapeo de funciones, filtros y 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 uno hace | norte | pone fin
...y...
para n en 0 ... 42 pone n fin
o incluso más corto
42 . veces lo hacen | norte | pone fin
Ruby también puede iterar sobre listas fijas usando Enumerator
s y llamando a su #next
método o haciendo a para cada una de ellas, como se indicó anteriormente.
Rust hace uso de iteradores externos en toda la biblioteca estándar, incluso en su for
bucle, que implícitamente llama al next()
método de un iterador hasta que se consume. El bucle más básico, for
por ejemplo, itera sobre un Range
tipo:
para i en 0 .. 42 { println! ( "{}" , i ); } // Imprime los números del 0 al 41
Específicamente, el for
bucle llamará al into_iter()
método de un valor, que devuelve un iterador que a su vez produce los elementos del bucle. El for
bucle (o de hecho, cualquier método que consuma el iterador) continúa hasta que el next()
método devuelve un None
valor (las iteraciones que producen elementos devuelven un Some(T)
valor, donde T
está el tipo de elemento).
Todas las colecciones proporcionadas por la biblioteca estándar implementan el IntoIterator
rasgo (lo que significa que definen el into_iter()
método). Los propios iteradores implementan el Iterator
rasgo, lo que requiere definir el next()
método. Además, a cualquier tipo de implementación Iterator
se le proporciona automáticamente una implementación IntoIterator
que regresa por sí misma.
Los iteradores admiten varios adaptadores ( ,,,, map()
etc. ) como métodos proporcionados automáticamente por el rasgo.filter()
skip()
take()
Iterator
Los usuarios pueden crear iteradores personalizados creando un tipo que implemente el Iterator
rasgo. Las colecciones personalizadas pueden implementar el IntoIterator
rasgo y devolver un tipo de iterador asociado para sus elementos, lo que permite su uso directamente en for
bucles. A continuación, el Fibonacci
tipo implementa un iterador personalizado e ilimitado:
estructura Fibonacci ( u64 , u64 ); impl Fibonacci { pub fn nuevo () -> Yo { Yo ( 0 , 1 ) } } iterador impl para Fibonacci { tipo elemento = u64 ; fn next ( & mut self ) -> Opción < Self :: Elemento > { let next = self . 0 ; ser . 0 = yo . 1 ; ser . 1 = yo . 0 + siguiente ; Algunos ( siguiente ) } } let fib = Fibonacci :: nuevo (); para n en fib . saltar ( 1 ). paso a paso ( 2 ). tomar ( 4 ) { println! ( "{n}" ); } // Imprime 1, 2, 5 y 13
Un iterador definido por el usuario normalmente toma 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 recorrer estructuras de datos abstractas sin revelar su representación interna.
Puede pensar que un iterador apunta a un elemento que forma parte de un contenedor más grande de elementos.
Las funciones miembro de la clase que tiene la lógica de iteración implementan un iterador interno. Un iterador externo se implementa mediante una clase separada que se puede adjuntar al objeto que tiene lógica de iteración. La ventaja del iterador externo es que muchos iteradores se pueden activar simultáneamente en el mismo objeto o existente.
{{cite web}}
: Mantenimiento CS1: bot: estado de la URL original desconocido ( enlace )Algunos autores utilizan el término iterador y otros el término generador. Algunos hacen distinciones sutiles entre los dos.
{{cite web}}
: Mantenimiento CS1: bot: estado de la URL original desconocido ( enlace )Un índice sólo se puede utilizar para contenedores que (eficientemente) admiten acceso aleatorio (es decir, acceso directo a un elemento en una posición determinada). Un iterador es un concepto más general. Los iteradores ofrecen un recorrido eficiente de listas vinculadas, archivos y otras estructuras de datos. A menudo conduce a la generación de código más eficiente.
{{cite web}}
: Mantenimiento CS1: bot: estado de la URL original desconocido ( enlace ){{cite web}}
: Mantenimiento CS1: bot: estado de la URL original desconocido ( enlace )•Los tipos de iterador iterator y const_iterator son de la categoría de iterador directo