Tema 9 Estructuras condicionales

En este tema vamos a estudiar las instrucciones condicionales. Estas permiten ejecutar un código u otro en función del resultado de evaluar una condición lógica.

9.1 Sentencia if

La instrucción if permite ejecutar un conjunto de instrucciones, u otro conjunto de instrucciones alternativo, en función de una condición. Veamos un ejemplo:

dividendo <- 7
divisor <- 2
if (divisor != 0) {
  cat(dividendo, "/", divisor, "=", dividendo / divisor, "\n")
} else {
  print("No es posible dividir entre 0")
}
## 7 / 2 = 3.5

Observa la sintaxis. La sentencia comienza con la palabra reservada if, seguida de una expresión lógica encerrada entre paréntesis. Después, encerrada entre llaves ({}), viene el conjunto de instrucciones a ejecutar si el resultado de evaluar la expresión lógica es verdadero. La parte else (en otro caso) incluye otro conjunto de instrucciones (encerradas entre llaves) que se ejecutan si la expresión lógica es falsa. Modifica el código previo para que divisor valga 0 y comprueba cómo se ejecutan las instrucciones asociadas a la parte else. Los conjuntos de instrucciones se escriben indentados (es decir, desplazados) a la derecha para facilitar la lectura de la instrucción, aunque sintácticamente no es obligatorio indentar estos conjuntos de instrucciones.

La parte else es opcional, por lo que si no necesitamos ejecutar instrucciones si no se verifica la condición lógica podemos omitirla.

# Convierte la variable x a positiva
(x <- rnorm(1))
## [1] 0.9005119
if (x < 0) {
  print("Cambio el signo")
  x <- -x
}
x
## [1] 0.9005119

Si no existe parte else y sólo queremos ejecutar una instrucción, no es necesario usar las llaves para delimitar las instrucciones:

# Convierte la variable x a positiva
(x <- rnorm(1))
## [1] 0.9418694
if (x < 0) x <- -x
x
## [1] 0.9418694

La condición lógica debe producir un valor escalar de tipo lógico. No tiene sentido que la condición lógica sea un vector de valores lógicos con más de un elemento. El siguiente código produce un error.

v <- c(3, -2, -4)
if (v > 0) {
  print('v es mayor que cero')
}

9.1.1 Sentencias if anidadas

Las instrucciones asociadas a una sentencia if pueden ser de cualquier tipo, incluido la propia instrucción if. Vamos a ver un ejemplo con un código que calcula el máximo de tres valores, almacenados en las variables x, y y z:

x <- 7; y <- 8; z <- 6
if (x > y) {
  if (x > z) {
    mayor <- x
  } else {
    mayor <- z
  }
} else {
  if (y > z) {
    mayor <- y
  } else {
    mayor <- z
  }
}
mayor
## [1] 8

9.1.2 Selección múltiple con la sentencia if

El siguiente fragmento de código, que convierte una calificación numérica en una categórica, tiene la estructura lógica de una selección múltiple, en el sentido de que existen n conjuntos de instrucciones mutuamente excluyentes. Es decir, se dispone de n conjuntos de instrucciones de los que sólo se ejecutará (seleccionará) un conjunto.

(nota <- runif(1, min = 0, max = 10))
## [1] 9.289427
if (nota >= 9) {
  notac <- "Sobresaliente"
} else {
  if (nota >= 7) {
    notac <- "Notable"
  } else {
    if (nota >= 5) {
      notac <- "Aprobado"
    } else {
      notac <- "Suspenso"
    }
  }
}
notac
## [1] "Sobresaliente"

Para implementar una estructura lógica de este tipo el cuerpo de la parte else de todas las instrucciones if viene constituido por una única instrucción if, lo que implica que no es necesario utilizar llaves para delimitar el cuerpo de la parte else. Esto posibilita el escribir la selección múltiple utilizando un formato alternativo como el mostrado a continuación.

(nota <- runif(1, min = 0, max = 10))
## [1] 5.497947
if (nota >= 9) {
  notac <- "Sobresaliente"
} else if (nota >= 7) {
  notac <- "Notable"
} else if (nota >= 5) {
  notac <- "Aprobado"
} else {
  notac <- "Suspenso"
}
notac
## [1] "Aprobado"

