20 CICLOS Y CONDICIONALES

Cuando se requiere ejecutar un mismo conjunto de instrucciones varias veces, resulta práctico establecer un ciclo, el cual podría repetirse un número predeterminado, un número indefinido o incluso un número infinito de veces. Para tal efecto, pueden utilizarse los controladores de flujo for, while y repeat, respectivamente. En otras situaciones se requiere que el conjunto de instrucciones se ejecute una sola vez si se satisface alguna condición, para lo cual se usa el controlador de flujo if o las variantes if ... else e ifelse.

Se utiliza for para controlar el flujo de un ciclo que ha de ejecutarse un número predeterminado de veces. Por su parte, while permite ejecutar el ciclo hasta que deje de satisfacerse una condición. Cuando se usa repeat, el ciclo se ejecutará infinitamente, siendo necesario utilizar algún recurso extraordinario para forzar la terminación del ciclo.

El controlador de flujo if permite establecer una condición, bajo cuyo cumplimiento se ejecutaría un conjunto de instrucciones. La variante if ... else permite definir un conjunto alternativo de instrucciones que habría de ejecutarse en caso de no satisfacerse la condición. Se usa ifelse para evaluar la satisfacción de una condición en cada uno de los elementos de un vector, devolviendo un resultado en caso de satisfacerse y algún otro, en caso contrario.

Es posible usar simultáneamente todos estos controladores de flujo, anidando unos dentro de otros, ya sean de la misma categoría (p. e. varios ciclos for anidados unos dentro de otros) o de categorías diferentes (p. e. condicionales if anidados dentro de ciclos while).

Para todos los casos que se detallan a continuación, se usa el descriptor genérico instrucciones. Tales instrucciones pueden estar conformadas por una única instrucción o por un conjunto de instrucciones. Si se trata de una única instrucción, no es obligatorio escribirla dentro de llaves, como sí lo es cuando se trata de varias instrucciones (cf. sección 2.10 y capítulo 22).

20.1 Número Predeterminado de Ciclos

La siguiente es la sintaxis básica del controlador de flujo for.

for (i in valores) {
  instrucciones
}

Los ciclos que usan el controlador de flujo for se caracterizan por incluir un índice que forma parte de las instrucciones, cuyo valor cambia en cada repetición del ciclo. En la sintaxis ejemplificada anteriormente, i es el índice en cuestión; para su definición, puede usarse cualquier nombre sintácticamente válido en R (cf. sección 12). En cada ciclo, el índice irá tomando secuencialmente cada uno de los valores definidos en el vector de valores.

Es frecuente que el índice tome valores numéricos, definidos a través de una secuencia de enteros, tal y como se ilustra a continuación.

for (dígito in 0:5)
  cat(dígito, "\n")
#> 0 
#> 1 
#> 2 
#> 3 
#> 4 
#> 5

En este caso el índice llamado dígito toma cada uno de los valores enteros entre cero y cinco, acorde con lo definido en la secuencia 0:5 (cf. sección 2.7). Para cada uno de tales valores se ejecuta la instrucción de imprimir dicho dígito y un salto de línea. Inicialmente, dígito toma el primer valor de la serie (0); a continuación se imprime el valor de dígito (0). Ahí concluye el primer ciclo. Antes de iniciar el segundo ciclo, dígito toma el segundo valor de la serie (1); a continuación se imprime el valor de dígito (1). Se continúa de esta manera hasta que dígito toma el último valor de la serie (5) y se ejecuta el último ciclo, esto es, se imprime el valor de dígito (5). Producto de este bucle, se tiene una impresión secuencial de los enteros entre 0 y 5.

Los valores que toma el índice no tienen que ser enteros. Bien podría definirse el siguiente ciclo.

for (ind in seq(1, 1.5, 0.1))
  print(log(ind))
#> [1] 0
#> [1] 0.09531018
#> [1] 0.1823216
#> [1] 0.2623643
#> [1] 0.3364722
#> [1] 0.4054651

En este caso el índice, al que se le ha denominado ind toma los valores definidos en la secuencia, esto es, 1, 1.1, 1.2, 1.3, 1.4 y 1.5 (cf. sección 2.7), cada uno de los cuales se usa como argumento de la función logaritmo natural, cuyo resultado se muestra en la consola, acorde con lo indicado en la instrucción.

También es posible asignar al índice una serie arbitraria de valores, sin que exista requerimiento alguno de intervalo u orden, tal y como se ilustra a continuación.

