Especificación. Nombres para las condiciones Problema: Dar el ı́ndice del menor elemento de una lista de enteros no negativos y distintos entre sı́. Algoritmos y Estructuras de Datos I problema ı́ndiceMenorDistintos(a : [Int]) = 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; } Segundo cuatrimestre de 2013 Departamento de Computación - FCEyN - UBA Nombramos las precondiciones, para aclarar su significado. Especificación - clase 4 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. Tipos compuestos Otra forma de escribir la segunda precondición: requiere Distintos2: (∀i ← [0..|a|)) a[i] ∈ / a[0..i); 1 Un ejemplo de sobrespecificación usando secuencias 2 Ası́ sı́ está bien Problema: dada una lista, retornar una lista con los mismos elementos que no esté ordenada de menor a mayor. Problema: dada una lista, retornar una lista con los mismos elementos que no esté ordenada de menor a mayor. problema Desordena(a : [Int]) = res : [Int] { requiere |a| > 1 requiere not(todos([a[i] == a[i + 1]|i ← [0..|a| − 1)])) asegura mismos(a, res); asegura (∀i ← [0..|res| − 1)) res[i] ≥ res[i + 1]]; aux cuenta(x : T , b : [T ]) : Int = long ([y |y ← b, y == x]); aux mismos(a, b : [T ]) : Bool = (|a| == |b| ∧ (∀c ← a) cuenta(c, a) == cuenta(c, b)); } está sobrespecificado porque impone el orden de mayor a menor. problema Desordena(a : [Int]) = res : [Int] { requiere |a| > 1 requiere not(todos([a[i] == a[i + 1]|i ← [0..|a| − 1)])) asegura mismos(a, res); asegura (∃i ← [0..|res| − 1)) res[i] > res[i + 1]]; aux cuenta(x : T , b : [T ]) : Int = long ([y |y ← b, y == x]); aux mismos(a, b : [T ]) : Bool = (|a| == |b| ∧ (∀c ← a) cuenta(c, a) == cuenta(c, b)); } 3 4 Otro ejemplo de sobrespecificación Tipos compuestos I cada valor de un tipo básico representa un elemento atómico, indivisible. Vimos los tipos básicos Int, Float, Bool, Char; y también los tipos enumerados. I un valor de un tipo compuesto contiene información que puede ser dividida en componentes de otros tipos I ya vimos dos ejemplos de tipos compuestos: Problema: Dar los ı́ndices de los números pares de una lista, en cualquier orden. 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 orden de a. I I 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]); } I secuencias: un valor de tipo secuencia de enteros tiene varios componentes, cada uno es un entero tuplas: un valor de tipo par ordenado de enteros tiene dos componentes para definir un tipo compuesto, tenemos que darle I I I un nombre uno o más observadores un invariante de tipo 5 Tipos compuestos 6 Tipo Punto Especifica un punto en el plano Componentes: coordenadas (x e y ). Hay observador para obtener cada una. I Especificación de un tipo compuesto nombre, observadores, invariante de tipo I Constructores de elementos de tipo compuesto mediante la especificación de un problema. Dan como resultado un elemento de tipo compuesto. I tipo Punto { observador X (p : Punto) : Float; observador Y (p : Punto) : Float invariante True; } Especificamos un problema para la función que recibe dos números reales y construye un punto con esas coordenadas. Usamos los observadores para describir el resultado Constructor Funciones auxiliares que toman como argumentos elementos de un tipo compuesto. problema nuevoPunto(a, b : Float) = res : Punto { asegura X (res) == a; asegura Y (res) == b; } 7 8 Un funcion auxiliar para el tipo compuesto Punto Observadores Un observador es una función que mapea elementos de un tipo compuesto en los valores de sus componentes. Puede tener más parámetros. Los observadores I función auxiliar para calcular la distancia entre dos puntos 1/2 aux dist(p, q : Punto) : Float = (X (p) − X (q))2 + (Y (p) − Y (q))2 Definen el tipo compuesto I I Pueden tener precondiciones I I si dos términos del tipo dan el mismo resultado para todos los observadores, se los considera iguales. Ası́ queda definida la igualdad == para tipos compuestos (el == está en todos los tipos) si vale la precondición, no pueden quedar indefinidos No tienen postcondiciones propias 9 Tipo Cı́rculo I I I 10 Invariantes de tipo Especificación del tipo compuesto tipo Cı́rculo { observador Centro(c : Cı́rculo) : Punto; observador Radio(c : Cı́rculo) : Float; invariante Radio(c) > 0; } Un invariante de tipo es una expresión booleana que relaciona los observadores del tipo, y es verdadera para cada elemento del tipo. Un invariante de tipo Problema de construir un cı́rculo a partir de su centro y su radio. Constructor problema nuevoCı́rculo(c : Punto, r : Float) = res : Cı́rculo { requiere r > 0; asegura (Centro(res) == c ∧ Radio(res) == r ); } Problema de construir un cı́rculo a partir de su centro y un punto sobre la circunferencia. Constructor problema nuevoCı́rculoPuntos(c, x : Punto) = res : Cı́rculo { requiere dist(c, x) > 0; asegura (Centro(res) == c ∧ Radio(res) == dist(c, x)); } I Da condiciones que deben cumplir todos los elementos de un tipo compuesto. I Al especificar un tipo damos explı́citamente los invariantes de tipo. I Al observar un elemento de un tipo compuesto está garantizado que se cumplen los invariantes. I Al construir un elemento de un tipo compuesto tendremos que asegurar que se cumplan los invariantes. tipo T { observador . . . ; .. . invariante R(x); } 11 12 Tipo MatrizhT i Tipos genéricos o tipos paramétricos I I ejemplo: una instancia del tipo genérico MatrizhT i es MatrizhChari I el tipo de matrices cuyos elementos son caracteres I un tipo genérico define una familia (infinita) de tipos I algunos miembros de la familia MatrizhT i son MatrizhInti , MatrizhPuntoi , MatrizhMatrizhFloatii cada elemento de la familia es una instancia del tipo genérico (los problemas que usan un tipo genérico definen una familia de problemas) el nombre del tipo tiene parámetros I I I tipo genérico de las matrices cuyos elementos pertenecen a un tipo cualquiera T tipo MatrizhT i{ observador filas(m : MatrizhT i) : Int; observador columnas(m : MatrizhT i) : Int; observador val(m : MatrizhT i, f , c : Int) : T { requiere 0 ≤ f < filas(m); requiere 0 ≤ c < columnas(m); precondición del observador } invariante filas(m) > 0; invariante columnas(m) > 0; } definen una familia de tipos I I I variables que representan a los tipos de los componentes los parámetros de tipo se escriben entre los sı́mbolos h y i después del nombre del tipo los usamos para definir funciones auxiliares o especificar problemas 13 Operaciones genéricas con matrices I 14 Operaciones sobre matrices para tipos instanciados expresión que cuenta la cantidad de elementos de una matriz I aux elementos(m : MatrizhT i) : Int = filas(m) ∗ columnas(m); I expresión que suma los elementos de una matriz de enteros aux suma(m : MatrizhInti) : Int = P [val(m, i, j) | i ← [0..filas(m)), j ← [0..columnas(m))]; especificación del problema de cambiar el valor de una posición de una matriz I problema cambiar(m : MatrizhT i, f , c : Int, v : T ) { requiere 0 ≤ f < filas(m); requiere 0 ≤ c < columnas(m); modifica m; asegura filas(m) == filas(pre(m)); asegura columnas(m) == columnas(pre(m)); asegura val(m, f , c) == v ; asegura (∀i ← [0..filas(m))) (∀j ← [0..columnas(m)), ¬(i == f ∧ j == c)) val(m, i, j) == val(pre(m), i, j); } especificación del problema de construir la matriz identidad de n × n: problema matId(n : Int) = ident : MatrizhInti { requiere n > 0; asegura filas(ident) == columnas(ident) == n; asegura (∀i ← [0..n)) val(ident, i, i) == 1; asegura (∀i ← [0..n))(∀j ← [0..n), i 6= j) val(ident, i, j) == 0; } I I I 15 ¿importa el orden? sı́; si estuviera al revés, se podrı́a indefinir val(ident, i, i) en este caso, se indefinirı́a la poscondición, valiendo la precondición 16 El tipo SecuenciahT i I ya lo usamos con su nombre alternativo: [T ] I presentamos también sus observadores: longitud e indexación Operaciones genéricas con secuencias I aux ssc(a, b : [T ]) : Bool = (∃i ← [0..|b| − |a|] a == b[i..(i + |a|)); I aux cab(a : [T ]) : T = a[0]; I aux cola(a : [T ]) : [T ] = a[1..|a|); I aux en(t : T , a : [T ]) : Bool = [x | x ← a, x == t] 6= [ ]; } I aux sub(a : [T ], d, h : Int) : [T ] = [a[i] | i ← [d..h]]; invariante long (s : SecuenciahT i) ≥ 0 I aux todos(sec : [Bool]) : Bool = False ∈ / sec; I aux alguno(sec : [Bool]) : Bool = True ∈ sec; tipo SecuenciahT i{ observador long (s : SecuenciahT i) : Int; observador ı́ndice(s : SecuenciahT i, i : Int) : T { requiere 0 ≤ i < long (s); precondición de observador } I notaciones alternativas I I |s| para la longitud si o s[i] para la indexación 18 17 Secuencias de Char Un ejemplo con secuencias de Char: palabras y textos Indistintamente las llamamos I SecuenciahChari I [Char] I String I para representar un texto usamos el tipo SecuenciahChari I para representar una lista de palabras usamos el tipo SecuenciahSecuenciahCharii I I Problema: decidir si alguna palabra de una lista aparece en un libro Precondiciones I I 19 son secuencias que en cada posición tienen una secuencia de caracteres cada palabra debe tener al menos una letra no contienen ningún espacio (dejarı́an de ser palabras) 20 Un ejemplo con secuencias de Char: palabras y textos Qué especifica este problema? problema hayAlguna(palabras : [[Char]], libro : [Char]) = res : Bool{ requiere NoVacı́as : (∀p ← palabras) |p| > 0; requiere SinEspacios : ¬(∃p ← palabras) ‘ ‘ ∈ p; asegura res == (∃p ← palabras) ssc(p, libro); } problema ???(palabras : [[Char]], libro : [Char]) = res :???{ requiere NoVacı́as : (∀p ← palabras) |p| > 0; requiere SinEspacios P : ¬(∃p ← palabras) ‘ ‘ ∈ p; asegura res == ([β(ssc(p, libro)) | p ← palabras]) } Que las palabras contengan solamente letras requiere SinSı́mbolos: (∀p ← palabras) soloLetras(p); aux letras: [Char ] = [‘A‘..‘Z ‘] + +[‘a‘..‘z‘]; aux soloLetras(s : [Char]) : Bool = (∀l ← s) l ∈ letras; 22 21 ifThenElsehT i Tuplas I ya las mencionamos I secuencias de tamaño fijo I cada elemento puede pertenecer a un tipo distinto I ejemplos: pares, ternas Función que elige entre dos elementos del mismo tipo, según una condición booleana (guarda) si la guarda es verdadera, elige el primero I si no, elige el segundo Por ejemplo tipo ParhA, Bi{ observador prm(p : Par hA, Bi) : A; observador sgd(p : Par hA, Bi) : B; } I expresión que devuelve el máximo entre dos elementos: aux máx(a, b : Int) : Int = ifThenElsehInti(a > b, a, b) cuando los argumentos se deducen del contexto, se puede escribir directamente tipo TernahA, B, C i{ observador prm3(t : TernahA, B, C i) : A; observador sgd3(t : TernahA, B, C i) : B; observador trc3(t : TernahA, B, C i) : C ; } I I aux máx(a, b : Int) : Int = ifThenElse(a > b, a, b) aux máx(a, b : Int) : Int = if a > b then a else b I o bien expresión que dado x devuelve 1/x si x 6= 0 y 0 sino aux unoSobre(x : Float) : Float = if x 6= 0 then 1/x else 0 | {z } Notación: ParhA, Bi también se puede escribir (A, B) TernahA, B, C i también se puede escribir (A, B, C ) no se indefine cuando x = 0 23 24 Más aplicaciones de IfThenElse I agregar un elemento como primer elemento de una lista aux cons(x : T , a : [T ]) : [T ] = [if i == −1 then x else a[i] | i ← [−1..|a|)]; I concatenar dos listas aux conc(a, b : [T ]) : [T ] = [if i < |a| then a[i] else b[i − |a|] | i ← [0..|a| + |b|)]; I otra lista casi idéntica aux casi-identica(a : [T ], i : Int, v : T ) : [T ] = [if i 6= j then a[i] else v | j ← [0..|a|)]; 25