Este formato resulta más legible para muchas personas porque todas las expresiones lógicas y conjuntos de instrucciones aparecen al mismo nivel de indentación, reforzando la idea de que es una selección múltiple excluyente, es decir, en la que sólo se ejecuta un conjunto de instrucciones.

9.2 La función ifelse

La función ifelse es una versión vectorizada de la instrucción if. Tiene tres parámetros: un vector de valores lógicos y dos vectores de expresiones. Si los vectores de expresiones tienen menos elementos que el vector lógico, se reciclan a la longitud del vector lógico. Veamos un ejemplo:

v <- c(4, -2, 16, 25, -7)
ifelse(v > 0, v, -v) # equivale a abs(v)
## [1]  4  2 16 25  7

La función ifelse produce un vector del mismo tamaño que el vector de valores lógicos usado como primer parámetro. El funcionamiento es el siguiente: para los índices del vector lógico (primer argumento) con valores de verdadero se usa el elemento del mismo índice del primer vector de expresiones (en el ejemplo v). En caso de que el valor sea falso se usa el elemento del mismo índice del segundo vector de expresiones (en el ejemplo -v). Veamos un ejemplo en el que se recicla el segundo vector de expresiones:

v <- c(4, -2, 16, 25, -7)
ifelse(v > 0, v, 0)
## [1]  4  0 16 25  0
ifelse(v > 0, v, rep(0, length(v))) # equivale al anterior
## [1]  4  0 16 25  0

9.3 La sentencia switch (avanzado)

Esta sección es avanzada y su contenido no es evaluable.

La sentencia if produce un resultado al ser ejecutada: el resultado de evaluar la última instrucción que ejecuta de sus conjuntos de instrucciones asociados. Por ejemplo, supongamos que queremos asignar a la variable signo el valor "positivo" o "negativo" en función del signo de una variable. Esto lo podemos implementar así:

(x <- rnorm(1))
## [1] 0.7067611
if (x >= 0) {
  signo <- "positivo"
} else {
  signo <- "negativo"
}
signo
## [1] "positivo"

Pero también podemos usar el hecho de que if produce un resultado para escribir un código más corto:

signo <- if (x >= 0) "positivo" else "negativo"
signo
## [1] "positivo"

Supongamos ahora que tenemos una sentencia if múltiple, cuyas condiciones equivalen a ver si una expresión coincide con cierta cadena de caracteres. Por ejemplo:

operando1 <- 3
operando2 <- 4
operador <- readline("Operador (+, -, * o /: ")
resultado <- if (operador == '+') {
  operando1 + operando2
} else if (operador == '-') {
  operando1 - operando2
} else if (operador == '*') {
  operando1 * operando2
} else if (operador == '/') {
  operando1 / operando2
} else {
  "operando no contemplado"
}
resultado

En estas circunstancias: una selección múltiple en la que las distintas condiciones se corresponden con ver si una expresión coincide con una cadena de caracteres, se puede usar la instrucción switch:

operando1 <- 3
operando2 <- 4
operador <- readline("Operador (+, -, * o /: ")
resultado <- switch(operador,
                    '+' = operando1 + operando2,
                    '-' = operando1 - operando2,
                    '*' = operando1 * operando2,
                    '/' = operando1 / operando2,
                    "operando no contemplado"
)
resultado

Como puede observarse, el uso de switch produce un código mucho más compacto. Dado que para poder emplear la sentencia switch se requiere unas condiciones poco comunes no profundizaremos más en su uso. El lector interesado puede buscar más información sobre ella.

