PrInf07: Más sobre expresiones lógicas

De MateWiki
Saltar a: navegación, buscar
Práctica de Informática
Más sobre expresiones lógicas
Práctica anterior Siguiente práctica
Este artículo es un guión de prácticas de Informática


Al usar la instrucción if hemos tenido un primer contacto con expresiones lógicas. Hasta el momento hemos realizado operaciones numéricas, tanto con escalares como con vectores y matrices, y hemos visto cómo mostrar texto. Pero en Octave UPM hay más tipos de información: uno de los más importantes es el tipo de datos lógico. Estas variables solo pueden tomar dos valores, verdadero o falso, y son las que usa la instrucción if para tomar una decisión y ejecutar o no una parte del código.

1 Requisitos previos

Es recomendable para esta práctica consultar el siguiente vídeo:

También es importante haber realizado la práctica 6.

2 Comandos que se aprenderán en esta práctica

En esta práctica vamos a ver qué significan los siguientes comandos de MATLAB / Octave

Operadores relacionales (<, >, <=, >=, ==, ~=) Operador AND (&) Operador OR (|) Operador NOT (~) xor
Operador AND perezoso (&&) Operador OR perezoso (||)

3 Contenido de la práctica

Si examinamos el espacio de trabajo de Octave UPM, habremos observado que hasta ahora hemos visto dos tipos de datos: números y texto. Los números aparecen como double en el espacio de trabajo, y los textos como char. Vamos a ver ahora un tercer tipo, logical, que ya hemos usado de manera inadvertida en la práctica anterior.

En la práctica anterior, veíamos que se podía mostrar un mensaje si la temperatura era superior a 30. Escribíamos

if c > 30
   disp('Mostrar un mensaje');
end

Este código tiene diferentes partes:

  • La primera es la instrucción if, que termina con end. Esta instrucción acepta a continuación una condición. Si la condición es verdadera, ejecuta el cuerpo de la instrucción (muestra el mensaje en este caso), y si no, continúa en la línea que viene después del end.
  • La condición es en este caso c > 30. Es una expresión lógica que devuelve verdadero o falso.
  • El cuerpo del mensaje lo forman los comandos que se van a ejecutar si la condición es verdadera. En este caso solo hay un comando, pero puede tener cualquier comando y cualquier otra instrucción. Por ejemplo, se podría poner un if dentro de otro if; en una práctica veremos cuándo es útil hacer este tipo de anidamientos. Aunque no es obligatorio para que el programa sea sintácticamente correcto, es una buena práctica indentar el código, para que el cuerpo aparezca con un par de espacios de sangrado. La indentación permite distinguir muy claramente qué forma parte del cuerpo del if y que está fuera.

En esta práctica nos vamos a centrar en cómo escribir la condición. En el caso del ejemplo, la condición usa el operador >, mayor que, que es un operador relacional infijo. Un operador infijo es el que va entre dos elementos (por ejemplo, en 1 + 2 el símbolo + es un operador infijo). Es relacional porque nos devuelve un resultado acerca de la relación que existe entre los elementos. Este operador devuelve verdadero si el elemento de la izquierda es mayor que el de la derecha. Existen muchos otros operadores relacionales, que tienen sus reglas de precedencia y combinación de varios operadores. Hay también otro tipo de operadores lógicos que nos permiten construir condiciones más complejas.

3.1 Operadores relacionales

