3 Estructuras de control

Como mencionamos anteriormente, un algoritmo está compuesto por una sucesión ordenada de comandos que se ejecutan uno detrás de otro. Sin embargo, con frecuencia es necesario recurrir a comandos especiales que alteran o controlan el orden en el que se ejecutan las acciones. Llamamos estructuras de control del flujo de las acciones al conjunto de reglas que permiten controlar el flujo de las acciones de un algoritmo o programa. Las mismas pueden clasificarse en secuenciales, condicionales e iterativas.

3.1 Estructuras de control secuenciales

Las estructuras secuenciales están compuestas por un número definido de acciones que se ubican en un orden específico y se suceden una tras otra. Los ejemplos que hemos discutido anteriormente están conformados por este tipo de estructura.

3.2 Estructuras de control condicionales

En algunas partes de un algoritmo puede ser útil detenerse a hacer una pregunta porque se llegó a una situación en la que puede haber una o más opciones disponibles para continuar. Dependiendo de la respuesta a la pregunta, que siempre deberá ser VERDADERO (TRUE) o FALSO (FALSE), el algoritmo seguirá ciertas acciones e ignorará otras. Estas preguntas y respuestas representan procesos de toma de decisión que conducen a diferentes caminos dentro del algoritmo, permitiéndonos que la solución para el problema en cuestión sea flexible y se adapte a distintas situaciones. Este tipo de estructuras de control de las acciones reciben el nombre de condicionales (o estructuras de selección) y pueden ser simples, dobles y múltiples.

3.2.1 Estructuras condicionales simples

Postulan una evaluación lógica y, si su resultado es VERDADERO, se procede a ejecutar las acciones encerradas por esta estructura. Se describen en pseudocódigo con la siguiente sintaxis:

SI <condición> ENTONCES
    Acción/es
FIN SI

La palabra SI indica el comando de evaluación lógica, <condición> indica la condición a evaluar y Acción/es son las instrucciones que se realizarán sólo si se cumple la condición, es decir, si la evaluación resulta en VERDADERO. Si la condición no se verifica, no se ejecuta ninguna acción y el algoritmo sigue su estructura secuencial a continuación del FIN SI.

En R, la estructura que nos permite realizar esto es:

if (<condición>) {
    ...código para ejecutar acciones...
}

Por ejemplo, el siguiente algoritmo registra la edad de una persona y, en el caso de que sea mayor de edad, avisa que puede votar en las elecciones provinciales de Santa Fe:

ALGORITMO: "Analizar edad para votar"
COMENZAR
    VARIABLE numérica edad
    LEER edad
    SI edad >= 18 ENTONCES
        ESCRIBIR "Edad = " edad " años: puede votar"
    FIN SI
FIN
# Programa: "Analizar edad para votar" -------------------------------
edad <- 21
if (edad >= 18) {
    cat("Edad =", edad, "años: puede votar")
}
Edad = 21 años: puede votar

Notar que si bien el uso de sangrías en el código es opcional, decidimos emplearlo para facilitar su lectura. Mantener la prolijidad en nuestros programas es esencial.

3.2.2 Estructuras condicionales dobles

Este tipo de estructura añade una acción a ejecutarse en el caso de que la condición evaluada no se verifique (es decir, devuelve el valor FALSO). La sintaxis es:

SI <condición> ENTONCES
  Acción/es
SI NO
  Acción/es
FIN SI

La palabra ENTONCES antecede a las acciones que se realizan si se cumple la condición y la expresión SI NO a las que se realizan si no se verifica la misma.

En R se utiliza el comando else:

if (<condición>) {
    ...código para ejecutar acciones...
} else {
    ...código para ejecutar acciones...
}

Retomando el ejemplo anterior:

ALGORITMO: "Analizar edad para votar"
COMENZAR
    VARIABLE numérica edad
    LEER edad
    SI edad >= 18 ENTONCES
        ESCRIBIR "Edad = " edad " años: puede votar"
    SI NO
        ESCRIBIR "Edad = " edad " años: no puede votar"
    FIN SI
FIN
# Programa: "Analizar edad para votar" -------------------------------
edad <- 21
if (edad >= 18) {
    cat("Edad =", edad, "años: puede votar")
} else {
    cat("Edad =", edad, "años: no puede votar")
}
Edad = 21 años: puede votar