9.4 Ejercicios

  1. Dadas las siguientes expresiones lógicas trata de calcular si producirán el valor TRUE o FALSE al ser evaluadas. Evalúalas después y comprueba si has acertado. Para evaluarlas ten en cuenta la precedencia de los operadores lógicos: ! tiene la mayor precedencia, seguido de && y de ||.

    i <- 6
    j <- 12
    (2*i) <= j
    (2*i-1) < j
    (i > 0) && (i <= 10)
    (i > 25) || (i < 50 && j < 50)
    (i < 4) || (j > 5)
    !(i > 6)
  2. Escribe un programa que lea dos números y determine cuál de ellos es el mayor.

  3. Realice un programa que lea un valor entero y determine si se trata de un número par o impar. Sugerencia: un número es par si el resto de dividirlo entre dos es cero.

  4. Escribe un programa que lea del teclado un carácter e indique en la pantalla si el carácter es una vocal minúscula o no.

  5. Escribe un programa que lea del teclado un carácter e indique en la pantalla si el carácter es una vocal minúscula, es una vocal mayúscula o no es una vocal.

  6. Escribe un programa que solicite una edad e indique en la pantalla si la edad introducida está en el rango [18,25].

  7. Implementa un programa que lea los coeficientes del siguiente sistema de ecuaciones e imprima los valores que son solución para x e y:

    \[ \begin{array}{c} ax + by = c \\ dx + ey = f \end{array} \]

    El sistema puede resolverse utilizando las siguientes fórmulas:

    \[ x = \frac{ce - bf}{ae - bd} \] \[ y = \frac{af - cd}{ae - bd} \]

    Hay que tener en cuenta aquellos casos que no tienen solución, esto ocurre cuando las rectas representadas por las ecuaciones son paralelas (la solución al sistema de ecuaciones es el punto donde se cortan las dos rectas).

  8. Escribe un programa que calcule las soluciones de una ecuación de segundo grado de la forma \(ax^{2}+bx+c = 0\), teniendo en cuenta que:

    \[ x = \frac{-b\pm \sqrt{b^{2} - 4ac}}{2a} \]

    Si las soluciones son complejas no las calcules e indica un mensaje en la pantalla. También debes tener en cuenta que a puede valer 0, en cuyo caso debes resolver un sistema con una incógnita. Si a y b valen 0 no hay solución.

  9. Realice un programa que lea del teclado las longitudes de los tres lados de un triángulo y muestre en la pantalla qué tipo de triángulo es de acuerdo con la siguiente casuística (a denota la longitud del lado más largo y b y c denotan la longitud de los otros dos lados):

    • Si \(a \geq b + c\), no se trata de un triángulo
    • Si \(a^{2} = b^{2} + c^{2}\), es un triángulo rectángulo
    • Si \(a^{2} < b^{2} + c^{2}\), es un triángulo acutángulo
    • Si \(a^{2} > b^{2} + c^{2}\), es un triángulo obtusángulo

  10. Haz una versión del ejercicio anterior en el que se lean los 3 vértices del triángulo en lugar de la longitud de los lados.

  11. Dado el siguiente data frame:

    notas <- data.frame(nombre = paste("Alumno", 1:10),
                        acertadas = sample(30, 
                                           size = 10, 
                                           replace = TRUE
                                    )
    )
    notas$falladas = 30 - notas$acertadas

    Añádele un campo con la nota categórica, de forma que los alumnos que hayan acertado 15 o más respuestas tengan de nota "pasa" y los que tengan menos de 15 tengan de nota "no pasa". Usa la función ifelse para hacer el cálculo.

  12. Realiza un programa que solicite dos valores enteros que indican un rango (se puede introducir primero el menor y luego el mayor o a la inversa) y a continuación lea un tercer valor entero e indique si dicho valor pertenece o no al rango.

  13. Implementa un programa que dada una fecha (día, mes y año) determine si es válida o no. Suponemos que la fecha es válida si el día es válido según el mes, el mes está en el intervalo (1, 12) y el año es positivo. Consideramos que un año es bisiesto cuando es múltiplo de 4.

9.5 Solución a los ejercicios

i <- 6
j <- 12
(2*i) <= j
## [1] TRUE
 (2*i-1) < j
## [1] TRUE
(i > 0) && (i <= 10)
## [1] TRUE
(i > 25) || (i < 50 && j < 50)
## [1] TRUE
(i < 4) || (j > 5)
## [1] TRUE
!(i > 6)
## [1] TRUE
# Mayor de dos números
x = as.numeric(readline("Primer número: "))
y = as.numeric(readline("Segundo número: "))
if (x > y) {
  cat("El mayor es el", x, "\n")
} else {
  cat("El mayor es el", y, "\n")
}
# Comprobar si un número es par
x = as.integer(readline("Introduce un entero: "))
if (x %% 2 == 0) {
  cat(x, "es par\n")
} else {
  cat(x, "es impar\n")
}
# Comprobar si un carácter es una vocal minúscula
c <- readline("Introduce un carácter: ")
if (c == "a" || c == "e" || c == "i" || c == "o" || c == "u") {
  cat(c, "es una vocal minúscula\n")
} else {
  cat(c, "no es una vocal minúscula\n")
}

