Auxiliares asignan un nombre a una expresión e aux f (argumentos) : tipo = e; Algoritmos y Estructuras de Datos I f es el nombre. los argumentos son opcionales; si están instancian variables de e. Segundo cuatrimestre de 2011 tipo es el tipo del resultado de la expresión e. e es la expresión nombrada por f . Departamento de Computación - FCEyN - UBA f puede usarse en la especificación en vez de la expresión e. Especificación - clase 3 Ejemplo aux suc(x : Int) : Int = x + 1; Lenguaje de especificación 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. 1 Definir funciones auxiliares vs. especificar problemas 2 Tipos enumerados Definimos funciones auxiliares I son expresiones del lenguaje, de un cierto tipo I usamos la función dentro de las especificaciones I una función auxiliar puede estar definida mediante una o más auxiliares. I tipo Nombre = constantes; Especificamos problemas I una especificación es un contrato que debe cumplir un algoritmo para ser solución del problema. I en la especificación de un problema (o de un tipo) no podemos usar la especificación de otro problema. I en la especificación de un problema (o de un tipo) sı́ podemos usar las funciones auxiliares ya definidas. Cantidad finita de elementos. Cada uno, denotado por una constante 3 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 4 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 prim((7, 5)) == 7 Otra forma aux esFinde2(d : Dı́a) : Bool = d > Viernes; 5 Secuencias ( o listas) 6 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]] 7 8 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. mod 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. 9 Secuencias por comprensión, varios selectores 10 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 11 [5..9] == [5, 6, 7, 8, 9] [5..9) == [5, 6, 7, 8] (5..9] == [6, 7, 8, 9] (5..9) == [6, 7, 8] 12 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? 13 Ejemplos de secuencias por comprensión y auxiliares 14 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 15 Longitud de la secuencia a Notación: long (a) se puede escribir |a| requiere |a| > 0; Primer elemento de la secuencia 16 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 17 Subsecuencias con intervalos 18 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 19 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 20 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)]); 21 Variante notacional de alguno( ) 22 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)]); 23 24 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 26 25 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); 27 I requiere (∀x ← a) x 6= 0; I requiere ¬(∃x ← a) x == 0; I requiere ¬(∃i ← [0..|a|)) a[i] == 0; 28 Especificación. Nombres para las condiciones Un ejemplo de sobrespecificación usando secuencias Problema: dada una lista, retornar una lista con los mismos elementos que no esté ordenada de menor a mayor. Problema: Dar el ı́ndice del menor elemento de una lista de enteros no negativos y distintos entre sı́. problema Desordena(a : [Int]) = res : [Int] { 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 órden de mayor a menor. 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 Ası́ sı́ está bien: problema Desordena(a : [Int]) = res : [Int] { 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)); } Otra forma de escribir la segunda precondición: requiere Distintos2: (∀i ← [0..|a|)) a[i] ∈ / a[0..i); 29 Otro ejemplo de sobre especificación Problema: dar los ı́ndices de los numeros pares de una lista, en cualquier órden. 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]); } 31 30