Ir al contenido principal

Comenzando con lógica proposicional...

Esta vez llega el turno de interiorizar un poco en lo que respecta a la lógica proposicional. Esta lógica, como veremos en próximas entradas, es un álgebra de Boole y nos servirá posteriormente para realizar e interpretar circuitos lógicos, entre otras aplicaciones.

Como el título lo indica, trabajaremos con proposiciones... pero ¿qué es una proposición?.

Una proposición es una frase o sentencia que tiene un valor de verdad, osea de la que podemos decir si es verdadera o falsa. Por lo que frases como:

  • 2+2=4                                 (proposición verdadera)
  • "La semana tiene 9 días."      (proposición falsa)
Son ejemplos simples de proposiciones, mientras que:
  • "¿Qué hora es?"
  • "¡Qué frío!"
Claramente no son proposiciones ya que no podemos darles un valor de verdad booleano (verdadero o falso, si o no, correcto o incorrecto, etc.)

Ahora que ya sabemos lo que es una proposición, podemos desplazarnos hacia el campo que nos interesa en este tema: trabajar con esos valores de verdad.

Para hacerlo, usaremos conectores lógicos (u operadores lógicos) que básicamente son funciones de verdad. Es decir, funciones que aplicadas a una o más proposiciones devuelven un valor de verdad determinado. Estos valores de verdad se colocan en lo que llamamos tabla de verdad que es una tabla donde colocamos todas las posibles combinaciones de valores de verdad que toman nuestras proposiciones y las operaciones a las que las sometemos.

El primer conector que veremos se llama NOT (negación) y su tabla de verdad es la siguiente:

- Usamos una proposición genérica de nombre p y le aplicamos la función NOT(no en español).




Como podemos ver, nuestra proposición p puede adoptar los valores verdadero (V) o falso (F). Si le aplicamos la función NOT (representada con el símbolo "¬" ), obtenemos la negación de lo ingresado, por lo que V negado es F y viceversa.




El siguiente conector es de tipo binario (conecta dos proposiciones) y se llama AND (conjunción,  "y" en español) y tiene la siguiente tabla de verdad:



Ahora, como tenemos dos proposiciones las combinaciones diferentes que podemos obtener son cuatro. 


Si aplicamos la función AND (representada con el símbolo "^"), vemos que ésta es verdadera sólo cuando ambas proposiciones son verdaderas. De ahí el nombre "y", ya que sólo puedo afirmar que"p y q" es verdadera cuando p es verdadera y q también. 




Otro conector binario es OR (disyunción, "o" en español) y tiene la siguiente tabla de verdad:



Si aplicamos la función OR (representada con el símbolo "v"), vemos que al ser una "o" la otra, basta que alguna de las proposiciones sea verdadera para que la disyunción sea verdadera.







En este caso también encontramos la disyunción excluyente (XOR) que adquiere el valor verdadero, sólo si una proposición es verdadera y la otra falsa.


También encontramos la implicación (condicional) que representamos con una flecha "->" y tiene la siguiente tabla de verdad:



La implicación se puede leer como:  "si p entonces q", "p entonces q", "p implica q", entre otras formas. Aquí p recibe el nombre de "antecedente" y q de "consecuente".
Si analizamos la tabla de verdad, vemos que la implicación sólo es falsa cuando un antecedente verdadero implica un consecuente falso. 










Y terminando con esta entrada, encontramos la doble implicación (bicondicional o equivalencia lógica), que se representa con una flecha de doble dirección "<->" y cuya tabla de verdad mostramos a continuación:


La leemos como "p sí y sólo sí q" y, como vemos en la tabla, se obtiene el valor verdadero cuando ambas proposiciones tienen el mismo valor de verdad, ya sea verdadero en ambas o falso en ambas.


También podemos ver (si retrocedemos hasta la tabla en cuestión) que la doble implicación puede escribirse como la negación de una disyunción excluyente.





Se llama doble implicación ya que justamente son dos implicaciones. 
Si realizamos p->q y q->p , obtendremos estos valores de verdad como podemos ver en la tabla de la derecha.

Comentarios

Entradas populares de este blog

C: Conversiones de tipo (casting) en C...

El casting o simplemente cast  nos permite hacer una conversión explícita de un tipo de dato a otro, a criterio del programador siempre y cuando estos tipos sean compatibles. Este cast se realiza a través de un operador de conversión de tipos (type casting operator) y es un recurso a tener en cuenta ya que hay situaciones en que nos puede resultar de gran utilidad. Hacer uso de un cast es tan sencillo como poner (tipo de dato)  delante de la expresión o variable a convertir. Veamos un ejemplo: Declaramos una variable de tipo int con un identificador tan creativo como "a" y le realizamos diferentes cast a a para mostrarlo como si fuera un float, un double y un char en un printf. Lo que obtendríamos en pantalla sería lo siguiente: Donde tenemos el valor de nuestro a, a convertido en float y double (mostrándolo con 3 cifras decimales) y a convertido en char. Si vemos este último caso, al hacer la conversión de "a" a char toma a como el código ascii de

C: Ejemplos: Congruencia de Zeller (nivel básico) ...

La Congruencia de Zeller es un algoritmo que se atribuye al matemático alemán Julius Christian Johannes Zeller que vivió en el siglo XIX. Este algoritmo nos permite determinar el día de la semana que le corresponde a una fecha determinada del calendario Gregoriano. La fórmula que nosotros usaremos (con algunas modificaciones respecto de la original para poder usarla en  informática) es la siguiente: Donde h es el día de la semana (entre 0 y 6), J es año/100 (la centuria) y K es año mod 100 (el año de la centuria). Y hay que tener en cuenta que los meses de enero y febrero cuentan como el mes 13 y 14 del año anterior. Ahora que tenemos la fórmula, programemos el algoritmo en C mediante el uso de una función: Analicemos el código paso a paso: Tenemos en cuenta el caso de enero y febrero: Dijimos que estos meses corresponden a los meses 13 y 14 del año anterior por lo que los asignamos como corresponde (mes + 12 , que dará 13 para enero y 14 para febrero) y le rest

Algoritmos: Resolución de problemas y refinamientos en pseudocódigo...

En otras entradas, vimos las partes que debe tener nuestro algoritmo en pseudocódigo y las estructuras que utilizaremos para resolverlo. Ahora llega el turno de implementar todo en conjunto para dar origen a nuestra creación. Pero ¿cómo resolvemos un problema así? Para hacerlo, utilizaremos lo que llamamos refinamientos sucesivos. Este concepto consiste en dividir el problema en subproblemas más pequeños y a estos, a su vez, en otros más pequeños; y así sucesivamente hasta que la solución de los últimos sea trivial, sencillo de resolver. Luego usaremos todas las soluciones obtenidas para armar la solución de nuestro problema mayor. Este principio, tiene base en parte de la técnica divide and conquer (dependiendo de la traducción: "divide y vencerás") que es una de las muchas técnicas de resolución de algoritmos existentes. Como vemos, al dividir el problema en otros más pequeños y más fáciles de resolver, podemos pasar de un problema complicado a uno cuya solución es much