for (x in c(4, 3.14, -2))
  print(exp(x))
#> [1] 54.59815
#> [1] 23.10387
#> [1] 0.1353353

En este caso, el índice (x) toma secuencialmente cada uno de los tres valores definidos en el vector. Cada uno de tales valores es usado como argumento de la función exponencial; seguidamente, se imprime el resultado por pantalla.

Para la definición de los diferentes valores que tome el índice, ni siquiera es necesario que estos sean numéricos. Bien pueden definirse elementos alfanuméricos, como en el siguiente ciclo.

flores <- c('Amaranthus', 'Clavel', 'Hortensia', 'Gerbera')
for (f in flores)
  print(nchar(f))
#> [1] 10
#> [1] 6
#> [1] 9
#> [1] 7

En este caso, el índice f toma cada uno de los valores del vector flores; para cada uno de tales elementos, se contabiliza el número de caracteres y se imprime el resultado.

20.2 Número indeterminado de ciclos

A continuación, se presenta la sintaxis básica del controlador de flujo while.

while(condición) {
  instrucciones
}

Los ciclos que usan el controlador de flujo while se ejecutan indefinidamente mientras se satisfaga la condición, la cual corresponden a una expresión formulada con base en operadores lógicos, los más comunes de los cuales se muestran en la siguiente tabla.

Operador Descripción Operador Descripción
< Menor que != Diferente
<= Menor o igual que ! Negación
 > Mayor que & Y
>= Mayor o igual que | O
== Igual

Es importante destacar la diferencia existente entre el operador de igualdad “==” y el operador “=”. El operador de igualdad se utiliza para verificar la satisfacción de una condición (que la expresión a la izquierda del operador tenga igual valor que la de su derecha). El operador “=” se utiliza para asignar un valor a un argumento dentro de una función. Aunque eventualmente también es usado como operador de asignación, en lugar del operador “<-”, se desaconseja este uso (cf. capítulo 22).

Cuando se usa el controlador de flujo while, es necesario que el objeto sobre el cual se verifica la satisfacción de la condición forme parte de las instrucciones, con la posibilidad de que estas alteren su valor, constituyendo esta la ruta normal para terminar el ciclo.

epsilon <- 1
while(epsilon > 0.001) {
  epsilon <- epsilon/2
  print(epsilon)
}
#> [1] 0.5
#> [1] 0.25
#> [1] 0.125
#> [1] 0.0625
#> [1] 0.03125
#> [1] 0.015625
#> [1] 0.0078125
#> [1] 0.00390625
#> [1] 0.001953125
#> [1] 0.0009765625

20.3 Número infinito de ciclos

Puesto que el operador de flujo repeat no incorpora una condición para la terminación del ciclo, este se repetiría infinitamente a no ser que se anidara un condicional if (cf. sección 20.4) que incorpore un comando para fin forzado (cf. sección 20.5).

A continuación, se presenta la sintaxis básica del operador de flujo repeat.

repeat {
  instrucciones
}

La definición de un condicional anidado if que fuerce la terminación del ciclo es equivalente a la definición de dicha condición en un ciclo while.

i <- 0
repeat {
  i <- i + 1
  print(i)
  if (i == 7)
    break
}

Un conjunto equivalente de instrucciones con el controlador while tiene la siguiente forma:

i <- 0
while(i != 7) {
  i <- i + 1
  print(i)
}

20.4 Condicional

El controlador de flujo if permite establecer una condición, bajo cuyo cumplimiento se ejecutarían las instrucciones, con base en el siguiente esquema.

if (condición) {
  instrucciones
}

En el siguiente ejemplo se ilustra el uso básico del condicional if, consistente en ejecutar las instrucciones si se satisface la condición y no hacer nada (saltar al siguiente bloque de instrucciones del script) en caso contrario.

if (length(x) != length(y)) {
  cat("¡Atención!", "\n")
  cat("Los vectores no tienen el mismo tamaño")
}

También puede usarse una secuencia de instrucciones similar a la anterior para detener la ejecución de un script, mediante la función stop, incluyendo un aviso personalizado.

if (length(x) != length(y))
  stop("Los vectores no tienen el mismo tamaño")

Aunque el uso y la posición de las llaves parezca un tanto anárquica, existen una serie de acuerdos al respecto, los cuales se presentan en el capítulo 22.