3.2.3 Estructuras condicionales múltiples o anidadas

Permiten combinar varias estructuras condicionales para establecer controles más complejos sobre el flujo de las acciones, representando una toma de decisión múltiple. Podemos ejemplificar la sintaxis de la siguiente forma:

SI <condición 1> ENTONCES
  Acción 1
SI NO
  SI <condición 2> ENTONCES
    Acción 2
  SI NO
    Acción 3
  FIN SI
FIN SI

En la estructura anterior, hay una primera evaluación lógica en la cual si el resultado es VERDADERO, se ejecuta la Acción 1 y nada más. En cambio, si su resultado es FALSO, se procede a realizar una segunda evaluación lógica, que da lugar a la ejecución de la Acción 2 o de la Acción 3 si su resultado es VERDADERO o FALSO, respectivamente.

Se debe notar que luego del primer SI NO comienza una nueva estructura completa de SI/ENTONCES/SI NO/FIN SI. Cada SI termina con su propio FIN SI. Al traducir esto a R, se vuelve algo más sencillo:

if (<condición 1>) {
    ...Acción 1...
} else if (<condición 2>) {
    ...Acción 2...
} else {
    ...Acción 3...
}

El último bloque de acciones (...Acción 3...) se evaluará si ninguna de las condiciones lógicas anteriores fue VERDADERO.

En el ejemplo de la edad:

ALGORITMO: "Analizar edad para votar"
COMENZAR
    VARIABLE numérica edad
    LEER edad
    SI edad < 18 ENTONCES
        ESCRIBIR "Edad = " edad " años: no puede votar"
    SI NO
        SI edad >= 70 ENTONCES
            ESCRIBIR "Edad = " edad " años: puede votar opcionalmente"
        SI NO
            ESCRIBIR "Edad = " edad " años: debe votar obligatoriamente"
        FIN SI
    FIN SI
FIN
# Programa: "Analizar edad para votar" -------------------------------
edad <- 21
if (edad < 18) {
    cat("Edad =", edad, "años: no puede votar")
} else if (edad >= 70) {
    cat("Edad =", edad, "años: puede votar opcionalmente")
} else {
    cat("Edad =", edad, "años: debe votar obligatoriamente")
}
Edad = 21 años: debe votar obligatoriamente

3.3 Estructuras de control iterativas

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 algoritmo.

3.3.1 Estructuras de control iterativas con un número fijo de iteraciones

Se aplican cuando se conoce de antemano el número exacto de veces que se debe repetir una secuencia de acciones. También se conocen como bucles (loops) controlados por un conteo, ya que el algoritmo va contando la cantidad de repeticiones haciendo uso de una variable que recibe el nombre de variable de iteración, índice o conteo.

Por ejemplo, imaginemos que queremos escribir un algoritmo que permita calcular la quinta potencia de cualquier número. Para esto, se debe tomar dicho número y multiplicarlo por sí mismo 5 veces. Por lo tanto, una posible solución es:

ALGORITMO: "Calcular la quinta potencia"
COMENZAR
    VARIABLE numérica x, resultado
    LEER x
    resultado <- 1
    resultado <- resultado * x
    resultado <- resultado * x
    resultado <- resultado * x
    resultado <- resultado * x
    resultado <- resultado * x
    ESCRIBIR x "elevado a la quinta es igual a" resultado
FIN

Ya que sabemos que la multiplicación se debe repetir 5 veces, podemos resumir lo anterior con la siguiente estructura:

ALGORITMO: "Calcular la quinta potencia"
COMENZAR
    VARIABLE numérica x, resultado
    LEER x
    resultado <- 1
    PARA i DESDE 1 HASTA 5 HACER
        resultado <- resultado * x
    FIN PARA
    ESCRIBIR x "elevado a la quinta es igual a" resultado
FIN

La letra i es la variable de iteración. Podríamos haber elegido otra letra u otra palabra en su lugar, pero emplear i es una elección bastante común. En este ejemplo, su única función es ir contando la cantidad de veces que se repiten las acciones encerradas dentro de la estructura PARA/FIN PARA. El bloque de instrucciones se repite tantas veces como i tarde en llegar a 5 partiendo desde 1. Por convención, a la variable de iteración no la declaramos junto con las otras variables numéricas (como x y resultado).