Vamos a intentar usar expresiones lógicas fuera de las instrucciones if, para aprender cómo operar con ellos y ser capaces de construir condiciones más complejas.

  1. Borra todas las variables del espacio de trabajo ejecutando el comando clear.
  2. Crea una variable de nombre c que tenga como valor 70. Ejecuta en la línea de comandos
    c > 30
    ¿En qué variable se ha guardado el resultado? ¿Cuál es su valor? ¿Y su tipo? (consulta el espacio de trabajo para averiguar el tipo).
  3. Escribe ahora
    d = c > 100;
    ¿Qué variables hay en el espacio de trabajo? ¿De qué tipo? ¿Podrías explicar qué ha hecho este trozo de código?
  4. Como vemos, es posible asignar valores lógicos a variables. En ocasiones nos puede venir bien crear directamente variables que sean verdadero o falso. Este tipo de variables se denominan flags (banderín). Su estado cambia cuando ocurre algo en un programa, y permite detectar en otra parte del programa que eso ha ocurrido. En una práctica futura veremos una aplicación práctica de este tipo de variables. Sin embargo, no se pueden crear de cualquier manera. Por ejemplo,
     a = 1;
    ¿Es la variable a un flag? ¿Por qué?
  5. Compara el resultado de la variable a con esta variable b
    b = true;
    ¿Cuál es la diferencia entre las variables a y b?
  6. Igual que existe true, tenemos el alias false para crear variables lógicas de contenido falso. Por ejemplo,
     c = false;
    Según el espacio de trabajo, ¿Cuál es el valor de c'?
  7. Existen varios tipos de relaciones que podemos comparar usando operadores. Hemos visto >, también podríamos usar <. Por ejemplo, ¿cuánto vale la variable ans despúes de ejecutar este código? ¿Por qué?
     x = 10; y = 11; x < y
  8. Existen también los operadores >= y <=. Por ejemplo, ¿qué devuelve el siguiente código? ¿Por qué?
     x = 10; y = 11; x <= y
  9. ¿Y este código?
     x = 11; y = 11; x <= y
  10. Existe también el operador == para comprobar si dos variables tienen el mismo valor. Por ejemplo
     x = 11; y = 11; x == y
    ¿Qué devuelve este código? ¿Por qué?
  11. El operador == comprueba si dos variables tienen el mismo valor. Recuerda que tenemos una variable a de tipo numérico, y una variable b de tipo lógico. ¿Tienen el mismo valor? Comprobémoslo con
     a == b
    ¿Cuál es el resultado? ¿Por qué?
  12. Veamos otro ejemplo
     x = 10; y = 11; x == y
    ¿Cuál es el resultado? ¿Por qué?
  13. Existe otro operador que es justo el contrario que ==. Es el operador ~= que comprueba si dos elementos tienen un valor diferente. Para obtener el símbolo ~ puedes pulsar AltGr y luego el número 4, y después darle a la tecla espacio. Veamos un ejemplo
     x = 10; y = 11; x ~= y
    ¿Cuál es el resultado? ¿Por qué?
  14. Si probamos las variables a y b con el operador ~=, ¿qué resultado obtendremos?
     a ~= b
    ¿Cuál es el resultado? ¿Por qué?

3.2 Operadores lógicos

Los operadores relacionales pueden combinarse entre ellos igual que cualquier otro tipo de operador, como por ejemplo los operadores aritméticos. Pero además, existen otras operaciones de carácter lógico que pueden ayudarnos a construir condiciones más complejas. Estas operaciones lógicas son: AND, OR y NOT.

El operador lógico AND se representa por el símbolo &. Es un operador infijo que devuelve el valor verdadero si ambos elementos son verdaderos. Devuelve falso en cualquier otro caso. Esto se suele representar con tablas de verdad. Por ejemplo, si realizamos la operación a & b, el resultado puede ser el siguiente, según los valores de a y b

a b a&b
F F F
F V F
V F F
V V V

El operador lógico OR se representa por el símbolo |. Es un operador infijo que devuelve el valor verdadero si al menos uno de los dos lados es verdadero. Su tabla de verdad es

a b a|b
F F F
F V V
V F V
V V V

El operador NOT se representa por ~. Es un operador unario, es decir, se aplica a un único elemento. El operador NOT cambia el estado del elemento al que se aplica. Es decir:

a ~a
V F
F V

Veamos un ejemplo. Estamos programando una máquina que dispensa abonos de transporte, y queremos emitir el tipo de abono adecuado según la edad. Estamos en la parte del programa que decide si emite un abono joven, para menores de 23 años. El programa tiene que comprobar que el dinero introducido es suficiente para pagar los 50 eurazos que vale el abono, y tiene que comprobar que la edad del usuario es la adecuada (menor de 23). Tenemos dos variables dinero y edad que contienen la cantidad de dinero que ha introducido el usuario y su edad. El trozo de código que se encargaría de esta comprobación podría ser el siguiente:

if (edad < 23) & (dinero >= 50)
   disp('Imprimiendo abono joven...');
end

Este código solo muestra el mensaje si la edad es menor que 23 y la cantidad de dinero es mayor o igual que 50. Hay que resaltar que hemos usado paréntesis para dejar clara el orden en el que se realizan las operaciones. Primero se evaluarán los contenidos de los paréntesis, dando verdadero o falso cada uno de ellos, y luego el operador & devolverá verdadero solo si ambos son verdaderos. Cuando estamos combinando varios operadores, sobre todo relacionales y lógicos, es mucho mejor usar paréntesis que confiar en la precedencia de operaciones para dejar claro qué operación ocurre. De esta manera el código será más sencillo de entender.

