Algoritmos y Estructuras de Datos I 1 Clase Práctica 2: Especificación - 22/08/14 (Tarde) De la Lógica Proposicional a la Especificación Sea una especificación: problema probleNom(param1 : tipo1, ..., paramN : tipoN ) = res : tipoRes{ requiere : preCond1; requiere : preCond2; asegura : posCond1; asegura algunNombre: posCond2; aux f uncAux(paramA : tipoA, ..., paramZ : tipoZ) : tipoExpr = expresion; } ¿Cómo se la ve desde el punto de vista lógico? preCond1 ∧ preCond2 → posCond1 ∧ posCond2 Podemos ver a la fórmula correspondiente a una especificación como un implica, donde el antecedente es la conjunción (en orden según se escribieron) de las precondiciones, y el consecuente la conjunción de las poscondiciones. Luego, la evaluación de la fórmula es por semántica de cortocircuito. Si la conjunción de las precondiciones es falsa, entonces no importa lo que suceda con las poscondiciones porque la implicación será verdadera (y no se sigue evaluando). Por eso, en ese caso no importa si los asegura dan falso, verdadero o se indefinen... cualquier algoritmo va a cumplir. En cambio, si las precondiciones son verdaderas, las poscondiciones también deberán serlo para que la especificación se cumpla (y no se viole el contrato). 2 Ejercicios introductorios de Especificación Ejercicio 1. Especificar una función que, dados dos parámetros a y b enteros: i) devuelva el cociente entero entre ambos. problema cociente(a, b : Z) = res : Z{ requiere : b 6= 0; asegura : res == a div b; } ii) devuelva el cociente entero en a y el resto en b. problema cociente(a, b : Z){ requiere : b 6= 0; modifica a, b; asegura : a == pre(a) div pre(b); asegura : b == pre(a) mod pre(b); } Observación: Hacemos uso de las operaciones del tipo de datos Z. En el segundo caso, en vez de devolver el resto, ¿podrı́amos devolver en b un valor de verdad según si la división fue exacta? No, pues modifica indica que se puede cambiar el valor del parámetro, no su tipo. 3 Ejercicios de Secuencias Ejercicio 2. Tipar las siguientes secuencias: i) [0 10 ,0 20 ,0 30 ,0 40 ] : [Char] ii) [[6], [123 + 1, −59], [ ]] : [[Z]] iii) [[0 u0 ], [0 b0 ,0 a0 ], [21]] : no tipa, mal formada. iv) [[ ], [[ ]]] : [[[T ]]] Facultad de Ciencias Exactas y Naturales 1 Universidad de Buenos Aires Algoritmos y Estructuras de Datos I Clase Práctica 2: Especificación - 22/08/14 (Tarde) Observación: La lista vacı́a es un elemento válido de tipo lista pero que no contiene ningún elemento, es decir su longitud es 0. Si no se puede deducir del contexto, su tipo es T (potencialmente cualquier tipo del lenguaje). Ejercicio 3. Dar las secuencias: i) Definir la lista de los primeros 10 múltiplos de 5 [5 × x|x ← [1..11)] ii) Idem anterior pero sólo impares [5 × x | x ← [1..10], x mod 2 == 1] iii) Dar por extensión la siguiente lista: [y | y ← [1..6], x ← [1..8], x < y ∧ y ≤ 3] [2, 3, 3] Observación: Notar cómo recorren los selectores: de izquierda a derecha, por cada valor que toma uno se fija y se empieza a recorrer desde el principio el que haya a derecha, ası́ sucesivamente. En el último ejemplo, para el valor 1 en y se recorre x de 1 a 8, luego para 2 en y se recorre x de 1 a 8, y ası́. En las secuencias por compresión, las variables ligadas son aquellas que se definen en los selectores, mientras que las libres son las que se utilizan porque vienen del contexto donde se está utilizando la lista (parámetros de entrada de problema, función aux, de salida). 4 Ejercicios de Funciones Auxiliares Notar que las auxiliares no llevan precondiciones. Si se definen fuera de una especificación pueden utilizarse en otros problemas, por lo tanto se recomienda fuertemente que nunca se indefinan y sean válidas para todo el dominio de los parámetros que reciben. En ese caso también será útil que su nombre sea declarativo y que no estén atadas al contexto de un problema particular. Ejercicio 4. Sea a una secuencia, obtener su secuencia inversa. aux inversa(a : [T ]) : [T ] = [a|a|−i−1 | i ← [0.. |a|)] Observación: ¿Podrı́a haber hecho una especificación para este problema? Sı́, todo depende del propósito que se busque. Si lo queremos utilizar en otros problemas, y como no necesita ninguna precondición, es útil tenerlo como auxiliar. ¿Está mas cerca del cómo o del qué? Podrı́a tener una especificación del problema que para cada posición del arreglo dijera dónde queda en el resultado (quizás engorroso indices, necesitamos cuantificadores). Ejercicio 5. Sea a una secuencia, obtener la subsecuencia que va de d hasta h. aux subsec(a : [Z], d, h : Z) : [Z] = [ai | i ← [0.. |a|), d ≤ i ≤ h] Observación: ¿Hace falta esta auxiliar? Ya contamos con la operación de listas subsec, que como en este caso cuando alguno de los indices no está en rango o no respeta el orden da [ ]. Ejercicio 6. Escribir una función auxiliar que, dada una secuencia s, devuelva la cantidad de elementos pares en la misma. aux cantP ares(s : [Z]) : [Z] = cuenta(True, [x mod 2 == 0 | x ← s]) aux cantP ares(s : [Z]) : [Z] = long([x | x ← s, x mod 2 == 0]) Ejercicio 7. Escribir una función auxiliar que, dada una secuencia s, determine si hay un elemento par en la misma. aux hayP ares(s : Z) : Bool = cantP ares(s) > 0 aux hayP ares(s : Z) : Bool = (∃x ← s) x mod 2 == 0 aux hayP ares(s : Z) : Bool = alguno([x mod 2 == 0 | x ← s]) Observación: ¿Cuál se acerca más al qué de las resoluciones anteriores? Destacar la segunda versión como un exponente sencillo de lo que se busca con la especificación (definir el qué). Facultad de Ciencias Exactas y Naturales 2 Universidad de Buenos Aires Algoritmos y Estructuras de Datos I Clase Práctica 2: Especificación - 22/08/14 (Tarde) Ejercicio 8. Determinar si una secuencia s es prefijo de otra t (ejemplo: [1, 5] es prefijo de los números de celular, por ejemplo de [1, 5, 5, 0, 0, 0, 0, 0, 1, 2] o [0 h0 ,0 i0 ,0 p0 ,0 o0 ] de [0 h0 ,0 i0 ,0 p0 ,0 o0 ,0 c0 ,0 a0 ,0 m0 ,0 p0 ,0 o0 ]). aux esP ref ijo(s, t : [T ]) : Bool = |s| ≤ |t| ∧ (∀i ← [0.. |s|)) ti == si aux esP ref ijo(s, t : [T ]) : Bool = |s| ≤ |t| ∧ t[0..|s|) == s Observación: Notar que si bien no contamos con requiere, en auxiliares de tipo Bool podemos hacer uso de la semántica de cortocircuito para evitar indefiniciones. Ejercicio 9. Determinar si todos los elementos de una secuencia son distintos. aux distintos(s : [T ]) : Bool = (∀i, j ∈ [0.. |s|), i 6= j) si 6= sj Observación: ¿Vale para la secuencia vacı́a? Sı́, pues ver si se cumple una condición para todos los elementos de esa lista, es decir ninguno, es verdadero. Recordar todos([ ]) es true. 5 Ejercicios de Especificación lvl 2 Ejercicio 10. Dada una secuencia de enteros s, modificarla de manera tal que al comienzo queden sus elementos pares y luego sus impares. i) La siguiente es una solución incorrecta. ¿Por qué? problema separar(s : [Z]){ modifica s; asegura : s == dameP ares(pre(s)) + +dameImpares(pre(s)); aux dameP ares(s : [Z]) : [Z] = [x | x ← s, x mod 2 == 0]; aux dameImpares(s : [Z]) : [Z] = [x | x ← s, x mod 2 == 1]; } El problema radica en que por cómo se arman las listas por comprensión, se impone un orden sobre los elementos resultantes, dejando afuera soluciones igualmente válidas (probar qué ejemplos cumplen con la poscondición y cuáles no). En otras palabras, se está sobreespecificando. ii) Solución correcta posible: problema separar(s : [Z]){ modifica s; asegura : mismos(s[0..cantP ares(pre(s)) , dameP ares(pre(s)); asegura : mismos(s[cantP ares(pre(s))..|(s)|) , dameImpares(pre(s)); aux dameP ares(s : [Z]) : [Z] = [x | x ← s, x mod 2 == 0]; aux dameImpares(s : [Z]) : [Z] = [x | x ← s, x mod 2 == 1]; } Utilizamos auxiliares que están definidas fuera de la especificación. En el caso de mismos es una auxiliar dada en el lenguaje, de tipo Bool y que evalúa que dos secuencias tengan los mismos elementos, sin importar el orden. Ejercicio 11. Dado un número entero n, obtener la lista de todos los números naturales que lo dividen (en cualquier orden). i) La siguiente solución es incorrecta según lo que se pide, ¿por qué? problema obtenerDivisores(n : Z) = result : [Z]{ requiere : n > 0; asegura : (∀d ← result) n mod d == 0; } Es importante pensar en qué resultados queremos aceptar al especificar. En este caso, si n es 30, result == [2, 3] cumple con la poscondición. Sin embargo no es solución al problema inicial, puesto que faltan divisores. Cuando permitimos más soluciones al problema de lo que buscamos, estamos subespecificando. Facultad de Ciencias Exactas y Naturales 3 Universidad de Buenos Aires Algoritmos y Estructuras de Datos I Clase Práctica 2: Especificación - 22/08/14 (Tarde) ii) Especificación correcta posible: problema obtenerDivisores(n : Z) = result : [Z]{ requiere : n > 0; asegura soloHayDivisores: (∀d ← result) n mod d == 0); asegura estanTodosLosDivisores: (∀d ← [1..n], n mod d == 0) d ∈ result; } Observación: En definitiva lo que querı́amos modelar era d ∈ result ↔ d divide a n, pero en la primera solución sólo estabamos asegurando →. Notar que esta manera de especificar, utilizando los existenciales, nos permite dar una descripción de qué se espera devolver, evitando problemas de orden en la secuencia. Además, podrı́amos sintetizar los dos asegura en uno ¿cómo? (mismos). 6 Comentario Es necesario remarcar que hay muchas maneras de especificar, y que dentro de la correctitud objetiva existe un grado de subjetividad para elegir qué camino tomar. En ese sentido, estas posibles soluciones pretenden servir como guı́a para explorar resoluciones posibles, pero no deben interpretarse como las únicas correctas. Facultad de Ciencias Exactas y Naturales 4 Universidad de Buenos Aires