Lenguaje de especificación Algoritmos y Estructuras de Datos I I Sirve para especificar problemas I Es un lenguaje “tipado”: los elementos pertenecen a distintos dominios o conjuntos (enteros, reales, tipos definidos por nosotros, por ustedes, etc.) I Incluye a la lógica proposicional trivaluada con el tipo Bool I Incluye tipos compuestos I Un tipo compuesto (central) es la secuencia. Primer cuatrimestre de 2012 Departamento de Computación - FCEyN - UBA Especificación - clase 3 Lenguaje de especificación 1 Especificar problemas versus definir funciones auxiliares 2 Auxiliares La especificación de un problema asignan un nombre a una expresión e aux f (argumentos) : tipo = e; I es un contrato que debe cumplir un algoritmo para ser solución del problema. I no puede incluir la especificación de otro problema. los argumentos son opcionales; si están instancian variables de e. I sı́ puede incluı́r funciones auxiliares ya definidas. tipo es el tipo del resultado de la expresión e. f es el nombre. e es la expresión nombrada por f . La definición de una función auxiliar I f puede usarse en la especificación en vez de la expresión e. es un nombre para una expresiones del lenguaje de especificación de un cierto tipo I sirve como abreviatura, o para aumentar legilibildad I se usa adentro de una o más especifcaciones I puede estar basada en una o más funciones auxiliares. Ejemplo aux suc(x : Int) : Int = x + 1; Se puede usar, por ejemplo, en la postcondición de un problema. Las expresiones auxiliares facilitan la lectura y la escritura de especificaciones. Funcionan como abreviaturas. 3 4 Tipos de datos Tipo Bool (valor de verdad) Sus valores son: 1, 0 , − I constantes: true, false, ⊥ (o Indef) I conectivos lógicos: ¬, ∧, ∨, →, ↔ con la semántica secuencial trivaluada que vimos antes Un tipo de datos es un conjunto de valores con operaciones asociadas. I tipos básicos: Z, R, Bool, Char (con sus constantes, y sus operaciones) I I I I tipos compuestos I para hablar de un elemento de un tipo T en nuestro lenguaje escribimos un término (o expresión) I I I I I I I variable de tipo T constante de tipo T función aplicada a otros términos (del tipo T o de otro tipo) comparación: A == B I I todos los tipos tienen un elemento distinguido: ⊥ o Indef. I I I ¬A se puede escribir no(A) (A ∧ B) se puede escribir (A && B) (A ∨ B) se puede escribir (A || B) (A → B) se puede escribir (A --> B) (A ↔ B) se puede escribir (A <--> B) todos los tipos tienen esta operación (A y B deben ser del mismo tipo T ) es de tipo bool es verdadero si el valor de A es igual al valor de B (salvo que alguno esté indefinido - ver hoja 11) A 6= B o A ! = B es equivalente a ¬(A == B) semántica secuencial trivaluada 6 5 Tipo Z (o se puede escribir Int) Tipo R (o se puede escribir Float) Sus elementos son los números enteros I I constantes: 0 ; 1 ; operaciones aritméticas I I I I I I I I ; 2 ; −2 ; ... Sus elementos son los números reales. I a + b (suma) a − b (resta) a ∗ b (multiplicación) a div b (división entera) a mod b (resto de dividir a a por b) ab o pot(a,b) (potencia) abs(a) (valor absoluto) I I I I I I I I si A es verdadero si A es falso si A es indefinido 7 ; 81 ; 7,4552 ; π... las mismas que Int, salvo div y mod a/b (división) logb (a) (logaritmo) trigonométricas las mismas que para Int conversión a entero I β o beta. Si A es de tipo Bool, −7 comparaciones (de tipo bool) I a < b (menor); a ≤ b o a <= b (menor o igual) a > b (mayor); a ≥ b o a >= b (mayor o igual) 1 β(A) = beta(A) = 0 − constantes: 0 ; 1 ; operaciones aritméticas I comparaciones (de tipo bool) I I −1 bac o int(a) todos los términos de tipo Int pueden usarse como términos de tipo Float 8 Tipo Char (caracteres) Términos Sus elementos son los las letras, dı́gitos y sı́mbolos I I constantes: ‘a‘, ‘b‘, ‘c‘, . . . , ‘z‘, . . . , ‘A‘, ‘B‘, ‘C ‘, . . . , ‘Z ‘, . . . , ‘0‘, ‘1‘, ‘2‘, . . . , ‘9‘ (en algún orden) I numera todos los caracteres no importa mucho cuál es el valor de cada uno, pero I I I I constantes del tipo I combinaciones de funciones aplicadas a funciones, constantes y variables Ejemplos de términos de tipo Int ord(‘a‘) + 1 == ord(‘b‘) ord(‘A‘) + 1 == ord(‘B‘) ord(‘1‘) + 1 == ord(‘2‘) función char I I variables del tipo o función ord I I I es la inversa de ord I 0+1 I ((3 + 4) ∗ 7)2 − 1 I 2 ∗ β(1 + 1 == 2) I 1 + ord(‘A‘) con x variable de tipo Int; y de tipo Float; z de tipo Bool I las comparaciones entre caracteres son comparaciones entre sus órdenes I I I I a < b es equivalente a ord(a) < ord(b) lo mismo para ≤, >, ≥ I 2∗x +1 β(y 2 > π) + x (x mod 3) ∗ β(z) 9 Semántica de los términos 10 Tipos enumerados Los términos representan elementos de los tipos I Los términos tienen valor indefinido cuando no se puede hacer alguna operación. Por ejemplo 1 div 0, o , (−1)1/2 tipo Nombre = constantes; Las operaciones son estrictas, excepto para Bool: si uno de sus argumentos es indefinido, el resultado también está indefinido. Ejemplos I 0 ∗ (−1)1/2 es indefinido (∗ es estricto) I 01/0 es indefinido (pot es estricto) I ((1 + 1 == 2) ∨ (0 > 1/0)) es verdadero (∨ no es estricto) Cantidad finita de elementos. Cada uno, denotado por una constante Las comparaciones con ⊥ son indefinidas; en particular, si x está indefinido, x == x es indefinido (no es verdadero) 11 I Nombre (del tipo): tiene que ser nuevo I constantes: nombres nuevos separados por comas I Convención: todos con mayúsculas I ord(a) da la posición del elemento en la definición (empezando de 0) I Inversa: ord−1 12 Tipos enumerados. Ejemplos Tipo upla El tipo Bool es básico; está predefinido ası́: Definimos el tipo Dı́a ası́: Uplas, de dos o más elementos, de cualquier tipo. (T1 , T2 , ...Tn ): Tipo de las n-uplas de elementos de tipo T1 , T2 , ... Tn , donde n es fijo. El tipo upla es tipo Dı́a = Lunes, Martes, Miércoles, Jueves, Viernes, Sábado, Domingo; primitivo del lenguaje de especificación. tipo Bool = 0, 1, -; I I I I I I Ejemplos Valen (Int, Int) son los pares ordenados de enteros. ord(Lunes) == 0 Dı́a(2) == Miércoles Jueves < Viernes (Int, Char, Bool) son la triplas ordenadas con un entero, luego un carácter y luego un valor booleano. Podemos definir prm, sgd, 3esimo, nesimo dan sus componentes. aux esFinde(d : Dı́a) : Bool = (d == Sábado || d == Domingo); Ejemplo prm((7, 5)) == 7 Otra forma aux esFinde2(d : Dı́a) : Bool = d > Viernes; 13 Secuencias ( o listas) 14 Secuencias. Notación La secuencia vacı́a (de elementos de cualquier tipo) se escribe [ ] Una secuencias de elementos de tipo T es una sucesión finita de elementos de tipo T (importa el órden). Una forma de escribir un elemento de tipo secuencia de tipo T es escribir dentro de un juego de corchetes cada uno de los elementos de tipo T separados por comas. T es un tipo arbitrario Ejemplos [T ] es el tipo de las secuencias cuyos elementos son de tipo T una secuencia de Int Hay secuencias de Int, de Bool, de Dı́as, de 5-uplas; [1, 1 + 1, 3, 2 ∗ 2, 3, 5] también hay secuencias de secuencias de tipo T . una secuencia de secuencias de Int etcétera [[12, 13], [-3, 9, 0], [5], [], [], [3]] 15 16 Secuencias por comprensión Secuencias por comprensión. Ejemplos [expresión | selectores, condiciones] [expresión | selectores, condiciones] expresión: cualquier expresión válida del lenguaje [x | x ∈ [1, 2, 3, 4, 5, 6, 7, 8, 9], x selectores: tienen la forma variable ∈ secuencia, donde variable se instancia en cada uno de los elementos de la secuencia. mód 2 == 0] == [2, 4, 6, 8] condiciones: son expresiones de tipo Bool sobre una o más variable. En caso de que no haya condiciones, se considera siempre verdadero. [ [x] | x ∈ [ ‘a‘,‘l‘,‘g‘,‘o‘,‘r‘,‘i‘ ,‘t‘, ‘m‘, ‘o‘, ‘s‘, ‘ ‘, ‘I‘] ] == [ [ ‘a‘ ], [ ‘l‘ ], [ ‘g‘ ], [ ‘o‘ ], [ ‘r‘ ], [ ‘i‘ ], [ ‘t‘ ], [ ‘m‘ ], [ ‘o‘ ], [ ‘s‘ ], [ ‘ ‘ ], [ ‘I‘ ] ] Esta notación da una nueva secuencia que contiene, uno tras otro, los valores de la expresion calculados a partir de los elementos indicados por los selectores que cumplen las condiciones. [(x, y ) | x ∈ [1, 2], y ∈ [1, 2, 3], x < y ] == [(1, 2), (1, 3), (2, 3)] A las variables dentro de los selectroes se las llama ligadas. A las demás se las llama libres. 17 Secuencias por comprensión, varios selectores 18 Secuencias dadas por intervalos [expresión1 ..expresión2 ] [(x, y )|x ← a, y ← b, x =/= y ] Las expresiones tienen que ser del mismo tipo, discreto y totalmente ordenado (por ejemplo, Int, Char, enumerados) es distinto de La secuencia tiene todos los valores del tipo entre expresión1 y expresión2 (ambos inclusive) [(x, y )|y ← b, x ← a, x =/= y ] Sı́, el orden importa... Si no vale expresión1 ≤ expresión2 , la secuencia es vacı́a Con un paréntesis en lugar de un corchete, se excluye uno de los extremos o ambos [(x, y )|x ← [1, 2], y ← [3, 4]] == [(1, 3), (1, 4), (2, 3), (2, 4)] Ejemplos: I [(x, y )|y ← [3, 4], x ← [1, 2]] == [(1, 3), (2, 3), (1, 4), (2, 4)] I I I 19 [5..9] == [5, 6, 7, 8, 9] [5..9) == [5, 6, 7, 8] (5..9] == [6, 7, 8, 9] (5..9) == [6, 7, 8] 20 Ejemplos de secuencias por comprensión Ejemplos de secuencias por comprensión y auxiliares Divisores comunes (asumir a y b positivos) aux divCom(a, b : Int) : [Int] = Cuadrados de los elementos impares [x|x ← [1..a + b], divide(x, a), divide(x, b)]; aux cuadImp(a : [Int]) : [Int] = [x ∗ x|x ← a, ¬divide(2, x)]; aux divide(a, b : Int) : Bool = b mod a == 0; Ejemplo: Ejemplo: cuadImp([1..9)) == [1, 9, 25, 49] divCom(8, 12) == [1, 2, 4] ¿Cuáles son variables libres y cuáles ligadas? 21 Ejemplos de secuencias por comprensión y auxiliares 22 Operaciones con secuencias I Longitud: long (a : [T ]) : Int I I Suma de los elementos distintos I aux sumDist(a, b : [Int]) : [Int] = [x + y | x ← a, y ← b, x 6= y ]; Indexación: indice(a : [T ], i : Int) : T I I Ejemplo: I I sumDist([1, 2, 3], [2, 3, 4, 5]) == [3, 4, 5, 6, 5, 6, 7, 5, 7, 8] I requiere 0 ≤ i < |a|; Elemento en la i-ésima posición de a La primera posición es la 0 Notación: indice(a, i) se puede escribir a[i] y también ai Cabeza: cab(a : [T ]) : T I I 23 Longitud de la secuencia a Notación: long (a) se puede escribir |a| requiere |a| > 0; Primer elemento de la secuencia 24 Más operaciones con secuencias I Cola: cola(a : [T ]) : [T ] I I I y más operaciones con secuencias I requiere |a| > 0; La secuencia sin su primer elemento I I Pertenencia: en(t : T , a : [T ]) : Bool I I I I Indica si el elemento aparece (al menos una vez) en la secuencia Notación: en(t, a) se puede escribir t en a y también t ∈ a t∈ / a es ¬(t ∈ a) I Agregar cabeza: cons(t : T , a : [T ]) : [T ] I I Sublista de a en las posiciones entre d y h (ambas inclusive) Cuando no es 0 ≤ d ≤ h < |a|, da la secuencia vacı́a otra secuencia cambiando el elemento que hay en una posición: cambiar (a : [T ], i : Int, val : T ) : [T ] I Una secuencia como a, agregándole t como primer elemento Notación: cons(t, a) se puede escribir t : a Secuencia con los elementos de a, seguidos de los de b Notación: conc(a, b) se puede escribir a + +b Subsecuencia: sub(a : [T ], d, h : Int) : [T ] I I I Concatenación: conc(a, b : [T ]) : [T ] I requiere 0 ≤ i < |a|; Igual a la secuencia a, pero el valor en la posición i es val 25 Subsecuencias con intervalos 26 Operaciones sobre secuencias que dan números I I Sumatoria: sum(sec : [T ]) : T I Notación para obtener la subsecuencia de una secuencia dada que está en un intervalo de posiciones I I I I Admite intervalos abiertos I I I I I I I a[d..h] == sub(a, d, h) a[d..h) == sub(a, d, h − 1) a(d..h] == sub(a, d + 1, h) a(d..h) == sub(a, d + 1, h − 1) a[d..] == sub(a, d, |a| − 1) a(d..] == sub(a, d + 1, |a| − 1) I Productoria: prod(sec : [T ]) : T I I I I 27 T debe ser un tipo numérico, es decir, Float o Int Calcula la suma de todos los elementos de la secuencia Si sec es vacı́a, el resultado es 0 P Notación: sum(sec) se puede escribir sec Ejemplo: P aux potNegDosHasta(n : Int) : Float = [2−m | m ← [1..n]]; T debe ser un tipo numérico (Float, Int) Calcula el producto de todos los elementos de la secuencia Si sec es vacı́a, el resultado es 1 Q Notación: prod(sec) se puede escribir sec 28 Operaciones con secuencias que dan un valor booleano Variante notacional de todos( ) (∀ selectores, condiciones) expresión I todos(sec : [Bool]) : Bool I Es verdadero solamente si todos los elementos de la secuencia son verdaderos (o la secuencia es vacı́a) I I alguno(sec : [Bool]) : Bool Término de tipo Bool Afirma que todos los elementos de una lista por comprensión cumplen una propiedad Equivale a escribir todos([expresión | selectores, condiciones]) Ejemplo: “todos los elementos en posiciones pares son mayores que 5”: auxpar (n : Int) : Bool = n mod 2 == 0; Es verdadero solamente si algún elemento de la secuencia es verdadero (y ninguno es Indef) aux posParM5(a : [Float]) : Bool = (∀i ← [0..|a|), par (i)) a[i] > 5; Esta expresión es equivalente a todos([a[i] > 5|i ← [0..|a|), par (i)]); 29 Variante notacional de alguno( ) 30 Cantidad de ocurrencias en una secuencia Para contar cuántos elementos de una secuencia cumplen una condición medimos la longitud de una secuencia definida por comprensión (∃ selectores, condiciones)expresión I Hay algún elemento que cumple la propiedad Ejemplos: ¿Cuántas veces aparece el elemento x en la secuencia a? I Equivale a alguno([expresión | selectores, condiciones]) aux cuenta(x : T , a : [T ]) : Int = long ([y |y ← a, y == x]); I Notación: en lugar de ∃ se puede escribir existe o existen Podemos usarla para saber si dos secuencias tienen los mismos elementos (quizás en otro orden) Ejemplo: “Hay algún elemento de la lista que es par y mayor que 5”: aux mismos(a, b : [T ]) : Bool = (|a| == |b| ∧ (∀c ← a) cuenta(c, a) == cuenta(c, b)); aux hayParM5(a : [Int]) : Bool = (∃x ← a, par (x)) x > 5; ¿Cuántos primos positivos hay que sean menores a n? Es equivalente a aux primosMenores(n : Int) : Int = long ([y | y ← [0..n), primo(y )]); aux primo(n : Int) : Bool = (n ≥ 2 ∧ ¬(∃m ← [2..n)) n mod m == 0); alguno([x > 5|x ← a, par (x)]); 31 32 Acumulación Ejemplos de acumulación Suma aux sum(l : [Float]) : Float = acum(s + i | s : Float = 0, i ← l); acum(expresión | inicializacion, selectores, condición) Construye un valor a partir de una o más secuencias, acumulando valores Productoria inicializacion tiene la forma acumulador : tipoAcum = init aux prod(l : [Float]) : Float = acum(p ∗ i | p : Float = 1, i ← l); I acumulador es un nombre de variable (nuevo) de tipo tipoAcum I init es una expresión de tipo tipoAcum Fibonacci aux fiboSuc(n : Int) : [Int] = acum(f + +[f [i − 1] + f [i − 2]] | f : [Int] = [1, 1], i ← [2..n]); expresión: es arbitraria, y puede (y suele) aparecer el acumulador si n ≥ 1, da los primeros n + 1 números de Fibonacci si n == 0, da [1, 1] por ejemplo, fiboSuc(5) == [1, 1, 2, 3, 5, 8] selectores y condición son como en las secuencias por comprensión Importante: acumulador no puede aparecer en la condición El n-ésimo número de Fibonacci (n ≥ 1) El valor inicial de acumulador es el valor de init Por cada valor de los selectores, tenemos un valor de expresión que se acumula en la variable acumulador. El resultado es el valor final del acumulador. aux fibo(n : Int) : Int = (fiboSuc(n − 1))[n − 1]; por ejemplo, fibo(6) == 8 34 33 Más ejemplos de acumulación Ejemplos de especificación con listas y acumulación Sumar los inversos multiplicativos de los reales en una lista Concatenación de elementos de una secuencia de secuencias problema sumarInvertidos(a : [Float]) = result : Float { requiere 0 ∈ / a; P asegura result = [1/x | x ← a]; } aux concat(a : [[T ]]) : [T ] = acum(l + +c | l : [T ] = [], c ← a); por ejemplo, concat([[1, 2], [3], [4, 5, 6]]) == [1, 2, 3, 4, 5, 6] Precondición: que el argumento no contenga ningún 0 Si no, la poscondición podrı́a indefinirse. Secuencias por comprensión Formas equivalentes: El término [expresión | selectores, condición] es equivalente a acum(res + +[expresión] | res : [T ] = [], selectores, condición); 35 I requiere (∀x ← a) x 6= 0; I requiere ¬(∃x ← a) x == 0; I requiere ¬(∃i ← [0..|a|)) a[i] == 0; 36 Un ejemplo de sobrespecificación usando secuencias Otro ejemplo de sobrespecificación Problema: dada una lista, retornar una lista con los mismos elementos que no esté ordenada de menor a mayor. Problema: dar los ı́ndices de los numeros pares de una lista, en cualquier órden. problema desordenar(a : [Int]) = res : [Int] { requiere |a| > 1; requiere not(todos([a[0] == a[i]|i ∈ [1..|a|)])); asegura mismos(a, res); asegura ordenado up(res); aux ordenado up(`) :Bool = (∀i ← [0..|ell| − 1)) `[i] ≥ `[i + 1]]; } está sobrespecificado porque impone el órden de mayor a menor. problema ı́ndicesDePares(a : [Int]) = res : [Int]{ asegura 0 ≤ |res| ≤ |a|; asegura res == [i|i ← [0..|a|), a[i]mod2 == 0] ; } sobrespecifica el problema, porque exige que res siga el órden de a. Ası́ sı́ está bien: problema ı́ndicesDePares(a : [Int]) = res : [Int]{ asegura 0 ≤ |res| ≤ |a|; asegura mismos(res, [i|i ← [0..|a|), a[i]mod2 == 0]); } Ası́ sı́ está bien: problema desordenar(a : [Int]) = res : [Int] { requiere |a| > 1; requiere not(todos([a[0] == a[i]|i ∈ [1..|a|)])); asegura mismos(a, res); asegura not(ordenado up(res)); aux ordenado up(`) :Bool = (∀i ← [0..|ell| − 1)) `[i] ≥ `[i + 1]]; } 37 Especificación. Nombres para las condiciones Problema: Dar el ı́ndice del menor elemento de una lista de enteros no negativos y distintos entre sı́. problema ı́ndiceMenorDistintos(a : [Float]) = res : Int { requiere NoNegativos: (∀x ← a) x ≥ 0; requiere Distintos: (∀i ← [0..|a|), j ← [0..|a|), i 6= j) ai 6= aj ; asegura 0 ≤ res < |a|; asegura (∀x ← a) a[res] ≤ x; } Nombramos las precondiciones, para aclarar su significado I Los nombres también pueden usarse como predicados en cualquier lugar de la especificación I El nombre no alcanza, hay que escribir la precondición en el lenguaje Otra forma de escribir la segunda precondición: requiere Distintos2: (∀i ← [0..|a|)) a[i] ∈ / a[0..i); 39 38