# En este ejercicio se pude usar el operador %in% para obtener
# un código más conciso
if (c %in% c("a", "e", "i", "o", "u")) {
  cat(c, "es una vocal minúscula\n")
} else {
  cat(c, "no es una vocal minúscula\n")
}
# Comprobar si un carácter es una vocal minúscula, mayúscula
# o no es una vocal

c <- readline("Introduce un carácter: ")
if (c == "a" || c == "e" || c == "i" || c == "o" || c == "u") {
  cat(c, "es una vocal minúscula\n")
} else if (c == "A" || c == "E" || c == "I" || c == "O" || c == "U") {
  cat(c, "es una vocal mayúscula\n")
} else {
  cat(c, "no es una vocal\n")
}

# Versión usando el operador %in%
if (c %in% c("a", "e", "i", "o", "u")) {
  cat(c, "es una vocal minúscula\n")
} else if (c %in% c("A", "E", "I", "O", "U")) {
  cat(c, "es una vocal mayúscula\n")
} else {
  cat(c, "no es una vocal minúscula\n")
}
# Comprobar si una edad está en el rango [18, 25]
edad <- as.integer(readline("Introduce una edad: "))
if (edad >= 18 && edad <= 25) {
  cat(edad, "está en el rango [18, 25]\n")
} else {
  cat(edad, "no está en el rango [18, 25]\n")
}
# Sistema de dos ecuaciones con dos incógnitas

a <- as.numeric(readline('Introduce el coeficiente a: '))
b <- as.numeric(readline('Introduce el coeficiente b: '))
c <- as.numeric(readline('Introduce el coeficiente c: '))
d <- as.numeric(readline('Introduce el coeficiente d: '))
e <- as.numeric(readline('Introduce el coeficiente e: '))
f <- as.numeric(readline('Introduce el coeficiente f: '))

denominador <- a*e - b*d
if (a == d && b == e && c == f) {
  cat('Las rectas se solapan\n')
} else if (denominador == 0) {
  cat('Las rectas son paralelas\n')
} else {
  x <- (c*e - b*f) / denominador
  y <- (a*f - c*d) / denominador
  cat('Solución: (', x, ', ', y, ')\n', sep = "")
  intervalo_x = c(x - 10, x + 10)
  plot(intervalo_x, (c - a * intervalo_x) / b, type = "l", col = "green")
  lines(intervalo_x, (f - d * intervalo_x) / e, type = "l", col = "blue")
  points(x, y, col = "red", pch = 19)
}
# Raíces ecuación de segundo grado

a <- as.numeric(readline('Introduce coeficiente a: '))
b <- as.numeric(readline('Introduce coeficiente b: '))
c <- as.numeric(readline('Introduce coeficiente c: '))

if (a == 0) {
  if (b == 0) {
    print('No tiene solución')
  } else {
    x <- -c / b
    cat('Sistema lineal. Solución', x, '\n')
    intervalo_x <- c(x - 5, x + 5)
    plot(intervalo_x, b*intervalo_x + c, type = "l", col = "blue")
    points(x, 0, col = "red", pch = 19)
    abline(h = 0, lty = "dashed")
  }
} else if (b^2 - 4*a*c < 0) {
  print("Las soluciones son complejas")
} else {
  x1 <- (-b + sqrt(b^2 - 4*a*c)) / (2*a)
  x2 <- (-b - sqrt(b^2 - 4*a*c)) / (2*a)
  cat('Soluciones reales:', x1, "y", x2, "\n")
  xmin <- min(c(x1, x2))
  xmax <- max(c(x1, x2))
  x <- seq(xmin - 10, xmax + 10, length.out = 100)
  plot(x, a*x ^ 2 + b*x + c, type = "l", col = "blue")
  points(x1, 0, col = "red", pch = 19)
  points(x2, 0, col = "red", pch = 19)
  abline(h = 0, lty = "dashed")
}
# Calcular tipo de triángulo según la longitud de sus lados
a <- as.numeric(readline("Longitud el lado más largo: "))
b <- as.numeric(readline("Longitud de otro lado: "))
c <- as.numeric(readline("Longitud de otro lado: "))

