Programación II Sesión 2: Especificación de problemas Diego R. Llanos Ferraris UNED, Centro Asociado de Palencia Nota: Estos apuntes son la guía utilizada por el Profesor Tutor para impartir el seminario correspondiente, y no sustituyen en ningún modo a los libros de la asignatura. 1. Introducción La especificación de problemas pretende dejar claro qué problema hay que resolver, de modo que no quede ninguna duda ni ambigüedad. Ejemplo: Supongamos la siguiente especificación: escribir un programa que reciba un vector de n elementos e indique si hay algún elemento igual a la suma de todos los precedentes. Intuitivamente esta especificación parece suficiente para entender el problema. Sin embargo, presenta ambigüedades: Qué pasa si n es negativo? Qué pasa si n vale cero? Qué pasa si n >= 1 y a[1] = 0? Hay que devolver cierto o falso? Todas estas imprecisiones en la formulación del problema son independientes de cómo se implemente su solución. En el fondo, de lo que se trata es de reducir las posibilidades de error a la hora de resolver un problema. Si el especificador y el implementador son personas diferentes, una especificación formal correcta evita errores de interpretación. Las especificaciones formales se basan en definir un conjunto de datos de entrada y sus propiedades (denominada precondición, y denotada por Q) y un conjunto de datos de salida para esos datos de entrada (postcondición, denotada por R). Para ello se utiliza lógica de predicados. Por ejemplo, para el problema anterior, una posible especificación formal sería la siguiente: 1
2. Lógica de predicados {Q 1 <= n <= 1000} fun essuma (a:vect; n:entero) dev (b:booleano) {R (b = α {1..n} tal que a[α] = α 1 β=1 a[β])} (1) La lógica de predicados es una herramienta que permite determinar si una fórmula lógica es verdadera o falsa para un conjunto de datos de entrada. Las fórmulas lógicas se denominan predicados, y se denotan con las letras mayúsculas P, Q, R, etcétera. Por otra parte, se utilizan letras minúsculas para designar variables. Los operadores utilizados en esas fórmulas lógicas son los siguientes: La negación: P La conjunción: P Q La disyunción: P Q El condicional: P Q El bicondicional: P Q La cuantificación universal: α D.P, donde D es un dominio de valores y P un predicado. La cuantificación existencial: α D.P, donde D es un dominio de valores y P un predicado. (Una nota acerca de los significados de los operadores condicional y bicondicional: P Q es falso si P es cierto y Q es falso, y es cierto en caso contrario. Con respecto al bicondicional, P Q es cierto si P es igual a Q y es falso en caso contrario). De este modo, se define recursivamente un predicado (o fórmula) válido como: Una fórmula atómica (las constantes cierto (V) y falso (F), las variables booleanas, expresiones aritméticas relacionales, funciones booleanas), o Una fórmula formada por predicados válidos y los operadores vistos arriba. 2
2.1. Variables libres y ligadas Informalmente, puede decirse que las variables ligadas van asociadas a un cuantificador existencial o universal, mientras que las variables libres no. Por ejemplo, en las fórmulas siguiente, P (x + y + z > 0) Q ( y.p) R ( x.q) (2) las cuales pueden reemplazarse por R ( x ( y (x + y + z > 0)) (3) En la fórmula 3, las variables x e y están ligadas a los cuantificadores más internos en cuyo ámbito se encuentran, mientras que z está libre. Sin embargo, si consideramos exclusivamente la definición de P dada en la fórmula 2, las tres variables son libres. Para evitar confusiones entre variables libres y ligadas, el libro de Peña sigue la siguiente convención: Las letras minúsculas a, b,...,z son variables libres. Las letras griegas minúsculas α, β,...,ω son variables ligadas. 2.2. Semántica Para saber si un predicado es verdadero o falso hay que conocer el valor de las variables que aparecen en él: x > 0 será verdadero para x = 7, por ejemplo. El valor de la variable es su estado. Formalmente, un estado es una aplicación que asocia a cada variable un valor de su dominio. Se denota por σ (sigma). Así, en el ejemplo de arriba, σ(x) = 7. Por su parte, el conjunto de todos los estados posibles se denota por ε. Hay un valor denominado indefinido, y que se denota por. Este valor es el valor que tienen las variables cuando no tienen un valor definido. Se dice que un predicado P está bien definido en un estado σ si todas sus variables tienen asignado un valor distinto de y todas las funciones que aparecen en P están definidas en σ. Al evaluar un predicado P para un estado σ se obtiene un valor cierto o falso para P, que se designa como [[P]]σ. 3
2.2.1. Satisfactibilidad, consecuencia lógica y equivalencia Se dice que σ satisface un predicado P, y se denota como σ = P, si [[P]]σ = V. Se dice que P es universalmente válido, o válido a secas, si para todo estado σ válido, σ satisface a P. Se dice que Q es consecuencia lógica de P, y se denota P = Q, si todo estado σ que satisface a P también satisface a Q. 3. Especificación con predicados El objetivo es definir las precondiciones y postcondiciones de un algoritmo utilizando lógica de predicados. El conjunto de estados definidos por un predicado P, y denotado como estados(p), es el conjunto estados(p) = {σ ε σ = P} (4) Así, si P (x div y > 1), con ε el subconjunto de Z x Z formado por los pares del mismo signo en los que el segundo número es distinto de cero, los valores (2,1), (-2,-1) pertenecen a estados(p), ya que lo satisfacen. Cuanto mayor sea el conjunto de estados que satisfacen un predicado, más débil será ese predicado. Inversamente, cuanto menor sea dicho conjunto, más fuerte será P. Esto permite establecer una relación de orden parcial (es decir, no todos los pares de predicados son comparables), como se define a continuación. Dados los predicados P y Q con estados pertenecientes al mismo dominio, se dice que P es más fuerte que Q si se cumple que estados(p) estados(q). Esto se denota también como P Q. Un buen ejercicio para aclarar esto es el ejercicio 2.2 del Peña, página 42. Se puede reforzar un predicado P añadiéndole una conjunción, ya que P Q P. La conjunción implica restringir el conjunto de estados que satisfacen la nueva fórmula. Del mismo modo, las disyunciones debilitan un predicado: P Q es más débil que P en el caso general. Substitución textual: Se denota como P E x al predicado que resulta de sustituir en P todas las apariciones libres de x por la expresión E. Para que esta sustitución sea posible, la expresión E no ha de nombrar variables ligadas de P y ha de ser del mismo tipo de datos de x. 4
Especificaciones de problemas La especificación formal de un algoritmo consiste en una terna {Q}S {R}, donde Q y R son predicados y S es una cabecera de tipo fun o acción. El predicado Q se denomina precondición y sus únicas variables libres son los parámetros de entrada o de entrada/salida de S. El predicado R se denomina postcondición y sus únicas variables libres son los parámetros (de cualquier clase) de S. En el libro de Peña (páginas 47 a 51) aparecen varios ejemplos de especificaciones formales, de menor a mayor complejidad. 5