for (variable in c("Hola", "cómo", "estás")) {
print("Esto es una repetición.")
}
[1] "Esto es una repetición."
[1] "Esto es una repetición."
[1] "Esto es una repetición."
Las estructuras de control iterativas son útiles cuando la solución de un problema requiere que se ejecute repetidamente un determinado conjunto de acciones. El número de veces que se debe repetir dicha secuencia de acciones puede ser fijo o puede variar dependiendo de algún dato o condición a evaluar en el programa. Dependiendo de esto, podemos hacer uso de diferentes estructuras de control iterativas, como las de tipo for o las de tipo while, que presentamos en este capítulo.
Una estructura for se aplican cuando se conoce de antemano el número exacto de veces que se debe repetir una secuencia de instrucciones dentro de un programa. También se conoce como bucle o loop for. La sintaxis es:
for (variable in conjunto) {
hacer esto }
En lo anterior, conjunto
representa un conjunto de elementos, usualmente números o cadenas de texto. El bloque de instrucciones encerrados entre las llaves (que puede contener una o muchas líneas) se ejecutará tantas veces como elementos haya en conjunto
. Por ejemplo:
for (variable in c("Hola", "cómo", "estás")) {
print("Esto es una repetición.")
}
[1] "Esto es una repetición."
[1] "Esto es una repetición."
[1] "Esto es una repetición."
En el ejemplo, con la expresión c("Hola", "cómo", "estás")
se define un conjunto de tres cadenas de texto. Dado que hay tres elementos en ese conjunto, la acción indicada entre las llaves se realiza exactamente tres veces. Dicha acción consiste en imprimir en la consola el mensaje “Esto es una repetición” y se realiza gracias al uso de la función print()
.
¿Y para qué está variable
en esa estructura? Se trata de un objeto que recibe el nombre de variable o índice de iteración. En cada repetición y respetando el orden, el objeto variable
va a recibir el valor de uno de los elementos del conjunto. En este caso, durante la primera iteración, variable
almacena el valor "Hola"
. En la segunda repetición, el valor "cómo"
y en la tercera, "estás"
. De hecho, si queremos podemos usar este objeto, cuyo valor va cambiando iteración tras iteración, en las acciones que se implementan dentro de las llaves:
for (variable in c("Hola", "cómo", "estás")) {
print("-------------------------")
print("Esto es una repetición:")
print(variable)
}
[1] "-------------------------"
[1] "Esto es una repetición:"
[1] "Hola"
[1] "-------------------------"
[1] "Esto es una repetición:"
[1] "cómo"
[1] "-------------------------"
[1] "Esto es una repetición:"
[1] "estás"
La variable de iteración, que en el ejemplo se llamó variable
, en realidad puede llevar cualquier nombre que queramos. Es común usar sencillamente el nombre i
. También es usual que el conjunto de elementos contenga números:
for (i in c(1, 2, 3, 4, 5)) {
print("-------------------------")
print("En esta iteración i vale:")
print(i)
}
[1] "-------------------------"
[1] "En esta iteración i vale:"
[1] 1
[1] "-------------------------"
[1] "En esta iteración i vale:"
[1] 2
[1] "-------------------------"
[1] "En esta iteración i vale:"
[1] 3
[1] "-------------------------"
[1] "En esta iteración i vale:"
[1] 4
[1] "-------------------------"
[1] "En esta iteración i vale:"
[1] 5
En los elementos del conjunto son números enteros ordenados, se puede usar un atajo con la forma inicio:fin
:
for (i in 1:5) {
print("-------------------------")
print("En esta iteración i vale:")
print(i)
}
[1] "-------------------------"
[1] "En esta iteración i vale:"
[1] 1
[1] "-------------------------"
[1] "En esta iteración i vale:"
[1] 2
[1] "-------------------------"
[1] "En esta iteración i vale:"
[1] 3
[1] "-------------------------"
[1] "En esta iteración i vale:"
[1] 4
[1] "-------------------------"
[1] "En esta iteración i vale:"
[1] 5
El siguiente bloque de código calcula y muestra la tabla de multiplicar del ocho:
for (i in 0:10) {
<- 8 * i
resultado cat("8 x", i, "=", resultado, "\n")
}
8 x 0 = 0
8 x 1 = 8
8 x 2 = 16
8 x 3 = 24
8 x 4 = 32
8 x 5 = 40
8 x 6 = 48
8 x 7 = 56
8 x 8 = 64
8 x 9 = 72
8 x 10 = 80
Para emitir mensajes, podemos usar print()
o cat()
:
print()
tiene la ventaja de que al terminar de emitir el mensaje, agrega un salto de línea: lo próximo que se escriba, aparecerá en un nuevo renglón de la consola. No obstante, no nos permite de forma sencilla concatenar varias piezas de información para armar frases complejas.cat()
nos permite unir cadenas de texto y valores guardados en variables para armar cualquier frase que queramos, separando entre comas cada una de las partes. Sin embargo, no incluye automáticamente un salto de línea: lo próximo que se escriba queda pegado a lo anterior en el mismo renglón. Para evitar esto, incluimos el carácter especial \n
que representa un salto de línea. Si no lo agreamos, el resultado se ve así:for (i in 0:10) {
<- 8 * i
resultado cat("8 x", i, "=", resultado)
}
8 x 0 = 08 x 1 = 88 x 2 = 168 x 3 = 248 x 4 = 328 x 5 = 408 x 6 = 488 x 7 = 568 x 8 = 648 x 9 = 728 x 10 = 80
Imaginemos que queremos escribir un programa que permita calcular la quinta potencia de cualquier número, por ejemplo, \(2^5\). Para esto, se debe tomar el número \(2\) y multiplicarlo por sí mismo \(5\) veces. Por lo tanto, una posible solución es:
<- 2
x <- 1
resultado <- resultado * x
resultado <- resultado * x
resultado <- resultado * x
resultado <- resultado * x
resultado <- resultado * x
resultado cat(x, "a la quinta es igual a", resultado)
2 a la quinta es igual a 32
Ya que sabemos que la multiplicación se debe repetir 5 veces, podemos resumir lo anterior con la siguiente estructura:
<- 2
x <- 1
resultado for (i in 1:5) {
<- resultado * x
resultado
}cat(x, "a la quinta es igual a", resultado)
2 a la quinta es igual a 32
¿Cuál será el valor final de salida
después de ejecutar el siguiente código en R?
<- 30
salida for (i in 1:4) {
<- salida - i
salida }
salida
es igual a: .
En otras circunstancias se necesita repetir un bloque de acciones sin conocer con exactitud cuántas veces, sino que esto depende de algún otro aspecto del programa. Las iteraciones deben continuar mientras que se verifique alguna condición, dando lugar a la estructura while, también conocida como bucle o loop controlado por una condición.
En una estructura while (mientras), el conjunto de instrucciones se repite mientras que se siga evaluando como TRUE
una condición declarada al inicio del bloque. Cuando la condición ya no se cumple, el proceso deja de ejecutarse. La sintaxis es:
while (condición) {
hacer esto }
El flujo de ejecución es el siguiente:
condición
.condición
es TRUE
, se ejecuta el bloque de código dentro del while
.condición
.condición
sigue siendo TRUE
, se repite el proceso.condición
es FALSE
, el bucle termina y el programa continúa con las instrucciones que siguen después del while
.La variable y
tiene un valor inicial igual a 5. Dado que este valor es mayor a 0, se ejecuta por primera vez el bloque entre llaves: le restamos 1 e imprimimos el resultado, el valor 4. Este valor sigue siendo mayor que 0, por lo tanto el proceso se repite. En una de las iteraciones, y
queda con el valor 1. Dado que es mayor que 0, la condición es TRUE
, a y
le restamos uno, queda igual a 0 y lo imprimimos. La condición se evalúa una vez más, pero resulta en FALSE
, por lo tanto no se detiene la repetición.
<- 5
y while (y > 0) {
<- y - 1
y print(y)
}
[1] 4
[1] 3
[1] 2
[1] 1
[1] 0
El valor 0 se imprime porque una vez dentro del bloque, se ejecutan todas sus acciones. El procesamiento no se interrumpe cuando y
toma el valor 0, sino cuando se vuelve a evaluar la condición lógica y esta es FALSE
, después de la última vuelta. En otras palabras, la evaluación de la condición sólo se lleva a cabo al inicio de cada iteración; si la condición se vuelve FALSE
en algún punto durante la ejecución del bloque, el programa no lo nota hasta que termine de ejecutarlo y la condición sea evaluada antes de comenzar la próxima iteración.
En el siguiente bloque intercambiamos de lugar las líneas dentro de las llaves. ¿En qué difiere el resultado?
<- 5
y while (y > 0) {
print(y)
<- y - 1
y }
[1] 5
[1] 4
[1] 3
[1] 2
[1] 1
Como ya observamos, la evaluación de la condición se lleva a cabo antes de cada iteración, incluso antes de ejecutar el código dentro del bloque por primera vez. Si la condición es FALSE
inicialmente, entonces las acciones en el cuerpo de la estructura no se ejecutan nunca:
<- -5
y while (y > 0) {
print(y)
<- y - 1
y }
Este programa divide un número por 2 mientras que el resultado sea mayor o igual a 1 (es decir, hasta encontrar un valor menor que 1):
<- 100
x while (x >= 1) {
<- x / 2
x print(x)
}
[1] 50
[1] 25
[1] 12.5
[1] 6.25
[1] 3.125
[1] 1.5625
[1] 0.78125
¿Cuál será la salida del siguiente código en R?
<- 1
x <- 0
suma
while (suma + x <= 15) {
<- suma + x
suma cat(suma, " ")
<- x + 1
x }
En R existe otra estructura iterativa, muy relacionada con el while. Se trata de la estructura repeat, que repite indefinidamente el bloque de instrucciones entre llaves. Para detener las iteraciones, se incluye dentro del bloque una evaluación lógica con un if y la instrucción break
. Si la condición es TRUE
, se ejecuta el break
, deteniendo el proceso iterativo.
<- 100
x repeat {
<- x / 2
x print(x)
if (x < 1) break
}
[1] 50
[1] 25
[1] 12.5
[1] 6.25
[1] 3.125
[1] 1.5625
[1] 0.78125
En general, no utilizaremos la estructura repeat, pero es otra herramienta disponible.
Así como contamos con la instrucción break
para detener un proceso iterativo (ya sea un for, while o repeat), también contamos con next
para saltar una iteración. Notemos su efecto:
<- 100
x while (x >= 1) {
<- x / 2
x print(x)
}
[1] 50
[1] 25
[1] 12.5
[1] 6.25
[1] 3.125
[1] 1.5625
[1] 0.78125
<- 100
x while (x >= 1) {
<- x / 2
x if (x == 12.5) next
print(x)
}
[1] 50
[1] 25
[1] 6.25
[1] 3.125
[1] 1.5625
[1] 0.78125
Con las sentencias de tipo while se debe tener mucha precaución, puesto que si la evaluación lógica no está bien especificada o nunca deja de ser evaluada como TRUE
, se incurre en un loop infinito: el programa nunca deja de repetir el bloque (al menos hasta que la máquina se tilde o se produzca un error por desbordamiento de memoria, por ejemplo).
La siguiente situación ilustra esto:
<- 9
var while (var < 10) {
<- var - 1
var cat("var =", var, "No puedo parar!!!\n")
}
var = 8 No puedo parar!!!
var = 7 No puedo parar!!!
var = 6 No puedo parar!!!
var = 5 No puedo parar!!!
var = 4 No puedo parar!!!
var = 3 No puedo parar!!!
var = 2 No puedo parar!!!
var = 1 No puedo parar!!!
var = 0 No puedo parar!!!
var = -1 No puedo parar!!!
var = -2 No puedo parar!!!
.
.
.
Si ejecutás ese código, vas a tener que forzar “a mano” el detenimiento del procesamiento, con el botón rojo de “Stop” arriba a la derecha de la consola, o con el atajo CTRL + C
. También podemos implementar un “conteo” de las iteraciones y agregar un break
: si el bloque se repitió, por ejemplo, 15 veces, que se detenga:
<- 0
conteo <- 9
var while (var < 10) {
<- var - 1
var cat("var =", var, "No puedo parar!!!\n")
<- conteo + 1
conteo if (conteo == 15) break
}cat("Se hicieron", conteo, "iteraciones.")
var = 8 No puedo parar!!!
var = 7 No puedo parar!!!
var = 6 No puedo parar!!!
var = 5 No puedo parar!!!
var = 4 No puedo parar!!!
var = 3 No puedo parar!!!
var = 2 No puedo parar!!!
var = 1 No puedo parar!!!
var = 0 No puedo parar!!!
var = -1 No puedo parar!!!
var = -2 No puedo parar!!!
var = -3 No puedo parar!!!
var = -4 No puedo parar!!!
var = -5 No puedo parar!!!
var = -6 No puedo parar!!!
Se hicieron 15 iteraciones.
Las distintas estructuras iterativas se pueden combinar entre sí, dando lugar flujos de ejecución del código altamente flexibles. En el siguiente caso se tienen dos estructuras for
anidadas:
for (i in 1:3) {
for (j in 1:2) {
<- i + j
suma cat("| i vale", i, "| j vale", j, "| La suma es igual a", suma, "|\n")
} }
| i vale 1 | j vale 1 | La suma es igual a 2 |
| i vale 1 | j vale 2 | La suma es igual a 3 |
| i vale 2 | j vale 1 | La suma es igual a 3 |
| i vale 2 | j vale 2 | La suma es igual a 4 |
| i vale 3 | j vale 1 | La suma es igual a 4 |
| i vale 3 | j vale 2 | La suma es igual a 5 |
En primer lugar, i
toma el valor 1, y entonces j
varía de 1 a 2, generando las combinaciones i = 1, j = 1
e i = 1, j = 2
. Luego de que el loop de j
finalice habiendo recorrido todo su campo de variación, comienza la segunda iteración del loop de i
, actualizándose su valor a 2 y comenzando otra vez el loop de j
, que varía de 1 a 2. Así, se generan las combinaciones i = 2, j = 1
e i = 2, j = 2
. Finalmente, se actualiza i
y pasa a valer 3, generando las combinaciones i = 3, j = 1
e i = 3, j = 2
. Para cada combinación, se muestra el valor de la suma entre i
y j
.
Recordemos que las variables de iteración pueden recibir cualquier nombre:
for (guau in 1:3) {
for (miau in 1:2) {
<- guau + miau
suma cat("| guau vale", guau, "| miau vale", miau, "| La suma es igual a", suma, "|\n")
} }
| guau vale 1 | miau vale 1 | La suma es igual a 2 |
| guau vale 1 | miau vale 2 | La suma es igual a 3 |
| guau vale 2 | miau vale 1 | La suma es igual a 3 |
| guau vale 2 | miau vale 2 | La suma es igual a 4 |
| guau vale 3 | miau vale 1 | La suma es igual a 4 |
| guau vale 3 | miau vale 2 | La suma es igual a 5 |
Con estas herramientas, podemos crear programas que nos permitan verificar algunos principios matemáticos. Por ejemplo, sabemos que la suma de los primeros \(n\) números naturales es igual a \(n(n+1)/2\). Entonces, la suma de los 50 primeros números naturales (\(1 + 2 + 3 + ... + 50\)) debe ser igual a \(50 \times 51 / 2 = 1275\). ¿Será verdad?
<- 50
n <- 0
suma for (i in 1:n) {
<- suma + i
suma
} suma
[1] 1275
Ahora que nos quedamos tranquilos de que ese postulado matemático se cumple, si necesitamos esa suma podemos hacer sencillamente:
* (n + 1) / 2 n
[1] 1275
Es momento de dejar volar nuestra imaginación y plantearnos cualquier problema de este estilo que se nos ocurra, ya estamos en condiciones de resolverlo. Por ejemplo, sumemos números naturales hasta que la suma pase el valor 100:
<- 0
suma <- 1
nro_natural while (suma < 100) {
<- suma + nro_natural
suma <- nro_natural + 1
nro_natural
}cat("Se deben sumar los primeros", nro_natural - 1, "números naturales para superar el valor 100.")
Se deben sumar los primeros 14 números naturales para superar el valor 100.
cat("La suma de los primeros", nro_natural - 1, "números naturales es igual a", suma, ".")
La suma de los primeros 14 números naturales es igual a 105 .
Analizar con atención el caso anterior y explicar por qué se utiliza nro_natural - 1
para indicar cuántos números naturales formaron parte de la suma.
¿Alguien sabe cuántos múltiplos de 8 menores a 150 hay? Contemos1:
# cada nro es múltiplo de sí mismo, así que el primero es el mismo 8
<- 8
multiplo # contamos que ya tenemos identificado al primer múltiplo
<- 1
conteo # encontramos los siguientes múltiplos sumando de a 8
while (multiplo < 150) {
print(multiplo)
<- multiplo + 8
multiplo <- conteo + 1
conteo }
[1] 8
[1] 16
[1] 24
[1] 32
[1] 40
[1] 48
[1] 56
[1] 64
[1] 72
[1] 80
[1] 88
[1] 96
[1] 104
[1] 112
[1] 120
[1] 128
[1] 136
[1] 144
cat("Hay", conteo, "múltiplos de 8 menores que 150.")
Hay 19 múltiplos de 8 menores que 150.
Para gente curiosa: este conteo se puede hacer sencillamente con 150 %/% 8 + 1
. ¿Por qué?↩︎