Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Teoricas Algo 1
Teoricas Algo 1
Especificación
I clase 1, p. 3
I clase 2, p. 28
I clase 3, p. 48
Cursada
I clases teóricas
I Paula Zabala y Santiago Figueira
I clases prácticas
Especificación I Carlos López Pombo, Pablo Turjanski y Martı́n Urtasun
Clase 1 I sitio web de la materia: www.dc.uba.ar/people/materias/algo1
I régimen de aprobación
Introducción a la especificación de problemas I parciales
I 3 parciales
I 3 recuperatorios (al final de la cursada)
I trabajos prácticos
I 3 entregas
I 3 recuperatorios (cada uno a continuación)
I grupos de 4 alumnos
I examen final (si lo dan en diciembre 2008 o febrero/marzo
2009, cuenta la nota de la cursada)
3 4
Objetivos y contenidos Especificación, algoritmo, programa
I Objetivos:
I especificar problemas
I describirlos de forma no ambigua 1. especificación = descripción del problema
I escribir programas sencillos I ¿qué problema tenemos?
I tratamiento de secuencias
I razonar acerca de estos programas
I en lenguaje formal
I demostrar matemáticamente que un programa es correcto
I describe propiedades de la solución
I vision abstracta del proceso de computación I Dijkstra, Hoare (años 70)
I manejo simbólico y herramientas para demostrar
↓
Una buena especificación responde algunas:
5. mantenimiento: corregir errores y adaptarlo a nuevos I Necesito una función que, dadas dos fechas en formato dd/mm/aaaa, me
requerimientos devuelva la cantidad de dı́as que hay entre ellas.
todavı́a faltan los requerimientos no funcionales
I forma de entrada de parámetros y de salida de resultados, tipo de
E D P V M ··· computadora, tiempo con el que se cuenta para programarlo, etc.
I no son parte de la especificación funcional y quedan para otras materias
9 10
11 12
Pasos primitivos 3. Programación - Programas
13 14
I objetivos:
I tiempo después, encontramos errores I antes de programar: entender el problema
I el programa no cumplı́a la especificación
I después de programar: determinar si el programa es correcto
I la especificación no describı́a correctamente el problema I testing
I verificación formal
I o cambian los requerimientos I derivación (construyo el programa a partir de la especificación)
I puede hacerlo el mismo equipo u otro
I justifica las etapas anteriores I estrategia:
I si se hicieron bien la especificación, diseño, programación y I evitar pensar (todavı́a) una solución para el problema
validación, las modificaciones van a ser más sencillas y menos I limitarse a describir cuál es el problema a resolver
frecuentes I qué propiedades tiene que cumplir una solución para resolver
el problema
I buscamos el qué y no el cómo
17 18
19 20
Tipos de datos Encabezado de un problema
Indica la forma que debe tener una solución.
Especificación formal
27 28
Lógica proposicional - sintaxis Ejemplos
I sı́mbolos
true , false , ⊥ , ¬ , ∧ , ∨ , → , ↔ , ( , )
¿Cuáles son fórmulas?
I variables proposicionales (infinitas) I p∨q no
I (p ∨ q) sı́
p , q , r , ...
I p∨q →r no
I fórmulas I (p ∨ q) → r no
1. true, false y ⊥ son fórmulas
2. cualquier variable proposicional es una fórmula I ((p ∨ q) → r ) sı́
3. si A es una fórmula, ¬A es una fórmula I (p → q → r ) no
4. si A1 , A2 , . . . , An son fórmulas, (A1 ∧ A2 ∧ · · · ∧ An ) es una
fórmula
5. si A1 , A2 , . . . , An son fórmulas, (A1 ∨ A2 ∨ · · · ∨ An ) es una
fórmula
6. si A y B son fórmulas, (A → B) es una fórmula
7. si A y B son fórmulas, (A ↔ B) es una fórmula
29 30
31 32
Ejemplo: tabla de verdad para ((p ∧ q) → r ) Semántica trivaluada
I 3 valores de verdad posibles
1. verdadero (1)
2. falso (0)
p q r (p ∧ q) ((p ∧ q) → r ) 3. indefinido (−)
1 1 1 1 1 I es la que vamos a usar en esta materia
1 1 0 1 0 I ¿por qué?
1 0 1 0 1 I queremos especificar problemas que puedan resolverse con un
1 0 0 0 1 algoritmo
0 1 1 0 1 I puede ser que un algoritmo realice una operación inválida
0 1 0 0 1 I dividir por cero
I raı́z cuadrada de un número negativo
0 0 1 0 1
0 0 0 0 1
I necesitamos contemplar esta posibilidad en la especificación
I interpretación:
I true siempre vale 1
I false siempre vale 0
I ⊥ siempre vale −
I se extienden las definiciones de ¬, ∧, ∨, →, ↔
33 34
39 40
Tipos de datos Tipo Bool (valor de verdad)
I valores: 1, 0 , −
I constantes: true, false, ⊥ (o Indef)
I conectivos lógicos: ¬, ∧, ∨, →, ↔ con la semántica
I conjunto de valores con operaciones trivaluada que vimos antes
I vamos a empezar viendo tipos básicos I ¬A se puede escribir no(A)
I para hablar de un elemento de un tipo T en nuestro lenguaje, I (A ∧ B) se puede escribir (A && B)
escribimos un término o expresión I (A ∨ B) se puede escribir (A || B)
I variable de tipo T
I (A → B) se puede escribir (A --> B)
I constante de tipo T
I (A ↔ B) se puede escribir (A <--> B)
I función (operación) aplicada a otros términos (del tipo T o de I comparación: A == B
otro tipo) I todos los tipos tienen esta operación (A y B deben ser del
I todos los tipos tienen un elemento distinguido: ⊥ o Indef mismo tipo T )
I es de tipo bool
I es verdadero si el valor de A igual al valor de B (salvo que
alguno esté indefinido - ver hoja 47)
I A 6= B o A ! = B es equivalente a ¬(A == B)
I semántica secuencial
41 42
47 48
Funciones auxiliares Definir vs. especificar
I Facilitan la lectura y la escritura de especificaciones
I Asignan un nombre a una expresión I Definimos funciones auxiliares
aux f (parametros) : tipo = e;
I Expresión del lenguaje a la que la función es equivalente
I Esto permite usar la función dentro de las especificaciones
I f es el nombre de la función
I Puede usarse en el resto de la especificación en lugar de la I Especificamos problemas
expresión e I Condiciones (el contrato) que deberı́a cumplir alguna función
para ser solución del problema
I Los parámetros son opcionales I No quiere decir que exista esa función o que sepamos cómo
I Se reemplazan en e cada vez que se usa f escribirla
I Podrı́a no haber ningún algoritmo que sirviera como solución
I tipo es el tipo del resultado de la función (el tipo de e) I Si damos la solución va a ser en otro lenguaje (por ejemplo,
de programación)
Ejemplo I En la especificación de un problema o de un tipo no podemos
usar otra función que hayamos especificado
aux suc(x : Int) : Int = x + 1;
I Podemos definir
I Nombre (del tipo): tiene que ser nuevo
aux esFinde(d : Dı́a) : Bool = (d == Sábado || d == Domingo);
I constantes: nombres nuevos separados por comas
I Convención: todos con mayúsculas I Otra forma
I ord(a) da la posición del elemento en la definición aux esFinde2(d : Dı́a) : Bool = d > Viernes;
(empezando de 0)
I Opuesta: nombre u ord−1
51 52
Secuencias Notación
I Una forma de escribir un elemento de tipo secuencia de tipo
T es escribir varios términos de tipo T separados por comas,
entre corchetes
I También se llaman listas
I Familia de tipos secuencia de Int: [1, 1 + 1, 3, 2 ∗ 2, 3, 5]
I Para cada tipo de datos hay un tipo secuencia I La secuencia vacı́a (de elementos de cualquier tipo) se
I Tiene como elementos las secuencias de elementos de ese tipo
representa []
I Secuencia: varios elementos del mismo tipo, posiblemente I Se puede formar secuencias de elementos de cualquier tipo
repetidos, ubicados en un cierto orden I Como las secuencias de enteros son tipos, existen por ejemplo
I Muy importantes en el lenguaje de especificación las secuencias de secuencias de enteros
I [T ]: Tipo de las secuencias cuyos elementos son de tipo T secuencia de secuencias de enteros:
53 54
57 58
I Cabeza: cab(a : [T ]) : T
I requiere |a| > 0;
I Primer elemento de la secuencia
59 60
Más operaciones Más operaciones
61 62
63 64
Operaciones de combinación Para todo
Existe Cantidades
I Es habitual querer contar cuántos elementos de una secuencia
cumplen una condición
(∃ selectores, condiciones)expresión I Para eso, medimos la longitud de una secuencia definida por
comprensión
I Hay algún elemento que cumple la propiedad
Ejemplos:
I Equivale a alguno([expresión | selectores, condiciones])
I “¿cuántas veces aparece el elemento x en la secuencia a?”
I Notación: en lugar de ∃ se puede escribir existe o existen
aux cuenta(x : T , a : [T ]) : Int = long ([y |y ← a, y == x]);
Podemos usarla para saber si dos secuencias tienen los mismos
I Ejemplo: “Hay algún elemento de la lista que es par y mayor elementos (en otro orden)
que 5”:
aux mismos(a, b : [T ]) : Bool =
aux hayParM5(a : [Int]) : Bool = (∃x ← a, par (x)) x > 5; (|a| == |b| ∧ (∀c ← a) cuenta(c, a) == cuenta(c, b));
Es equivalente a
I “¿cuántos primos positivos hay que sean menores a n?”
aux primosMenores(n : Int) : Int = long ([y | y ← [0..n), primo(y )]);
alguno([x > 5|x ← a, par (x)]);
aux primo(n : Int) : Bool =
(n ≥ 2 ∧ ¬(∃m ← [2..n)) n mod m == 0);
67 68
Acumulación Ejemplos de acumulación
I aux sum(l : [Float]) : Float = acum(s+i | s : Float = 0, i ← l);
acum(expresión | inicializacion, selectores, condición)
I Productoria
I Notación parecida a las secuencias por comprensión aux prod(l : [Float]) : Float =
I Construye un valor a partir de una o más secuencias acum(p ∗ i | p : Float = 1, i ← l);
I inicializacion tiene la forma acumulador : tipoAcum = init
I acumulador es un nombre de variable (nuevo)
I Fibonacci
I init es una expresión de tipo tipoAcum aux fiboSuc(n : Int) : [Int] =
I selectores y condición: Como en las secuencias por acum(f ++[f [i −1]+f [i −2]] | f : [Int] = [1, 1], i ← [2..n]);
comprensión
I expresión: También, pero puede (y suele) aparecer el I si n ≥ 1, devuelve los primeros n + 1 números de Fibonacci
acumulador
I si n == 0, devuelve [1, 1]
I acumulador no puede aparecer en la condición I por ejemplo, fiboSuc(5) == [1, 1, 2, 3, 5, 8]
I Significado:
I El valor inicial de acumulador es el valor de init I n-ésimo número de Fibonacci (n ≥ 1)
I Por cada valor de los selectores, se calcula la expresión aux fibo(n : Int) : Int = (fiboSuc(n − 1))[n − 1];
I Ese es el nuevo valor que toma el acumulador
I El resultado de acum es el resultado final del acumulador (de
I por ejemplo, fibo(6) == 8
tipo tipoAcum) 69 70
71 72
Ejemplos de especificación Parámetros modificables
I Calcular el cociente de dos enteros I Alternativa 2
I Único resultado: el cociente
problema división(a, b : Int) = result : Int { I Resto: parámetro modificable
requiere b 6= 0;
asegura result == a div b; problema cocienteResto2(a, b, r : Int) = q : Int {
} requiere b > 0;
modifica r ;
I Calcular el cociente y el resto, para divisor positivo asegura a == q ∗ b + r ∧ 0 ≤ r < b;
I Necesitamos devolver dos valores }
I Usamos una tupla
I El tipo (Int, Int) son los pares ordenados de enteros I Alternativa 3
I prm y sgd devuelven sus componentes I Otro parámetro para el cociente
I La función no tiene resultado
problema cocienteResto(a, b : Int) = result : (Int,Int) { problema cocienteResto3(a, b, q, r : Int){
requiere b > 0; requiere b > 0;
asegura a == q ∗ b + r ∧ 0 ≤ r < b; modifica q, r ;
aux q = prm(result), r = sgd(result); asegura a == q ∗ b + r ∧ 0 ≤ r < b;
} }
73 74
77 78
79 80
Tipo Punto Tipo Cı́rculo
I un punto en el plano I sus componentes son de tipos distintos
I componentes: coordenadas (x e y ) tipo Cı́rculo {
I un observador para obtener cada una observador Centro(c : Cı́rculo) : Punto;
tipo Punto { observador Radio(c : Cı́rculo) : Float;
observador X (p : Punto) : Float; invariante Radio(c) > 0;
observador Y (p : Punto) : Float }
} I especificar el problema de construir un cı́rculo a partir de
I especificación una función que recibe dos números reales y I su centro y su radio
construye un punto con esas coordenadas: problema nuevoCı́rculo(c : Punto, r : Float) = res : Cı́rculo {
problema nuevoPunto(a, b : Float) = res : Punto { requiere r > 0;
asegura X (res) == a; asegura (Centro(res) == c ∧ Radio(res) == r );
asegura Y (res) == b; }
} I su centro y un punto sobre la circunferencia:
Cuando el resultado es de un tipo compuesto, usamos los problema nuevoCı́rculoPuntos(c, x : Punto) = res : Cı́rculo {
observadores para describir el resultado requiere dist(c, x) > 0;
asegura (Centro(res) == c ∧ Radio(res) == dist(c, x));
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
81 82
Tuplas ifThenElsehT i
I ya las mencionamos Función que elige entre dos elementos del mismo tipo, según una
condición (guarda)
I secuencias de tamaño fijo
I si la guarda es verdadera, elige el primero
I cada elemento puede pertenecer a un tipo distinto
I si no, elige el segundo
I ejemplos: pares, ternas
tipo ParhA, Bi{ Por ejemplo
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)
tipo TernahA, B, C i{ cuando los argumentos se deducen del contexto, se puede
observador prm3(t : TernahA, B, C i) : A; escribir directamente
observador sgd3(t : TernahA, B, C i) : B;
observador trc3(t : TernahA, B, C i) : C ; aux máx(a, b : Int) : Int = ifThenElse(a > b, a, b) o bien
} aux máx(a, b : Int) : Int = if a > b then a else b
I notación I expresión que dado x devuelve 1/x si x 6= 0 y 0 sino
I ParhA, Bi también se puede escribir (A, B) aux unoSobre(x : Float) : Float = if x 6= 0 then 1/x else 0
| {z }
I TernahA, B, C i también se puede escribir (A, B, C ) no se indefine cuando x = 0
91 92
Más aplicaciones de IfThenElse
93 94
95 96
Expresiones Transparencia referencial
97 98
99 100
Aplicación de funciones Ecuaciones
En programación funcional (como en matemática) las funciones I Dada una expresión, ¿cómo sabemos qué valor denota?
son elementos (valores)
I Usando las ecuaciones que la definen
I una función es un valor I por ejemplo: doble x = x + x
I la operación básica que podemos realizar con ese valor es la I se reemplaza cada sub expresión por otras según las
aplicación
ecuaciones
I aplicar la función a un elemento para obtener un resultado
I pero este proceso puede no terminar
I sintácticamente, la aplicación se escribe como una I aún con ecuaciones bien pensadas
yuxtaposición (la función seguida de su parámetro)
doble (1 + 1)
I f es una función y e un elemento de su conjunto de partida I reemplazo 1 + 1 por doble 1
I f e denota el elemento que se relaciona con e por medio de la doble (doble 1)
función f I reemplazo doble 1 por 1 + 1
I por ejemplo, doble 2 representa al número 4 I volvı́ a empezar...
101 102
105 106
121 122
123 124
Listas Ejemplos
I se usan mucho (el igual que en el lenguaje de especificación)
I son un tipo algebraico recursivo paramétrico
I en especificación las vimos definidas con observadores I calcular la longitud de una lista
I en Haskell, se definen con constructores longitud :: List a -> Int
I primero, vamos a definirlas como un tipo algebraico común longitud Nil = 0
data List a = Nil | Cons a (List a) longitud (Cons x xs) = 1 + (longitud xs)
Tipos abstractos
131 132
Criterios para tipos algebraicos Ejemplo de tipo algebraico: Complejo
1. Toda expresión del tipo representa un valor válido I Toda combinación de dos Float es un complejo
I Constructor I Dos complejos son iguales sii sus partes reales y sus partes
I Valores cualesquiera para sus parámetros imaginarias coinciden
2. Igualdad por construcción
data Complejo = C Float Float
I Dos valores son iguales solamente si se construyen igual
I Mismo constructor parteReal, ParteImag :: Complejo -> Float
I Mismos argumentos
ParteReal (C r i) = r
Condiciones ideales ParteImag (C r i) = i
I A veces se construyen tipos algebraicos sin respetarlas
hacerPolar :: Float -> Float -> Complejo
I Cı́rc (-1.2)
I Rect 2.3 4.5 6= Rect 4.5 2.3 hacerPolar rho theta =
C (rho * cos theta) (rho * sin theta)
133 134
data Racional = R Int Int Se puede usar, pero con mucho cuidado
I Al construir
numerador, denominador :: Racional -> Int
I Nunca segunda componente 0
numerador (R n d) = n I Al definir funciones
denominador (R n d) = d I Mismo resultado para toda representación del mismo racional
I Función numerador (devuelve la primera componente)
I Resultados distintos para R (-1) (-2) y R 2 4
¿Está bien definido? ¡No! I Son el mismo número, no estamos representando las fracciones
Tipos abstractos
I No todo par de enteros es un racional: R 1 0 I Ayuda del lenguaje
I Hay racionales iguales con distinto numerador y denominador: I La representación interna se usa en pocas funciones
R42yR21 I Mensaje de error si se intenta violar ese acuerdo
135 136
Mantenimiento Uso de tipos abstractos
137 138
141 142
143 144
Nuevas operaciones Creación de tipos abstractos
I Definamos una operación nueva: unión I Escribir un tipo abstracto que puedan usar otros
unión :: IntSet -> IntSet -> IntSet
programadores
unión p q | esVacı́o p = q I En Haskell se hace con módulos
unión p q | otherwise = uniónAux (elegir p) q I Archivos de texto que contienen parte de un programa
uniónAux (x, p’) q = agregar x (unión p’q) I Dos caracterı́sticas importantes en cualquier lenguaje
I Encapsulamiento
I Pudimos hacerlo sin conocer la representación de los I Agrupar estructura de datos con funciones básicas
conjuntos I Un programa no es una lista de definiciones y tipos, son
I Usamos las operaciones provistas ”cápsulas”
I Si cambia la representación I Cada una con un (tal vez más) tipo de datos y sus funciones
I Habrá que rescribir las ecuaciones para las operaciones del tipo especı́ficas
abstracto (vacı́o, esVacı́o, pertenece, agregar, I Ocultamiento
elegir) I Cuando se escribe un módulo se indica qué nombres exporta
I unión y cualquier otra definida en otros programas quedan Cuáles van a poder usarse desde afuera y cuáles no
intactas I Aplicación
I La recursión no es privativa del pattern matching Ocultar funciones auxiliares (como uniónAux)
I unión está definida en forma recursiva (a través de uniónAux) También para crear tipos de datos abstractos: Ocultar la
I Pero no usa recursión estructural ¡ni siquiera conocemos la representación interna
estructura! 145 146
149 150
151 152
Implementación de conjuntos
module ConjuntoInt (IntSet, vacı́o, esVacı́o, pertenece,
agregar, elegir) where
import List (insert)
newtype IntSet = Set [Int] Programación funcional
vacı́o :: IntSet Clase 4
vacı́o = Set []
esVacı́o :: IntSet -> Bool
Terminación
esVacı́o (Set xs) = null xs
pertenece :: Int -> IntSet -> Bool
pertenece x (Set xs) = x ‘elem‘ xs
agregar :: Int -> IntSet -> IntSet
agregar x (Set xs) | elem x xs = Set xs
agregar x (Set xs) | otherwise = Set (insert x xs)
elegir :: IntSet -> (Int, IntSet)
elegir (Set (x:xs)) = (x, Set xs)
153 154
159 160
Reglas de terminación R1 - las funciones se evalúan dentro de su precondición
problema prLL(l : [[Int]]) = res : [R]{ problema prom(l : [Int]) = res : R{
asegura res == requiere |l| > 0;
[if |y | > 0 then prom(y ) else 0 | y ← l] asegura res == prom(l); } P
} aux prom(l : [Int]) : Float = l/|l|
Daremos 5 reglas sobre ecuaciones canónicas de una cierta función
f: R1, R2, R3, R4 y R5. prLL :: [[Int]] -> [Float]
prLL [] = []
I si se pueden probar las 5 reglas, entonces f termina prLL ([]:xs) = 0 : (prLL xs)
I las reglas solo predican sobre las ecuaciones canónicas; nos prLL (x:xs) = (prom x) : (prLL xs)
olvidamos de las ecuaciones originales prLL l / (|l| == 0 , true , True) = []
prLL l / (|l| > 0 ∧ l[0] == [ ], xs == l[1..|l|) , True) = 0 : (prLL xs)
I si no podemos probar alguna de las 5 reglas no quiere decir
prLL l / (|l| > 0 , l[0] == x ∧ xs == l[1..|l|), True) = (prom x):(prLL xs)
que la función no termine; las 5 reglas son condiciones
suficientes pero no necesarias R1 sea i entre 1 y n, y sea g y (g puede ser f) una expresión que
aparece en Gi o en Ei . Si
I x hace verdadero P ,
f
I valen ¬B , . . . , ¬B
1 i−1 ,
I si g y aparece en E vale además B ,
i i
entonces y debe hacer verdadero Pg .
161
En el ejemplo, prom siempre se invoca con argumentos que satisfacen Pprom . 162
f x/(B1 , C1 , G1 ) = E1
f x/(B2 , C2 , G2 ) = E2 Condiciones básicas (y fáciles de demostrar)
..
. R1 todas las funciones se evalúan dentro de su precondición
f x/(Bn , Cn , Gn ) = En R2 hay suficientes ecuaciones (no faltan casos)
Proponemos una función variante Fv (x) tal que: Debemos proponer una función variante Fv (x) y una cota tal que
R3 si x están definidas y hacen verdadero Pf , entonces Fv (x) no (esto es más difı́cil de demostrar):
se indefine. R3 Fv (x) no se indefine (si vale Pf )
R4 sea i entre 1 y n, con Ei un caso recursivo, y x tales que se
R4 Fv (x) es decreciente
cumple (Pf ∧ ¬B1 ∧ . . . ∧ ¬Bi−1 ∧ Bi ). Entonces, para cada
aparición de f y en Ei , vale que Fv (x) > Fv (y ). R5 si Fv (x) pasa la cota entonces f entra a un caso base (y por
R5 existe una constante entera k, que denominaremos cota de lo tanto termina)
Fv , con la siguiente propiedad: toda vez que x hace verdadero
Pf y Fv (x) ≤ k, debe existir i entre 1 y n tal que
I Ei es un caso base y
I vale (¬B1 ∧ . . . ∧ ¬Bi−1 ∧ Bi ).
165 166
169 170
ultimo - R4 ultimo - R5
mezclarOrdenado - R1 mezclarOrdenado - R2
mezOrd l1 l2 / (|l1| == 0, true, True) = l2
mezOrd l1 l2 / (|l2| == 0, true, True) = l1
mezOrd l1 l2 / (|l1| == 0, true, True) = l2
mezOrd l1 l2 / (|l1| > 0 ∧ |l2| > 0 ∧ x ≤ y ,
mezOrd l1 l2 / (|l2| == 0, true, True) = l1
x == l1[0] ∧ xs == cola(l1) ∧ y == l2[0] ∧ ys == cola(l2),
mezOrd l1 l2 / (|l1| > 0 ∧ |l2| > 0 ∧ x ≤ y ,
x<=y) = x : (mezOrd xs (y:ys))
x == l1[0] ∧ xs == cola(l1) ∧ y == l2[0] ∧ ys == cola(l2),
mezOrd l1 l2 / (|l1| > 0 ∧ |l2| > 0,
x<=y) = x : (mezOrd xs (y:ys))
x == l1[0] ∧ xs == cola(l1) ∧ y == l2[0] ∧ ys == cola(l2),
mezOrd l1 l2 / (|l1| > 0 ∧ |l2| > 0,
True) = y : (mezOrd (x:xs) ys)
x == l1[0] ∧ xs == cola(l1) ∧ y == l2[0] ∧ ys == cola(l2),
R1 sea i entre 1 y n, y sea g y (g puede ser f) una expresión que True) = y : (mezOrd (x:xs) ys)
aparece en Gi o en Ei . Si
R2 si x están definidas y hacen verdadero Pf , entonces existe un i entre
I x hace verdadero Pf , 1 y n tal que x hacen verdadero Bi .
I valen ¬B1 , . . . , ¬Bi−1 ,
I si g y aparece en Ei vale además Bi , Observar que siempre ocurre que
entonces y debe hacer verdadero Pg . |l1| = 0 ∨ |l2| = 0 ∨ (|l1| > 0 ∧ |l2| > 0)
Observar que mezord siempre se llama con parámetros que verifican la
precondición (las dos listas que recibe están ordenadas).
175 176
mezclarOrdenado - Función variante y cota mezclarOrdenado - R3
mezOrd l1 l2 / (|l1| == 0, true, True) = l2
mezOrd l1 l2 / (|l2| == 0, true, True) = l1
mezOrd l1 l2 / (|l1| > 0 ∧ |l2| > 0 ∧ x ≤ y , mezOrd l1 l2 / (|l1| == 0, true, True) = l2
x == l1[0] ∧ xs == cola(l1) ∧ y == l2[0] ∧ ys == cola(l2), mezOrd l1 l2 / (|l2| == 0, true, True) = l1
x<=y) = x : (mezOrd xs (y:ys)) mezOrd l1 l2 / (|l1| > 0 ∧ |l2| > 0 ∧ x ≤ y ,
mezOrd l1 l2 / (|l1| > 0 ∧ |l2| > 0, x == l1[0] ∧ xs == cola(l1) ∧ y == l2[0] ∧ ys == cola(l2),
x == l1[0] ∧ xs == cola(l1) ∧ y == l2[0] ∧ ys == cola(l2), x<=y) = x : (mezOrd xs (y:ys))
True) = y : (mezOrd (x:xs) ys) mezOrd l1 l2 / (|l1| > 0 ∧ |l2| > 0,
x == l1[0] ∧ xs == cola(l1) ∧ y == l2[0] ∧ ys == cola(l2),
Posibilidades: True) = y : (mezOrd (x:xs) ys)
mezclarOrdenado - R4 mezclarOrdenado - R5
mezOrd l1 l2 / (|l1| == 0, true, True) = l2 mezOrd l1 l2 / (|l1| == 0, true, True) = l2
mezOrd l1 l2 / (|l2| == 0, true, True) = l1 mezOrd l1 l2 / (|l2| == 0, true, True) = l1
mezOrd l1 l2 / (|l1| > 0 ∧ |l2| > 0 ∧ x ≤ y , mezOrd l1 l2 / (|l1| > 0 ∧ |l2| > 0 ∧ x ≤ y ,
x == l1[0] ∧ xs == cola(l1) ∧ y == l2[0] ∧ ys == cola(l2), x == l1[0] ∧ xs == cola(l1) ∧ y == l2[0] ∧ ys == cola(l2),
x<=y) = x : (mezOrd xs (y:ys)) x<=y) = x : (mezOrd xs (y:ys))
mezOrd l1 l2 / (|l1| > 0 ∧ |l2| > 0, mezOrd l1 l2 / (|l1| > 0 ∧ |l2| > 0,
x == l1[0] ∧ xs == cola(l1) ∧ y == l2[0] ∧ ys == cola(l2), x == l1[0] ∧ xs == cola(l1) ∧ y == l2[0] ∧ ys == cola(l2),
True) = y : (mezOrd (x:xs) ys) True) = y : (mezOrd (x:xs) ys)
R4 sea i entre 1 y n, con Ei un caso recursivo, y x tales que se R5 toda vez que x hace verdadero Pf y Fv (x) ≤ k, debe existir i
cumple (Pf ∧ ¬B1 ∧ . . . ∧ ¬Bi−1 ∧ Bi ). Entonces, para cada entre 1 y n tal que Ei es un caso base, y vale
aparición de f y en Ei , vale que Fv (x) > Fv (y ). (¬B1 ∧ . . . ∧ ¬Bi−1 ∧ Bi ).
181 182
Reducción Ejemplo
Modelo de cómputo:
I cómo se calcula el valor de una expresión I expresión
I puede afectar la semántica suma (restar 2 (amigos Juan)) 4
I distintos modelos pueden dar distintos resultados I ecuación
Reducción: restar x y = x - y
I reducción
I mecanismo de evaluación en Haskell
I reemplazar una subexpresión por otra 1. busco un redex y asignación
suma (restar 2 (amigos Juan)) 4
I reemplazada: | {z }
I instancia del lado izquierdo de una ecuación orientada redex
I se llama redex (reducible expression) o radical I asignación:
I reemplazante: x←2
I lado derecho, instanciado de manera acorde y ← (amigos Juan)
I el resto de la expresión queda igual 2. reemplazo el redex con esa asignación
I instanciación
suma (restar 2 (amigos Juan)) 4 suma (2 - (amigos Juan)) 4
I asignación de expresiones a variables de un pattern
187 188
Formas normales Mecanismo de reducción
189 190
Normalización Bottom
191 192
Indefinición Orden de evaluación
I le pasamos un valor definido a una función
I parciales: a veces devuelven ⊥ I forma de elegir el próximo redex
I totales: nunca
I le pasamos ⊥ a una función
I recordar confluencia
I ¿devuelve ⊥? I si por dos órdenes llegamos a valores definidos, es el mismo
I no siempre valor
I depende de sus ecuaciones y del orden de reducción I pero puede ser que un orden llegue a ⊥ y otro no
I estrictas: f ⊥ ⊥
I no estrictas: f ⊥ valor infinito :: Integer -> Integer const :: a -> b -> a
infinito = infinito + 1 const x y = x
Totales vs. parciales
Estrictas vs. no estrictas
I total const 2 infinito
I ecuaciones
suc :: Integer -> Integer const :: a -> b -> a
suc x = x + 1 2 const 2 (infinito+1)
const x y = x
I parciales I ¿cuánto vale?
recip :: Float -> Float 2 const 2 ((infinito+1)+1)
I const 2 infinito
recip x | x /= 0 = 1/x
2 ..
I las dos son estrictas Depende del diseño del lenguaje. El .
I si les paso ⊥ devuelven ⊥ secreto está en el orden de evaluación
193 194
195 196
Ejemplos de evaluación normal Propiedades de los órdenes
f x = 0
f (1/0) 0
head (tail (inc [1,2,3,4])) head (tail (2:inc [2,3,4])) head (inc [2,3,4])
head (3:inc [3,4]) 3
197 198
fib 1 = 1
fib 2 = 1
fib n | n > 2 = fib (n-1) + fib (n-2)
I perezosa, haragana, fiaca
¿Cuántas reducciones necesito?
I algoritmo usado en Haskell
I fib 20 → 200.000 I orden normal, pero...
I const 3 (fib 20) → ? I aprovecha la transparencia referencial
I aplicativo: 200.001 I si una expresión vuelve a aparecer, se acuerda el valor anterior
I normal: 1 I quin (fib 20) → 200.005
I quin x = x + x + x + x + x
quin (fib 20) → ?
I aplicativo: 200.005
I normal: (fib 20) + (fib 20) + (fib 20) + (fib 20) + (fib 20) →
1.000.000
199 200
Estructuras infinitas Alto orden
I filtrar los elementos mayores a 2 de una lista:
filtroMayoresADos :: [Int] -> [Int]
I es una ventaja de la evaluación lazy filtroMayoresADos [] = []
filtroMayoresADos (x:xs) | x>2 = x:filtroMayoresADos xs
I ejemplos
| otherwise = filtroMayoresADos xs
I naturales, pares, impares :: [Integer] I filtrar los elementos pares de una lista:
naturales = secuencia 0 1 filtroPares :: [Int] -> [Int]
impares = secuencia 1 2 filtroPares [] = []
pares = secuencia 0 2 filtroPares (x:xs) | (x ‘mod‘ 2==0) = x:filtroPares xs
| otherwise = filtroPares xs
secuencia :: Integer -> Integer -> [Integer] I más general:
secuencia n p = n:secuencia (n+p) p filter: (Int -> Bool) -> [Int] -> [Int]
filter f [] = []
I evaluación de estructuras infinitas filter f (x:xs) | f x = x:filter f xs
| otherwise = filter f xs
I head naturales head (secuencia 0 1)
I filtroMayoresADos xs = filter mayorADos xs
head (0:secuencia 1 1) 0
where mayorADos x = x>2
I take 10 impares ··· [1,3,5,7,9,11,13,15,17,19] I filtroPares xs = filter par xs
207 208
Variables en imperativo La asignación
I operación fundamental para modificar el valor de una variable
I sintaxis
variable = expresión;
I nombre asociado a un espacio de memoria I operación asimétrica
I puede cambiar de valor varias veces en la ejecución I lado izquierdo: debe ir una variable u otra expresión que
I en C++ se declaran dando su tipo y su nombre represente una posición de memoria
I lado derecho: puede ser una expresión del mismo tipo que la
I int x; −→ x es una variable de tipo int variable
I char c; −→ c es una variable de tipo char I constante
I programación imperativa I variable
I conjunto de variables
I función aplicada a argumentos
I instrucciones que van cambiando sus valores
I efecto de la asignación
I los valores finales, deberı́an resolver el problema 1. se evalúa el valor de la expresión de la derecha
2. ese valor se copia en el espacio de memoria de la variable
3. el resto de la memoria no cambia
I ejemplos: x = 0; y = x; x = x+x;
x = suma2(z+1,3); x = x*x + 2*y + z;
I no son asignaciones:
209
3 = x; doble (x) = y; 8*x = 8; 210
211 212
Afirmaciones en imperativo Cláusulas vale y estado
I al demostrar una propiedad, agregamos afirmaciones sobre el
estado entre instrucciones I para referirnos al valor que tenı́a en un estado
I se amplı́a el lenguaje de especificación con la sentencia vale anterior
vale nombre: P; I usamos estado para dar un nombre al estado
I ejemplo de código I nos referimos al estado anterior con
I el nombre (nombre) es opcional con afirmaciones
I P es un predicado variable@nombreEstado
I expresión de tipo Bool del lenguaje de especificación x = 0;
I se coloca entre dos instrucciones //vale x == 0; I semántica de la asignación:
I significa que P vale en ese punto del programa en cualquier x = x + 3; //estado a
ejecución //vale x == 3; v = e;
I el compilador no entiende las sentencias del lenguaje de x = 2 * x; //vale v == e@a
especificación //vale x == 6;
I para que no dé error, las ponemos en comentarios I después de ejecutar la asignación, la variable v
I como en Haskell, pero con otra sintaxis tiene el valor que tenı́a antes la expresión e
I hay dos maneras:
I // comenta una sola lı́nea
I en cada ejecución el estado puede ser distinto
I /*...*/ comenta varias lı́neas
213 214
Invocación de funciones - pasaje por valor Invocación de funciones - pasaje por referencia
problema swap(x, y : Int) {
problema doble(x : Int) = res : Int { modifica x, y ;
asegura res == 2 ∗ x; } asegura x == pre(y ) ∧ y == pre(x); }
//estado a
Programación imperativa v = e;
Clase 2 //vale v == e@a ∧ z1 = z1 @a ∧ · · · ∧ zk = zk @a
I donde z1 , . . . , zk son todas las variables del programa en
cuestión distintas a v que aparezcan en una cláusula modifica
Teorema del Invariante o local
I las otras variables se supone que no cambian ası́ que no hace
falta decir nada)
I si la expresión e es la invocación a una función que recibe
parámetros por referencia, puede haber más cambios, pero al
menos
//vale v == e@a
Condicionales Condicionales
I supongamos este código:
if (B) uno else dos;
I B tiene que ser una expresión booleana (verdadera o falsa) sin
Equivalentemente:
efectos secundarios (no tiene que modificar el estado) I si sabemos que
I se llama guarda
I uno y dos son instrucciones //vale P ∧ B //vale P ∧ ¬B
I en particular, pueden ser bloques (entre llaves)
uno; dos;
I si sabemos que //vale Q1 //vale Q2
//vale P ∧ B //vale P ∧ ¬B I entonces podemos afirmar
uno; dos;
//vale Q //vale Q //vale P
I entonces podemos afirmar if (B) uno else dos;
//vale Q1 ∨ Q2
//vale P
if (B) uno else dos;
//vale Q
I decimos que P es la precondición del condicional y Q es su
poscondición 227 228
Ejemplo de demostración de condicional Ejemplo de demostración de condicional
problema max(x, y : Int) = result : Int{
I demuestro cada rama fuera del condicional
asegura Q : (x > y ∧ result == x) ∨ (x ≤ y ∧ result == y ) I rama true:
//vale m == 0 ∧ x > y ;
}
m = x;
//vale x > y ∧ m == x;
int max(int x, int y) { //implica (x > y ∧ m == x) ∨ (x ≤ y ∧ m == y );
int m = 0; (justificación: p → (p ∨ q) es tautologı́a)
//vale Pif : m == 0; I rama false:
if (x > y) //vale m == 0 ∧ x ≤ y ;
m = x; m = y;
else //vale x ≤ y ∧ m == y ;
m = y; //implica (x > y ∧ m == x) ∨ (x ≤ y ∧ m == y );
(justificación: p → (p ∨ q) es tautologı́a)
//vale Qif : (x > y ∧ m == x) ∨ (x ≤ y ∧ m == y );
return m;
//vale Q;
I pudimos llegar a Qif por las dos ramas,
}
I entonces demostré que el condicional es correcto para la
precondición Pif y poscondición Qif
229 230
Arreglos
247 248
Arreglos y listas Arreglos en C++
I los arreglos en C++ son referencias
I pueden modificarse cuando son pasados como argumentos
I no hay forma de averiguar su tamaño una vez que fueron creados
I ambos representan secuencias de elementos de un tipo I el programa tiene que encargarse de almacenarlo de alguna forma
I los arreglos tienen longitud fija; las listas, no I en la declaración de un parámetro no se indica su tamaño
I los elementos de un arreglo pueden accederse en forma problema ceroPorUno(a : [Int], tam : Int){
independiente requiere tam == |a|;
I los de la lista se acceden secuencialmente, empezando por la modifica a;
cabeza asegura a == [if i == 0 then 1 else i | i ← pre(a)[0..tam)]; }
I para acceder al i-ésimo elemento de una lista, hay que obtener void ceroPorUno(int a[], int tam) {
i veces la cola y luego la cabeza int j = 0;
I para acceder al i-ésimo elemento de un arreglo, simplemente se while (j < tam) {
usa el ı́ndice // invariante 0 ≤ j ≤ tam ∧ a[j..tam) == pre(a)[j..tam)∧
// a[0..j) == [if i == 0 then 1 else i | i ← pre(a)[0..j)];
// variante tam − j;
if (a[j] == 0) a[j] = 1;
j++;
}
249 250
}
i = i + 1; 1 2 5
z }| { z }| { z }| {
0≤i ≤n ∧ x∈
/ a[0..i) ∧ (i ≥ n ∨ x == a[i])
// estado F
// vale i == i@E + 1 ⇓
v @F == (n − i)@F Demostración:
== n − i@F I supongamos i ≥ n (i.e. 3 es falso). Por 1, i == n. Por 2,
== n − (i@E + 1) tenemos x ∈
/ a[0..n). Luego 4 también es falso.
== n − i@E − 1 I supongamos i < n (i.e. 3 es verdadero). Por 5, x == a[i]. De
< n − i@E 1 concluimos que 4 es verdadero.
== v @E
255 256
3 y 4 son triviales Complejidad de un algoritmo
257 258
259 260
Mejorando la búsqueda Búsqueda binaria - especificación del problema
Supongamos que en lugar de un diccionario tenemos un arreglo
ordenado y en lugar de buscar palabras buscamos números enteros.
Supongamos que tenemos un diccionario y queremos buscar una
palabra x. Podemos hacer una búsqueda más eficiente
I revisamos solo algunas posiciones del arreglo: en cada paso
¿Cómo podemos mejorar la búsqueda?
descartamos la mitad del espacio de búsqueda
I abrimos el diccionario a la mitad I menos comparaciones
I si x está en esa página, terminamos Este tipo de algoritmo se llama búsqueda binaria.
I si x es menor (según el orden de diccionario) que las palabras Tenemos un nuevo problema en donde
de la página actual, x no puede estar en la parte derecha
I mantenemos la poscondición
I si x es mayor (según el orden de diccionario) que las palabras I reforzamos la precondición
de la página actual, x no puede estar en la parte izquierda
I seguimos buscando solo en la parte izquierda o derecha (según problema buscarBin (a : [Int], x : Int, n : Int) = res : Bool{
sea el caso) requiere |a| == n > 0;
requiere (∀j ∈ [0..n − 1)) a[j] ≤ a[j + 1];
asegura res == (x ∈ a);
}
261 262
Conclusiones
271 272
Ordenamiento de un arreglo La especificación
273 274
Complejidad de Upsort
I el ciclo de Upsort itera n veces
I empieza con actual == n − 1
I termina con actual == 0
I en cada iteración decrementa actual en uno
I una iteración hace
I una búsqueda de maxPos
I un swap
I un decremento
I de estos pasos, el único que no es O(1), constante, es el
primero
I ¿cuántos pasos hace maxPos en cada iteración?
I siempre O(hasta − desde + 1) = O(actual + 1)
I en la primera hace n (busca el máximo del segmento a ordenar)
I en la segunda hace n − 1 (busca el segundo mayor)
I en la tercera hace n − 2
I y ası́ (podrı́amos verlo por inducción) hasta 2
I el total de pasos es
O(n + (n − 1) + . . . + 2) = O(n ∗ (n + 1)/2 − 1) = O(n2 )
I los mejores algoritmos de ordenamiento son O(n log n)
295