Mediante el condicional if puede verificarse la existencia de un paquete que se requiera en un script, programando su instalación, con sus correspondientes dependencias, en caso de que no esté presente. Para ilustración, considérese el paquete homals.

if (!require(homals))
  install.packages("homals", dependencies = T)
library(homals)

El controlador de flujo if puede complementarse con el controlador else, para especificar un conjunto de instrucciones alternativas que se ejecutarían en caso de no satisfacerse la condición, así:

if (condición) {
  instrucciones
}
else {
  instrucciones alternativas
} 

Al usar la anterior estructura, la llave de apertura de las instrucciones alternativas irá en la misma línea del controlador else. La llave de cierre de un bloque siempre va sola en una línea.

if (length(x) != length(y)) {
  cat("¡Error!", "\n") 
  cat("Los vectores no tienen el mismo tamaño")
}
else {
  z <- (x * y)
  print(z)
}

Es posible encadenar una serie de estructuras if ... else, para incluir todas las alternativas que sean necesarias, derivando a un conjunto de instrucciones diferente para cada uno de los casos considerados.

if (condición1) {
  instrucciones1
}
else if (condición2) {
  instrucciones2
}

.
.
.
}
else if (condiciónk) {
  instruccionesk
}
.
.
.
}
else {
  instruccionesz
}

Si al final de la secuencia if ... else se incluye una opción en la que no se especifique ninguna condición (simplemente else), esta categoría recogería cualquier caso no incluido en las categorías que se hubieran definido anteriormente. Si no se incluyera esta última categoría, únicamente se considerarían las categorías definidas explícitamente.

Supóngase, por ejemplo, que se desea calcular la tarifa para un evento, dependiendo del tipo de asistente.

if (a == "estudiante")
  tarifa <- 80
else if (a == "egresado")
  tarifa <- 100
else if (a == "externo")
  tarifa <- 150
cat("El valor de su inscripción es: ", tarifa)

Una estructura equivalente a if ... else, aplicable a los elementos de un vector o algún otro contenedor de elementos (v. gr. matriz, arreglo, data frame. cf. capítulo 8) es ifelse. En este caso se evalúa la satisfacción de una condición sobre cada uno de los elementos del contenedor y, para cada uno de ellos, se genera una respuesta en caso de satisfacerse la condición y alguna otra ni no se satisface. El resultado es un objeto del mismo tamaño y atributos que el objeto sobre cuyos elementos se evalúa la condición.

ifelse (condición_elem.obj, res.si.verdadero, res.si.falso)

Considérese el siguiente código:

nota <- c(3.2, 4.3, 5.0, 1.5, 2.9)
ifelse (nota >= 3.0, "Aprobado ", "Reprobado")
#> [1] "Aprobado " "Aprobado " "Aprobado " "Reprobado" "Reprobado"

20.5 Salto y fin forzado

Los ciclos for y while tienen una manera de terminación normal. En el caso de los ciclos for, cuando el índice haya tomado todos los valores definidos; en los ciclos while, cuando deje de cumplirse la condición. En ambos casos es posible terminar los ciclos de manera extraordinaria, usando break y next como instrucciones de un condicional interno. Para los ciclos repeat, al no existir una forma de terminación ordinaria, esta es la única forma de interrumpir el ciclo.

Cuando se satisface la condición definida en un condicional interno if y se usa next como instrucciones, se omite la realización del resto de instrucciones del correspondiente ciclo, pasando al ciclo siguiente. Al usar break en condiciones análogas, se interrumpe no solo el bucle presente, sino que se dan por terminados todos los ciclos.

for (dígito in 0:4) {
  if (dígito == 3)
    next
  print(dígito)
}
#> [1] 0
#> [1] 1
#> [1] 2
#> [1] 4

Nótese que en el bucle anterior, cuando el índice dígito tomó el valor 3, se saltó su impresión; sin embargo, se continuó con el siguiente ciclo y se imprimió 4.

epsilon <- 1
while(epsilon > 0.0001) {
  epsilon <- epsilon/2
  if (epsilon == 0.03125)
    break
  print(epsilon)
}
#> [1] 0.5
#> [1] 0.25
#> [1] 0.125
#> [1] 0.0625

En el bucle anterior, en el que se usa la instrucción de salida break, no solamente se deja de imprimir el valor de epsilon cuando toma el valor 0.03125, sino que se dan por terminados todos los ciclos.