if (a > b + c) {
  print('No es un triángulo')
} else if (a ^ 2 == b ^ 2 + c ^ 2) {
  print('Triángulo rectángulo')
} else if (a ^ 2 < b ^ 2 + c ^ 2) {
  print('Triángulo acutángulo')
} else {
  print('Triángulo obtusángulo')
}
# Calcular tipo de triángulo dados sus vértices
x1 <- as.numeric(readline("Coordinada x punto 1: ")) # p1 = (x1, y1)
y1 <- as.numeric(readline("Coordinada y punto 1: "))
x2 <- as.numeric(readline("Coordinada x punto 2: ")) # p2 = (x2, y2)
y2 <- as.numeric(readline("Coordinada y punto 2: "))
x3 <- as.numeric(readline("Coordinada x punto 3: ")) # p3 = (x3, y3)
y3 <- as.numeric(readline("Coordinada y punto 3: "))

d <- c(sqrt((x1 - x2) ^2 + (y1 - y2) ^ 2),
       sqrt((x2 - x3) ^2 + (y2 - y3) ^ 2),
       sqrt((x3 - x1) ^2 + (y3 - y1) ^ 2)
)
a <- max(d)
resto <- d[-which.max(d)]
b <- resto[1]
c <- resto[2]

if (a > b + c) {
  print('No es un triángulo')
} else if (a ^ 2 == b ^ 2 + c ^ 2) {
  print('Triángulo rectángulo')
} else if (a ^ 2 < b ^ 2 + c ^ 2) {
  print('Triángulo acutángulo')
}  else {
  print('Triángulo obtusángulo')
}

# Dibuja el triángulo
plot(c(x1, x2, x3, x1), c(y1, y2, y3, y1), type = "o", pch = 19)
# Añadir nota categórica
notas <- data.frame(nombre = paste("Alumno", 1:10),
                    acertadas = sample(30, size = 10, replace = TRUE)
)
notas$falladas = 30 - notas$acertadas
notas$pasa = ifelse(notas$acertadas >= 15, "pasa", "no pasa")
notas
##       nombre acertadas falladas    pasa
## 1   Alumno 1        12       18 no pasa
## 2   Alumno 2        29        1    pasa
## 3   Alumno 3        27        3    pasa
## 4   Alumno 4        15       15    pasa
## 5   Alumno 5         6       24 no pasa
## 6   Alumno 6        21        9    pasa
## 7   Alumno 7        27        3    pasa
## 8   Alumno 8         3       27 no pasa
## 9   Alumno 9         8       22 no pasa
## 10 Alumno 10        19       11    pasa
# Valor en rango

ei <- as.integer(readline('Extremo del rango: '))
es <- as.integer(readline('El otro extremo del rango: '))

if (es < ei) {
  tmp <- ei
  ei <- es
  es <- tmp
}
x <- as.numeric(readline('Introduce valor: '))

if (x >= ei && x <= es) {
  cat(x, " pertenece al interval [", ei, ", ", es, "]\n", sep = "")
} else {
  cat(x, " no pertenece al interval [", ei, ", ", es, "]\n", sep = "")
}
# Día válido

dia  <- as.integer(readline('Día: '))
mes  <- as.integer(readline('Mes: '))
anio <- as.integer(readline('Año: '))

if (mes %in% c(1, 3, 5, 7, 8, 10, 12)) {
  dia_maximo <- 31
} else if (mes %in% c(4, 6, 9, 11)) {
  dia_maximo <- 30
} else if (mes == 2) {
  if (anio %% 4 == 0) {
    dia_maximo <- 29
  } else {
    dia_maximo <- 28
  }
}
if (mes < 1 || mes > 12 || anio < 0 || dia < 1 || dia > dia_maximo) {
  cat(dia, "/", mes, "/", anio, " es incorrecto\n", sep = "")
} else {
  cat(dia, "/", mes, "/", anio, " es correcto\n", sep = "")
}