Lapiz.png Tarea: Crea dos variables edad y dinero y prueba a ejecutar el código anterior. Elige valores de las variables que hagan que el mensaje se muestre, y luego elige otros valores para que no se muestre. Escribe todo el código en un fichero M


El operador OR también puede usarse para construir condiciones más complejas. Por ejemplo, un estudiante puede aprobar una asignatura si la nota media es mayor o igual que 5, o si la nota del examen final es mayor que 5. El código podría ser:

if (notaMedia >= 5) | (examenFinal >= 5)
  disp('Aprobado');
end
Lapiz.png Tarea: Crea dos variables notaMedia y examenFinal y prueba a ejecutar el código anterior. Elige valores de las variables que hagan que el mensaje se muestre, y luego elige otros valores para que no se muestre. Escribe todo el código en un fichero M


El uso del operador NOT está recomendado cuando la condición se lea más claramente usando este operador, ya que siempre es posible construir el complementario de una condición usando otros operadores. Por ejemplo, un cliente no puede pasar si no es mayor de edad:

if ~(edad>=18)
  disp('No puede pasar.');
else
  disp('Puede pasar.');
end


Lapiz.png Tarea: Crea una variable edad y prueba a ejecutar el código anterior. Elige un valor que haga que el mensaje se muestre, y luego elige otro valor para que no se muestre. Escribe todo el código en un fichero M


Lapiz.png Tarea: Modifica el programa anterior para que no use el operador NOT y no cambie el resultado de la ejecución


Existe otro operador que no es tan habitual, que se denomina OR exclusivo. Este operador devuelve verdadero solo si uno de los dos elementos es verdadero. Si los dos son falso, o los dos son verdaderos, devuelve falso. No tiene representación infija en Octave UPM, pero tenemos la función xor para usar este operador. Su tabla de verdad es:

a b xor(a,b)
F F F
F V V
V F V
V V F

Su uso no es tan habitual como el resto de operadores.

3.3 Evaluación perezosa

Algunos operadores lógicos no necesitan evaluar todos los elementos para saber qué resultado van a devolver. Por ejemplo, si el primer elemento en el operador AND es falso, da igual el valor que tenga el segundo elemento, el resultado será falso. Sin embargo, el operador AND evalúa ambos lados antes de devolver un resultado.

Para evitar evaluaciones innecesarias, existe una versión más inteligente que realiza evaluación perezosa. En un lenguaje de programación, evaluación perezosa es una técnica que evita calcular términos que no van a influir en el resultado final.

Para usar estos operadores, simplemente tenemos que repetirlos. Por ejemplo, el operador AND inteligente y perezoso es '&&', y el operador OR inteligente y perezoso es '||'. Solo estos dos operadores tienen su versión con evaluación perezosa.

Aunque su sintaxis (la manera de escribirlo) es diferente, su semántica (el resultado que devuelven para unas entradas en particular) no cambia en absoluto. Por este motivo, es una buena idea siempre usar los operadores perezosos por defecto. En el peor de los casos no estaremos ganando nada, pero en el mejor de los casos nos evitaremos cálculos innecesarios. Además, algunos programas, sobre todo implementaciones de métodos numéricos, pueden ser más sencillos de escribir y de entender si aprovechamos la evaluación perezosa (lo veremos en una próxima práctica).

Si repetimos los ejemplos de arriba con evaluación perezosa, el ejemplo del operador AND quedará como sigue:

if (edad < 23) && (dinero >= 50)
   disp('Imprimiendo abono joven...');
end

El comportamiento no cambia en absoluto, pero quizás esta versión sea más eficiente que la anterior.

El ejemplo del operador OR quedaría como sigue:

if (notaMedia >= 5) || (examenFinal >= 5)
  disp('Aprobado');
end

De nuevo, el comportamiento no cambia, pero esta versión puede ser más eficiente que la anterior.

Lapiz.png Tarea: Modifica los dos ficheros M que has escrito en el apartado anterior para que usen evaluadores perezosos, tal y como se muestra en los dos trozos de código anteriores. Comprueba que el resultado no se altera


4 Ejercicio post-práctica

Los operadores relacionales se pueden combinar entre ellos, y formar expresiones más complejas. Sin embargo, no siempre obtenemos los resultados previstos. Queremos escribir una condición para comprobar si la variable x está entre 10 y 12. Para ello vamos a realizar un pequeño programa

x = input('Dame el valor de x: ');

if 10 < x < 12
  disp('x está entre 10 y 12');
else
  disp('x NO está entre 10 y 12');
end

Prueba a introducir los siguientes valores de x: 7, 11 y 13. ¿Funciona correctamente el programa? ¿Cómo lo arreglarías?