La iteración es una repetición de algo. En programación, este término se utiliza en relación con los bucles: subprogramas que se ejecutan varias veces. Una ejecución se llama una iteración.
Los bucles son diferentes. Algunos se ejecutan mientras una condición determinada es verdadera o falsa. Otros se ejecutan un número determinado de veces. En consecuencia, la cantidad de iteraciones puede variar. Hay bucles con un número infinito de iteraciones o bucles que no se ejecutan ninguna vez: en esos casos, habrá cero iteraciones.
Por ejemplo, un bucle debe multiplicar el número a por 10 hasta que sea mayor o igual a 100. Si el valor inicial de a es 10, el bucle se ejecutará una vez: 10 * 10 = 100. Se obtendrá una iteración. Pero si a = 0,1, el bucle se ejecutará tres veces: 0,1 * 10 * 10 * 10 = 100. Se obtendrán tres iteraciones.
Si a inicialmente es, digamos, 101, el bucle no se ejecutará ninguna vez: cero iteraciones.
¿Para qué sirven las iteraciones?
En el desarrollo, a menudo hay tareas en las que se necesita repetir la misma acción varias veces: mostrar un mensaje, obtener o escribir datos, enviar una solicitud, etc. Hacerlo manualmente es incómodo y viola un principio importante de la programación: DRY, don’t repeat yourself, o «no te repitas». En lugar del desarrollador, el bucle repite la acción: realiza la cantidad necesaria de iteraciones.
En un sentido más estricto, las iteraciones ayudan a controlar la ejecución del programa.
Por ejemplo, si le dices a un bucle «ejecútate 10 veces», se ejecutará exactamente ese número de veces; puedes estar seguro de ello.
Por el contrario, si no sabemos cuántas iteraciones necesitamos, el bucle verificará la condición para finalizar en cada una de ellas. Cuando se cumpla la condición establecida por el desarrollador, terminará. Así que no será necesario contar las iteraciones manualmente.
Por lo tanto, las iteraciones y los bucles en general son uno de los conceptos básicos en la programación. Están presentes en casi todos los lenguajes populares: Java, JavaScript, PHP, Python, C++, etc.
¿Cómo funcionan las iteraciones?
Estructura del bucle. Cuando un programador declara un bucle, primero describe sus condiciones: las instrucciones que se deben ejecutar. Por ejemplo, escribe la condición para salir del bucle o el número de repeticiones.
Después de la declaración, se escribe el cuerpo del bucle. Esta es una subprograma que generalmente está separada del código principal mediante llaves o las palabras clave begin y end. En Python, se utilizan sangrías para esto.
Ejecución de la iteración. Cuando el programa llega al bucle, mira la declaración. Si no hay condiciones para salir del bucle, comienza a ejecutarse el código dentro del bucle: su cuerpo. Este código interno lo escribe el desarrollador, puede ser cualquiera.
En cada iteración, el cuerpo del bucle se ejecuta una vez. Luego, el programa vuelve a verificar la condición, y así sucesivamente.
Otra variante de comportamiento. El comportamiento descrito anteriormente se puede modificar. Hay diferentes tipos de bucles: uno verifica la condición antes de entrar en la subprograma, y otro entra directamente en el cuerpo del bucle. La condición la verifica por primera vez después de la primera iteración.
En consecuencia, si la condición es inicialmente falsa (por ejemplo, «repetir mientras 0 sea igual a 1»), el primer tipo de bucle se ejecutará cero veces, y el segundo, una vez. Esto sucede porque el segundo tipo no verifica la condición antes de la primera iteración.
Iteración e iterador
No se debe confundir la iteración con otro concepto similar: el iterador. Así se llama a la variable contador: en ella, el programa registra cuántas veces se ha ejecutado el bucle. Por lo general, los iteradores se utilizan en bucles que deben ejecutarse un número determinado de veces.
Cómo funciona. El iterador más simple funciona así: al final de cada iteración, se le suma una unidad. Una vez que el valor del iterador alcanza un cierto umbral, el bucle se detiene. El conteo suele comenzar desde cero. Con menos frecuencia, desde uno.
El valor del iterador está disponible en el cuerpo del bucle. Se puede comparar con algo más o sumar a otra variable. Pero no se debe modificar el iterador en sí dentro del cuerpo del bucle: el bucle se romperá. Algunos lenguajes lo permiten, pero no se debe hacer así: esto empeora la legibilidad del código.
Cambio de comportamiento. En realidad, en la mayoría de los lenguajes de programación se puede cambiar el comportamiento del iterador. Por ejemplo, sumarle no una unidad, sino otro número. O incluso al revés: restarle una unidad al número-operador hasta que sea igual a 0. Pero la necesidad de esto es rara, y sin una necesidad apremiante es mejor no hacerlo: esto empeora la legibilidad del código.
Una excepción es el recorrido por un objeto, del que hablaremos a continuación. Allí, el comportamiento del iterador difiere del de un contador normal de 0 a n.
Interfaz del iterador. A veces, puede encontrarse con los conceptos «interfaz Iterator», «protocolo Iterator» y similares. No es exactamente lo mismo. En pocas palabras, es un objeto que sabe cómo hacer enumeraciones. Está integrado en algunos tipos de datos. Pero puede encontrarse con él en niveles más avanzados del estudio del desarrollo: sin conocer los fundamentos, es difícil entender qué es y cómo funciona.
También, en algunas funciones estándar de los lenguajes hay iteradores integrados. También funcionan como contadores, pero el desarrollador no tiene que escribir el bucle.
¿Qué significa «objeto iterable»?
Otro concepto de la programación es la iterabilidad. Algunas entidades se denominan iterables o iterable. Si vas a estudiar algún lenguaje moderno, por ejemplo, JavaScript, es muy probable que te encuentres con este concepto.
Los objetos iterables son aquellos cuyo contenido se puede volver a contar. El ejemplo más simple es una cadena: en un bucle, puedes ir desde su primera letra hasta la última. Tiene un número finito de caracteres, y cada uno de ellos tiene una posición estrictamente definida.
El segundo ejemplo de objeto iterable es una matriz. Es una variable en la que se escriben varios valores, y cada uno tiene su propio número de orden. La estructura es similar a una serie numérica. Una matriz también se puede volver a contar: moverse en un bucle desde el primer objeto hasta el último o viceversa.
La necesidad de recorrer estos objetos se encuentra a menudo en el desarrollo, por ejemplo, al analizar, procesar cadenas u otros datos. Por lo tanto, la iterabilidad es un concepto importante.
La interfaz del iterador, que mencionamos anteriormente, debe estar presente en los objetos iterables. Esta es una condición importante. Sin ella, no se podrá volver a contar el objeto.
Iteración en los recorridos por objetos
En muchos lenguajes de programación hay formatos especiales de bucles para recorrer matrices y otros objetos iterables. En esencia, son los mismos bucles con un número determinado de repeticiones, solo con un iterador diferente.
El iterador en estos bucles toma cada valor del contenido del objeto uno tras otro, del primero al último. Analicemos un ejemplo.
Supongamos que en un bucle necesitas recorrer la cadena «escuela». Habrá cinco repeticiones. El iterador tomará sucesivamente los valores «e», «s», «c», «u», «e», «l», «a». Después de esto, llegará al final de la cadena y el bucle terminará.
Este formato es conveniente porque al trabajar con una matriz o una cadena, a menudo es necesario trabajar con elementos individuales de un conjunto. Por ejemplo, una matriz contiene muchos mensajes, y cada uno debe procesarse por separado. Esto se puede hacer fácilmente mediante un recorrido por la matriz en un bucle.
Solo se puede recorrer así los objetos iterables.
Diferencias entre iteraciones y recursión
Otro ejemplo de algoritmo repetitivo es la recursión. Pero funciona según un principio diferente. El enfoque recursivo e iterativo difiere en lo siguiente:
- una subprograma iterativa se llama, se completa y se llama de nuevo varias veces seguidas;
- una función recursiva se llama a sí misma, pero con diferentes argumentos. Como resultado, varias subprogramas anidadas entre sí pueden estar abiertas simultáneamente.
Las funciones recursivas también tienen condiciones para salir, pero se escriben de forma diferente. Si los argumentos cumplen alguna condición, la función devuelve algún valor y se cierra. Este valor se pasa al ejemplo anterior, y así sucesivamente. Se cierran de las internas a las externas.
En las soluciones de algunas tareas, la recursión parece más elegante que las iteraciones, pero este enfoque consume bastante memoria. Por lo tanto, no siempre es óptimo utilizarlo; a menudo se recomienda reemplazarlo por uno iterativo.
Ejemplos de tareas que se resuelven iterativamente
Aquí hay algunos ejemplos de tareas que se pueden resolver convenientemente mediante varias iteraciones con una condición o un número determinado de repeticiones. Esta no es una lista exhaustiva, pero es prácticamente imposible enumerar todas las tareas posibles. Hay demasiadas.
Cálculo cíclico. Un ejemplo simple es el cálculo de alguna expresión matemática, por ejemplo, el factorial o el n-ésimo número de Fibonacci. Por cierto, estas tareas también se pueden resolver de forma recursiva, pero a menudo no es óptimo en términos de uso de memoria.
El factorial es un número que se obtiene si se multiplican sucesivamente entre sí los números del 1 al n. Por ejemplo, el factorial de 5 (escrito como 5!) es 1 * 2 * 3 * 4 * 5 = 120.
Como es necesario multiplicar los números varias veces y de forma sucesiva, aquí sería adecuado un bucle con un número determinado de iteraciones. El iterador contará los valores del 1 al n.
Por cierto, el iterador se puede utilizar en la multiplicación: almacenar en una variable el resultado intermedio y multiplicar por el iterador en cada paso. Después de todo, también cambia sucesivamente del 1 al n. Sin embargo, entonces hay que especificar inmediatamente que el conteo del iterador comience en 1 y no en 0, como es habitual. De lo contrario, todo el resultado será igual a 0.
Recorrido de una matriz u objeto. Imagina que llega del servidor una gran matriz de cadenas. En cada una de ellas hay datos sobre algún producto del sitio. Este es un ejemplo simplificado condicional; en realidad, las características de la transmisión de datos del servidor son algo más complejas.
Es necesario procesar esta matriz de cadenas: extraer partes separadas de cada cadena y escribirlas en los lugares necesarios. Resulta un algoritmo iterativo. Los elementos de la matriz están numerados, y este es un objeto iterable, por lo que se puede recorrer en un bucle especial para el recorrido.
En cada paso del bucle, se pueden realizar las acciones especificadas con el elemento necesario. En el siguiente paso, las mismas acciones se realizarán con otro elemento, y así sucesivamente hasta el final de la matriz. No es necesario escribir varias veces lo mismo, lo hará el programa.
Sin embargo, en algunos lenguajes existen funciones especiales para este tipo de procesamiento de matrices. Con su ayuda, se puede recorrer y procesar un objeto iterable en una sola línea, pero esto no existe en todas partes, y es un nivel más avanzado.
Repetición hasta que se cumpla una condición. Un ejemplo abstracto: cada mes, la cantidad de usuarios del sitio aumenta en promedio un n por ciento. Es necesario analizar cuántos meses pasarán antes de que la cantidad de usuarios alcance un cierto número m. Supongamos que la dinámica del crecimiento permanece igual.
En este caso, no sabemos con exactitud cuántas repeticiones son necesarias. Pero la tarea se reduce a calcular varias veces el n % de la cantidad actual, sumarlo a la cantidad actual y compararlo con el número umbral m. En cada paso, es necesario aumentar el contador en 1: un mes. Esto se puede hacer de forma iterativa.
Se necesitará un bucle con un número indefinido de repeticiones: aquel que se ejecuta mientras una condición determinada es verdadera o falsa. Se puede escribir la condición «repetir hasta que la cantidad de usuarios sea igual o mayor que m», y el programa se repetirá solo el número de veces necesario.
Luego, la subprograma debe devolver el número de meses, es decir, el número real de iteraciones. Por cierto, el bucle se puede utilizar repetidamente con diferentes n y m: para cada conjunto de datos de entrada, el bucle calculará su resultado. En consecuencia, también cambiará el número de iteraciones.
¿Con qué frecuencia es necesario resolver tareas de forma iterativa?
Resolver tareas en un bucle es un algoritmo estándar para muchos lenguajes. Pero en el desarrollo comercial, este enfoque no siempre es aplicable. Por ejemplo, en JavaScript se considera que es mejor prescindir del recorrido de las matrices en un bucle; en su lugar, se recomienda utilizar funciones especiales integradas. Pero en general, las iteraciones se encuentran en muchos lugares, y el enfoque concreto depende en gran medida del lenguaje de programación.
Una persona se enfrenta a las iteraciones desde el principio de su aprendizaje de la programación. Los bucles son parte de la sintaxis básica de casi cualquier lenguaje. Y sin varias iteraciones, no se puede hacer un bucle.
Incluso si posteriormente el programador trabaja principalmente con un lenguaje en el que rara vez se necesitan bucles, aún así debe conocerlos. Es la base, la base que ayuda a comprender la estructura y el principio de funcionamiento de un lenguaje.