En R, el ejemplo anterior se implementa así:

# Programa: "Calcular la quinta potencia" ------------------------
x <- 4
resultado <- 1
for (i in 1:5) {
    resultado <- resultado * x
}
cat(x, "elevado a la quinta es igual a", resultado)
4 elevado a la quinta es igual a 1024

Dado que la variable de iteración toma un valor numérico que va cambiando en cada repetición del bloque, se puede aprovechar para hacer cuentas con el mismo. Por ejemplo, el siguiente algoritmo muestra la tabla del ocho:

ALGORITMO: "Mostrar tabla del 8"
COMENZAR
    VARIABLE numérica resultado
    PARA i DESDE 0 HASTA 10 HACER
        resultado <- 8 * i
        ESCRIBIR "8 x" i "=" resultado
    FIN PARA
FIN
# Programa: "Mostrar tabla del 8" -------------------------------
for (i in 0:10) {
    resultado <- 8 * i
    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 

En lo anterior, \n es un carácter especial que indica “salto de línea”. Si no lo agregamos, los mensajes se imprimirían uno al lado del otro en el mismo renglón:

# Programa: "Mostrar tabla del 8" -------------------------------
for (i in 0:10) {
    resultado <- 8 * i
    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

De manera general, la sintaxis para este tipo de estructuras es:

PARA <variable> DESDE <valor1> HASTA <valor2> CON PASO <valor3> HACER
    Acción/es
FIN PARA

Dado un valor inicial <valor1> asignado a la <variable>, esta se irá aumentando o disminuyendo según el paso <valor3> hasta llegar a tomar el valor <valor2>. Si no se indica el paso se asume que la variable de iteración aumenta de uno en uno. En R:

for (<variable> in seq(<valor1>, <valor2>, <valor3>)) {
    ...Acción/es...
}

Notar en el ejemplo de la quinta potencia que 1:5 es lo mismo que seq(1, 5, 1), pero podemos usar la función seq() en otros contextos más complejos, donde la variable de iteración puede pegar otros saltos en lugar de uno en uno.

3.3.2 Estructuras de control iterativas con un número indeterminado de iteraciones

En otras circunstancias se puede necesitar repetir un bloque de acciones sin conocer con exactitud cuántas veces, si no que esto depende de algún otro aspecto del algoritmo. Las iteraciones pueden continuar mientras que o hasta que se verifique alguna condición, dando lugar a dos tipos de estructuras. Estos casos también se conocen como bucles (loops) controlados por una condición.

3.3.2.1 Mientras que

El conjunto de sentencias se repite mientras que se siga evaluando como VERDADERO una condición declarada al inicio del bloque. Cuando la condición ya no se cumple, el proceso deja de ejecutarse. La sintaxis es:

MIENTRAS QUE <condición> HACER
   Acción/es a repetir
FIN MIENTRAS

En R:

while (<condición>) {
    ...Acción/es a repetir...
}

Observaciones:

  • 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 FALSO inicialmente, entonces las acciones en el cuerpo de la estructura no se ejecutan nunca.
  • 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 FALSO en algún punto durante la ejecución de un bloque, el programa no lo nota hasta que se termine de ejecutar el bloque y la condición sea evaluada antes de comenzar la próxima iteración.

Veamos un ejemplo:

ALGORITMO: "Dividir un número por 2 hasta encontrar un valor menor que 0.01"
COMENZAR
    VARIABLE numérica x
    LEER x
    MIENTRAS QUE x >= 0.01 HACER
        x <- x / 2
        ESCRIBIR x
    FIN MIENTRAS
FIN
x <- 100
while (x >= 0.01) {
    x <- x / 2
    cat(x, "\n")
}
50 
25 
12.5 
6.25 
3.125 
1.5625 
0.78125 
0.390625 
0.1953125 
0.09765625 
0.04882812 
0.02441406 
0.01220703 
0.006103516 

3.3.2.2 Hasta que

A diferencia de la estructura MIENTRAS QUE, la estructura HASTA QUE repite el bloque de acciones hasta que se cumpla una condición, es decir, se ejecuta mientras que dicha condición sea evaluada como FALSA. La sintaxis es:

REPETIR
   Acción/es
HASTA QUE <condición>

Observación: con la estructura MIENTRAS QUE podría ser que el conjunto de sentencias nunca llegue a ejecutarse si desde partida la condición evaluada ya es falsa. Por el contrario, en la estructura HASTA QUE el proceso se realiza al menos una vez, dado que la condición se evalúa al final.

El ejemplo anterior empleando este tipo de estructura:

ALGORITMO: "Dividir un número por 2 hasta encontrar un valor menor que 0.01"
COMENZAR
    VARIABLE numérica x
    LEER x
    REPETIR
        x <- x / 2
        ESCRIBIR x
    HASTA QUE x < 0.01
FIN

En R este tipo de estructura se implementa con la sentencia repeat {}. Si bien a continuación se muestra el correspondiente ejemplo, no vamos a utilizar esta estructura, debido a que su escritura es más compleja y a que generalmente es posible obtener el mismo resultado con un while () {}.

x <- 100
repeat {
    x <- x / 2
    cat(x, "\n")
    if (x < 0.01) break
}
50 
25 
12.5 
6.25 
3.125 
1.5625 
0.78125 
0.390625 
0.1953125 
0.09765625 
0.04882812 
0.02441406 
0.01220703 
0.006103516 

3.3.2.3 Loops infinitos

Con las sentencias de tipo MIENTRAS QUE 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:

var <- 9
while (var < 10) {
    var <- var - 1
    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!!!
...

En R se puede usar la instrucción break para forzar la detención del proceso iterativo si se presenta alguna condición en particular8:

var <- 9
while (var < 10) {
    var <- var - 1
    cat("var =", var, "No puedo parar!!!\n")
    if (var == -3) break
}
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!!!

3.4 Ejemplos

A continuación se presentan algunos otros ejemplos

  • No necesariamente tiene que ser i la variable iteradora, podemos darle cualquier nombre:

    for (guau in 1:5) {
        print(guau)
    }
    [1] 1
    [1] 2
    [1] 3
    [1] 4
    [1] 5

    La sentencia print() también sirve para mostrar resultados. La ventaja con respecto a cat() es que no necesitamos agregar \n para que el siguiente mensaje se escriba en un nuevo renglón, ya que lo agrega por sí sola sin que lo pidamos. La desventaja es que no nos permite crear un mensaje combinando elementos separados entre comas, tal como se puede hacer con cat() (por ejemplo, cat("El valor de x es igual a", x)).

  • Acá tenemos un ejemplo de dos estructuras for anidadas. En primer lugar, i toma el valor 1, y entonces j varía de 1 a 2, generando las combinaciones i = 1, j = 1; 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; i = 2, j = 2. Finalmente, se actualiza i y pasa a valer 3, generando las combinaciones i = 3, j = 1; i = 3, j = 2. Para cada combinación, se muestra el valor de la suma:

    for (i in 1:3) {
        for (j in 1:2) {
            suma <- i + j
            cat("i vale", i, "y j vale", j, ". La suma es igual a", suma, "\n") 
        }
    }
    i vale 1 y j vale 1 . La suma es igual a 2 
    i vale 1 y j vale 2 . La suma es igual a 3 
    i vale 2 y j vale 1 . La suma es igual a 3 
    i vale 2 y j vale 2 . La suma es igual a 4 
    i vale 3 y j vale 1 . La suma es igual a 4 
    i vale 3 y j vale 2 . La suma es igual a 5 
  • Sumar los números naturales del 1 al 5:

    suma <- 0
    for (i in 1:5) {
        suma <- suma + i
    }
    suma
    [1] 15
  • Sumar números naturales hasta que la suma pase el valor 100 y detenerse:

    suma <- 0
    i <- 1
    while (suma < 100) {
        suma <-  suma + i
        i <- i + 1
    }
    suma
    [1] 105
  • Escribir todos los múltiplos de 8 menores que 150:

    ALGORITMO: "Múltiplos de 8 menores a 150"
    COMENZAR
        VARIABLE numérica multiplo
        multiplo <- 8
        MIENTRAS QUE multiplo < 150 HACER
            ESCRIBIR multiplo
            multiplo <- multiplo + 8
        FIN MIENTRAS
    FIN
    # Programa: "Múltiplos de 8 menores a 150" ------------------------
    multiplo <- 8
    while (multiplo < 150) {
        print(multiplo)
        multiplo <- multiplo + 8
    }
    [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