Tipos compuestos I cada valor de un tipo básico representa un elemento atómico, indivisible I Algoritmos y Estructuras de Datos I I I I Primer cuatrimestre 2007 I I Teórica de especificación 4 Tipos compuestos un valor de un tipo compuesto contiene información que puede ser dividida en componentes de otros tipos ya vimos dos ejemplos de tipos compuestos: I I I Int Float Bool Char 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 un nombre uno o más observadores 1 Observadores 2 Tipo Punto I I funciones que se aplican a valores del tipo compuesto y devuelven el valor de sus componentes I I I I si dos términos del tipo dan el mismo resultado para todos los observadores, se los considera iguales esta es la definición implı́cita de la igualdad == para tipos compuestos (el == está en todos los tipos) I pueden tener precondiciones I no tienen poscondiciones propias I si hay condiciones generales que deben cumplir los elementos del tipo, se las presenta como invariantes de tipo especificación una función que recibe dos números reales y construye un punto con esas coordenadas: problema nuevoPunto(a, b : Float) = res : Punto { asegura X (res) == a; asegura Y (res) == b; } si vale la precondición, no pueden devolver un valor indefinido I un punto en el plano componentes: coordenadas (x e y ) un observador para obtener cada una tipo Punto { observador X (p : Punto) : Float; observador Y (p : Punto) : Float } pueden tener más parámetros definen el tipo compuesto I I I I Cuando el resultado es de un tipo compuesto, usamos los observadores para describir el resultado función auxiliar para calcular la distancia entre dos puntos aux dist(p, q : Punto) : Float = (X (p) − X (q))2 + (Y (p) − Y (q))2 3 1/2 4 Tipo Cı́rculo I Invariantes de tipo sus componentes son de tipos distintos tipo Cı́rculo { observador Centro(c : Cı́rculo) : Punto; observador Radio(c : Cı́rculo) : Float; invariante Radio(c) > 0; } I I son la garantı́a de que los elementos estén bien construidos I deben cumplirse para cualquier elemento del tipo I los problemas que reciban valores del tipo compuesto como argumentos pueden suponer que se cumplen los invariantes tipo T { observador . . . ; .. . especificar el problema de construir un cı́rculo a partir de I su centro y su radio invariante R(x); problema nuevoCı́rculo(c : Punto, r : Float) = res : Cı́rculo { requiere r > 0; asegura (Centro(res) == c ∧ Radio(res) == r ); } I } I su centro y un punto sobre la circunferencia: problema probA(. . . , x : T, . . . ) = . . . { requiere . . . ∧ R(x) ∧ . . . ; | {z } no hace falta asegura . . . ; } los que devuelven resultados de ese tipo tienen que asegurar que no se violen problema probB(. . . ) = r : T { requiere . . . ; asegura Q(r ) −→ Q(r ) debe ser más fuerte que R(r ) } problema nuevoCı́rculoPuntos(c, x : Punto) = res : Cı́rculo { requiere dist(c, x) > 0; asegura (Centro(res) == c ∧ Radio(res) == dist(c, x)); } 5 Tipo MatrizhT i Tipos genéricos I I en los ejemplos vistos, cada componente es de un tipo determinado de antemano también hay tipos compuestos que representan una estructura I I I I definen una familia de tipos I los problemas que usen un tipo genérico definen una familia de problemas cada elemento de la familia es una instancia del tipo genérico permiten definir funciones auxiliares o especificar problemas I genéricos: su descripción depende únicamente de la estructura del tipo genérico especı́ficos de un tipo: su descripción solo tiene sentido para un cierto tipo I I ejemplo: una instancia del tipo genérico MatrizhT i es MatrizhChari I el nombre del tipo tiene parámetros 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); precondición del observador requiere 0 ≤ c < columnas(m); } invariante filas(m) > 0; invariante columnas(m) > 0; } se llaman tipos genéricos o tipos paramétricos I I I su contenido van a ser valores no siempre del mismo tipo comportamiento uniforme I I 6 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 el tipo de matrices cuyos elementos son caracteres un tipo genérico define una familia (infinita) de tipos I algunos miembros de la familia MatrizhT i son MatrizhInti , 7 MatrizhPuntoi , MatrizhMatrizhFloatii 8 Operaciones genéricas con matrices I Operaciones sobre matrices para tipos instanciados I expresión que cuenta la cantidad de elementos de una matriz aux suma(m : MatrizhInti) : Int = P [val(m, i, j) | i ← [0..filas(m)), j ← [0..columnas(m))]; aux elementos(m : MatrizhT i) : Int = filas(m) ∗ columnas(m); I expresión que suma los elementos de una matriz de enteros 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 ¿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 10 9 El tipo SecuenciahT i Operaciones genéricas con secuencias I I ya lo usamos con su nombre alternativo: [T ] I presentamos también sus observadores: longitud e indexación I I 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 I I I I I notaciones alternativas I I podemos definir |s| para la longitud si o s[i] para la indexación 11 aux ssc(a, b : [T ]) : Bool = (∀i ← [0..|b| − |a|)) a == b[i..(i + |a|)); aux cab(a : [T ]) : T = a[0]; aux cola(a : [T ]) : [T ] = a[1..|a|); aux en(t : T , a : [T ]) : Bool = [x | x ← a, x == t] 6= [ ]; aux sub(a : [T ], d, h : Int) : [T ] = [a[i] | i ← [d..h], (0 ≥ ∧h < |a|)]; aux todos(sec : [Bool]) : Bool = false ∈ / sec; aux alguno(sec : [Bool]) : Bool = true ∈ sec; I para algunas no usamos directamente los observadores I aprovechamos la notación de listas por comprensión I el selector es parte de la notación de listas por comprensión (estructura especial de nuestro lenguaje de especificación) 12 Operaciones sobre secuencias para tipos instanciados I I Tuplas para representar textos (cadenas de caracteres) usamos el tipo SecuenciahChari (o [Char]) queremos ver si alguna palabra de una lista aparece en un libro I I I 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); } I secuencias que en cada posición tienen una secuencia de caracteres 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 ; } precondiciones I I I tipo ParhA, Bi{ observador prm(p : Par hA, Bi) : A; observador sgd(p : Par hA, Bi) : B; } lista de palabras: tipo [[Char]] o SecuenciahSecuenciahCharii I I I ya las mencionamos secuencias de tamaño fijo cada elemento puede pertenecer a un tipo distinto ejemplos: pares, ternas cada palabra debe tener al menos una letra no contienen ningún espacio (dejarı́an de ser palabras) en vez de SinEspacios: que las palabras no contengan ningún sı́mbolo que no sea una letra I notación I requiere SinSı́mbolos: (∀p ← palabras) soloLetras(p); aux letras: [Char ] = [‘A‘..‘Z ‘] + +[‘a‘..‘z‘]; aux soloLetras(s : [Char]) : Bool = (∀l ← s) l ∈ letras; I ParhA, Bi también se puede escribir (A, B) TernahA, B, C i también se puede escribir (A, B, C ) 13 ifThenElsehT i 14 Más aplicaciones de IfThenElse Función que elige entre dos elementos del mismo tipo, según una condición (guarda) I I si la guarda es verdadera, elige el primero si no, elige el segundo I aux cons(x : T , a : [T ]) : [T ] = [if i == −1 then x else a[i] | i ← [−1..|a|)]; Por ejemplo I I expresión que devuelve el máximo entre dos elementos: cuando los argumentos se deducen del contexto, se puede escribir directamente I concatenar dos listas aux conc(a, b : [T ]) : [T ] = [if i < |a| then a[i] else b[i − |a|] | i ← [0..|a| + |b|)]; aux máx(a, b : Int) : Int = ifThenElsehInti(a > b, a, b) 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 agregar un elemento como primer elemento de una lista I o bien cambiar el valor de una posición aux cambiar(a : [T ], i : Int, v : T ) : [T ] = [if i 6= j then a[i] else v | j ← [0..|a|)]; 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 } no se indefine cuando x = 0 15 16