Algoritmos y Estructuras de Datos I El tipo compuesto Racional

Anuncio
El tipo compuesto Racional
tipo Racional {
observador numerR(r : Racional): Int;
observador denomR(r: Racional): Int;
invariante denomR(r) 6= 0;
invariante mcd(numerR(r), denomR(r)) == 1;
Algoritmos y Estructuras de Datos I
Primer cuatrimestre de 2015
problema crear(n, d: Int) = rac: Racional {
requiere d 6= 0;
asegura numerR(rac) * d == denomR(rac) * n; }
Departamento de Computación - FCEyN - UBA
Tipos abstractos de datos - clase 9
problema denom(r: Racional) = d: int {
asegura d == denomR(r) ; }
Invariante de representación y función de abstracción
problema numer(r: Racional) = n: int {
asegura d == numerR(r) ; }
}
1
TADs Racionales en C++ (Intento 1)
2
¿Es correcta esta implementación de Racional?
class Racional {
public:
Racional (int,int);
int numer();
int denom();
private:
int num, den;
};
class Racional {
public:
Racional (int,int);
int numer ();
int denom ();
private:
int num, den;
};
Racional::Racional (int n, int d) {
num = n;
den = d;
};
Racional::Racional (int n, int d) {
num = n;
den = d;
};
int Racional::numer(){return num;};
int Racional::numer (){return num;};
int Racional::denom(){return den;};
I
En el constructor this denota la instancia que se crea.
int Racional::denom (){return den;};
I
El resto de las funciones tienen un parámetro implı́cito de tipo
Racional que se llama this. En general asumimos que es el primero
del problema.
¿Qué sucede con Racional (1,0)? y con Racional (2,20).numer()?
3
4
Correctitud de implementación de TADs
Invariante de representación para Racional
Utilizamos dos predicados
I
Invariante de representación (InvRep):
I
Restringe los posibles valores que pueden tomar las variables
que definen la representación del TAD.
I
I
I
class Racional {
public:
Racional (int,int);
int numer ();
int denom ();
private:
int num, den;
// invRep(x): x.den 6= 0 ∧ mcd(x.num, x.den) == 1
};
Por ejemplo, el denominador de un racional no puede ser 0
Debe ser preservado por todas las operaciones que provee el
TAD (es decir, debe valer al final de la ejecución de cada
operación)
Predicado de abstracción (abs):
I
I
I
Relaciona la especificación del TAD con su implementación.
Mapea instancias de una clase en valores del tipo. Conecta
representación interna con observadores.
Asume que InvRep es verdadero (también que el invariante del
tipo compuesto es verdadero, pero esto es siempre cierto).
I
Notar que el InvRep se especifica usando los campos de la clase
(variables de instancia): num y den.
I
Asumimos que el tipo de cada campo es una implementación
correcta de un tipo del lenguaje de especificación (en este caso, int
implementa Int).
5
Corrección de implementación de TADS
6
Preservación de invRep en constructor de Racional
Tipo Racional{
observador numerR(r: Racional): Int;
observador denomR(r: Racional): Int;
invariante denomR(r) 6= 0;
invariante mcd(numerR(r ), denomR(r )) == 1;
problema crearR(n, d: Int) = rac: Racional {
requiere d 6= 0;
asegura numerR(rac) * d == denomR(rac) * n; }
}
Para demostrar que una clase C es una implementación correcta de un
tipo compuesto tenemos que mostrar:
1. Todas las operaciones públicas del TAD preservan invRep.
Asumimos que ya está definida la operación gcd para int.
2. Los métodos públicos cumplen con su contrato.
Racional::Racional(int n, int d){
// vale d 6= 0
(por el requiere)
int mcd = gcd(n,d);
num = n/mcd;
den = d/mcd;
// vale num == n/mcd(n, d) ∧ den == d/mcd(n, d)
// implica den 6= 0
porque d 6= 0
// implica mcd(num, den) == mcd(n, d)/mcd(n, d)
(porque mcd(a/c, b/c) == mcd(a, b)/c cuando c es un divisor común)
// implica mcd(num, den) == mcd(n, d)/mcd(n, d) == 1
// implica invRep(this)
}
7
8
Preservación de invRep en constructor de Racional
Preservación de invRep en funciones sin modifica
Tipo Racional{
observador numerR(r: Racional): Int;
observador denomR(r: Racional): Int;
invariante denomR(r) 6= 0;
invariante mcd(numerR(r ), denomR(r )) == 1;
Tipo Racional{
observador numerR(r: Racional): Int;
observador denomR(r: Racional): Int;
invariante denomR(r ) 6= 0;
invariante mcd(numerR(r ), denomR(r )) == 1;
problema copiar(a: Racional) = rac: Racional {
asegura numerR(rac) == numerR(a);
asegura denomR(rac) == denomR(a); }
}
problema denom(r: Racional) = d: int {
asegura d == denomR(r) ; }
Racional::Racional(Racional a){
// vale invRep(a)
Asumimos que a es correcto
NO vale invRep(this) porque estamos construyendo this
num = a.num;
den = a.den;
// vale num == a.num ∧ den == a.den
// implica den 6= 0
porque a.den 6= 0 por invRep(a)
// implica mcd(num, den) == 1(porque mcd(a.num, a.den) == 1 por invRep(a)
// implica invRep(this)
// notar que vale invRep(a) porque a no se modifica
}
int Racional::denom(){
// vale invRep(pre(this))
return den;
// implica invRep(this)
}
this existe y asumimos que es correcto
(porque this == pre(this))
Este caso (al igual que numer ) es trivial por el código de denom. En general vamos a
tener que probar que invRep vale al final (como en los casos anteriores).
10
9
Preservación de invRep en funciones que modifica this
Preservación de invRep en función que modifica parámetro
Tipo Racional{
observador numerR(r: Racional): Int;
observador denomR(r: Racional): Int;
invariante denomR(r ) 6= 0;
invariante mcd(numerR(r ), denomR(r )) == 1;
problema cuadNum(a: Racional) {
modifica a;
asegura numerR(a) == numerR(pre(a))*numerR(pre(a));
asegura denomR(a) == denomR(pre(a)); }
}
void Racional::cuadNum(){
// vale invRep(pre(this))
Asumimos que this es correcto
num = num*num;
// vale num == pre(this).num ∗ pre(this).num ∧ den == pre(this).den
// implica den 6= 0
(porque pre(this).den 6= 0 por invRep(pre(this)))
// implica mcd(num, den) == mcd(pre(this).num ∗ pre(this).num, pre(this).den)
// implica mcd(num, den) == 1
(usamos la propiedad mcd(a ∗ b, c) divide a mcd(a, c) ∗ mcd(b, c))
(en particular: mcd(a ∗ a, c) divide a mcd(a, c) ∗ mcd(a, c))
(y dado que mcd(pre(this).num, pre(this).den) == 1 por invRep(pre(this)))
(mcd(pre(this).num ∗ pre(this).num, pre(this).den) == 1 para dividir a 1 ∗ 1)
// implica invRep(this)
}
11
Tipo Racional{
observador numerR(r: Racional): Int;
observador denomR(r: Racional): Int;
invariante denomR(r ) 6= 0;
invariante mcd(numerR(r ), denomR(r )) == 1;
problema swapR(a: Racional, b: Racional) {
modifica a,b;
asegura numerR(a) == numerR(pre(b)) ∧ numerR(b) == numerR(pre(a));
asegura denomR(a) == denomR(pre(b)) ∧ denomR(b) == denomR(pre(a)) }
}
void Racional::swapR(Racional& b){
// vale invRep(pre(this)) ∧ invRep(pre(b))Asumimos que this y b son correctos
swap(num, b.num);
swap(den, b.den);
// vale num == pre(b).num ∧ den == pre(b).den∧
b.num == pre(this).num ∧ b.den == pre(this).den
// implica den 6= 0
(porque pre(b).den 6= 0 por invRep(pre(b)))
// implica mcd(num, den) == mcd(pre(b).num, pre(b).den) == 1
(porque vale invRep(pre(b)))
// implica invRep(this)
// implica b.den 6= 0
(porque pre(this).den 6= 0 por invRep(pre(this)))
// implica mcd(b.num, b.den) == mcd(pre(this).num, pre(this).den) == 1
(porque vale invRep(pre(this)))
// implica invRep(b)
es necesario probarlo porque b se modifica
}
12
Corrección de implementación de TADS
Satisfacción de contratos
Tipo Racional{
observador numerR(r: Racional): Int;
observador denomR(r: Racional): Int;
invariante denomR(r ) 6= 0
invariante mcd(numerR(r ), denomR(r )) == 1
problema crearR(n, d: Int) = rac: Racional {
requiere d 6= 0;
asegura numerR(rac) * d == denomR(rac) * n; }
}
Para demostrar que una clase C es una implementación correcta de un
tipo compuesto tenemos que mostrar:
1. Todas las operaciones públicas del TAD preservan invRep.
Racional::Racional(int n, int d){
// vale d 6= 0
(por el requiere)
int mcd = gcd(n,d);
num = n/mcd;
den = d/mcd;
// vale num == n/mcd(n, d) ∧ den == d/mcd(n, d)
// implica invRep(this) : den 6= 0 ∧ mcd(num, den) == 1
// implica num/den == n/d
(porque den 6= 0, d 6= 0, mcd(n, d) 6= 0)
// implica num ∗ d == den ∗ n
}
2. Los métodos públicos cumplen con su contrato.
Notar que num ∗ d == den ∗ n NO es el la poscondicion de crearR
Necesitamos relacionar la implementación con los observadores del tipo compuesto
13
Predicado de abstracción
I
14
Predicado de abstracción de Racional
Predicado que relaciona una instancia concreta de una clase
(implementación) con un elemento de su tipo (especificación).
class Racional {
public:
Racional (int,int);
int numer ();
int denom ();
private:
int num, den;
// invRep(x): x.den 6= 0 ∧ mcd(x.num, x.den) == 1
// abs(x,rac): numerR(rac) == x.num ∧ denomR(rac) == x.den
};
abs(imp : clase, esp : TipoCompuesto)
I
Se asume que abs(imp : clase, esp : TipoCompuesto) vale solo para
las instancias de la implementación que satisfacen el invariante de
representación.
I
Formalmente
invRep(imp) ⇒ abs(imp : clase, esp : TipoCompuesto)
invRep y abs están estrechamente ligados (vamos a volver)
Importante: Sólo se puede utilizar el predicado abs(imp, a) en estados
donde vale invRep(imp)
Convención: A menudo escribiremos abs(a, ae ) para denotar con ae el
valor del tipo (especificación)
15
16
Ahora sı́: Satisfacción de contratos
Satisfación de contrato en constructor con párametro
Tipo Racional{
observador numerR(r: Racional): Int;
observador denomR(r: Racional): Int;
invariante denomR(r ) 6= 0;
invariante mcd(numerR(r ), denomR(r )) == 1;
Tipo Racional{
observador numerR(r: Racional): Int;
observador denomR(r: Racional): Int;
invariante denomR(r) 6= 0;
invariante mcd(numerR(r ), denomR(r )) == 1;
problema crearR(n, d: Int) = rac: Racional {
requiere d 6= 0;
asegura numerR(rac) * d == denomR(rac) * n; }
}
problema copiar(a: Racional) = rac: Racional {
asegura numerR(rac) == numerR(a);
asegura denomR(rac) == denomR(a); }
}
// invRep(x): x.den 6= 0 ∧ mcd(x.num, x.den) == 1
// abs(x,rac): numerR(rac) == x.num ∧ denomR(rac) == x.den
// invRep(x): x.den 6= 0 ∧ mcd(x.num, x.den) == 1
// abs(x,rac): numerR(rac) == x.num ∧ denomR(rac) == x.den
Racional::Racional(int n, int d){
// vale d 6= 0
(por el requiere)
int mcd = gcd(n,d);
num = n/mcd;
den = d/mcd;
// implica invRep(this) : den 6= 0 ∧ mcd(num, den) == 1
// implica abs(this, rac) (porque vale invRep(this) y this es el resultado)
// implica num ∗ d == den ∗ n
// implica numerR(rac) ∗ d == denomR(rac) ∗ n
(por abs(this,rac))
}
17
Satisfacción de contrato en función sin modifica
Racional::Racional(Racional a){
// vale invRep(a) ∧ abs(a, ae )
Asumimos que a es correcto
usamos ae para hablar de a de la especificación
// implica numerR(ae ) == a.num ∧ denomR(ae ) == a.den
num = a.num;
den = a.den;
// vale num == a.num ∧ den == a.den
// implica num == numerR(ae ) ∧ den == denomR(ae )
(por abs(a, ae )))
// implica invRep(this)
(lo probamos antes)
// implica abs(this, rac) (porque vale invRep(this) y this es el resultado)
// implica numerR(rac) == numerR(ae ) ∧ denomR(rac) == denomR(ae )
(porque vale abs(this, rac))
}
18
Satisfacción de contratos con modifica this y parámetro
Tipo Racional{
observador numerR(r: Racional): Int;
observador denomR(r: Racional): Int;
invariante denomR(r ) 6= 0;
invariante mcd(numerR(r ), denomR(r )) == 1;
problema swapR(a: Racional, b: Racional) {
modifica a,b;
asegura numerR(a) == numerR(pre(b)) ∧ numerR(b) == numerR(pre(a));
asegura denomR(a) == denomR(pre(b)) ∧ denomR(b) == denomR(pre(a)) }
}
Tipo Racional{
observador numerR(r: Racional): Int;
observador denomR(r: Racional): Int;
invariante denomR(r ) 6= 0;
invariante mcd(numerR(r ), denomR(r )) == 1;
problema denom(r: Racional) = d: int {
asegura d == denomR(r) ; }
// invRep(x): x.den 6= 0 ∧ mcd(x.num, x.den) == 1
// abs(x,rac): numerR(rac) == x.num ∧ denomR(rac) == x.den
int Racional::denom (){
// vale invRep(pre(this)) ∧ abs(pre(this), r )
return den;
// vale d == den
(porque den es el resultado)
// implica invRep(this)
(porque this == pre(this))
// implica abs(this, r )
(porque this == pre(this) y abs(pre(this), r ))
// implica d == denomR(r )
(porque vale abs(this, r ))
}
19
// invRep(x): x.den > 0 ∧ mcd(x.num, x.den) == 1
// abs(x,rac): numerR(rac) == x.num ∧ denomR(rac) == x.den
void Racional::swapR(Racional& b){
// vale invRep(pre(this)) ∧ invRep(pre(b)) ∧
abs(pre(this), pre(a)) ∧ abs(pre(b), pre(be ))
swap(num, b.num);
swap(den, b.den);
// vale num == pre(b).num ∧ den == pre(b).den ∧
b.num == pre(this).num ∧ b.den == pre(this).den
// implica invRep(this) ∧ invRep(b)
(salteando algunos pasos)
// implica abs(this, a) ∧ abs(b, be )
(porque valen los invariantes)
// implica numerR(a) == numer (pre(be )) ∧ denomR(a) == denomR(pre(be ))
∧ numerR(be ) == numerR(pre(a)) ∧ denomR(be ) == denomR(pre(a))
(usando las abs)
}
20
Contratos con requiere
Relación entre invRep y abs
invRep y abs no son únicas: En general hay varias elecciones.
problema inversa(a: Racional) {
modifica a;
requiere numerR(pre(a)) 6= 0;
asegura numerR(a) ∗ numerR(pre(a)) == denomR(a) ∗ denomR(pre(a));
}
Ejemplos:
// invRep(x): x.den > 0 ∧ mcd(x.num, x.den) == 1
// abs(x,rac): numerR(rac) == x.num ∧ denomR(rac) == x.den
// invRep(x): x.den > 0 ∧ mcd(x.num, x.den) == 1
// abs(x,rac): numerR(rac) == x.num ∧ denomR(rac) == x.den
void Racional::inversa(){
// vale invRep(pre(this)) ∧ abs(pre(this), pre(a))
// vale numerR(pre(a)) 6= 0
(por requiere)
// implica pre(this).num 6= 0
(por abs(pre(this),pre(a)))
int aux = num;
num = den;
den = aux;
// implica num == pre(this).den ∧
den == pre(this).num (por transformación de estados)
// implica den 6= 0
(porque pre(this).num 6= 0)
// implica mcd(num, den) == 1(porque mcd(pre(this).num, pre(this).den) == 1)
// implica invRep(this) ∧ abs(this, a)
// implica num ∗ pre(this).num == den ∗ pre(this).den
// implica numerR(a) ∗ numerR(pre(a)) == denomR(a) ∗ denomR(pre(a))
(porque vale abs(this, a) y abs(pre(this), pre(a)))
}
// invRep(x): x.den 6= 0
// abs(x,rac): numerR(rac) == x.num/mcd(x.num, x.den) ∧
denomR(rac) == x.den/mcd(x.num, x.den)
// invRep(x): true
// abs(x,rac): (x.den 6= 0 ∧ numerR(rac) == x.num/mcd(x.num, x.den) ∧
denomR(rac) == x.den/mcd(x.num, x.den))
∨ (x.den == 0 ∧ numerR(rac) == 1 ∧ denomR(rac) == 1)
I
Notar que para invReps más débiles se requiere un abs más
complicado.
I
La elección impacta en la implementación de las operaciones
Ejercicio: implementar y probar la correctitud de Racional con las
alternativas anteriores
22
21
Relación entre invRep y abs
Funciones auxiliares (Helpers)
class Racional {
public:
...
void suma(Racional);
private:
int num, den;
// invRep(this): this.den > 0 ∧ mcd(this.num, this.den) == 1
void reconstruir()
};
void Racional:: suma(Racional r){
num = num*r.den + r.num * den;
den =den*r.den;
reconstuir();
}
La elección no es libre. Por ejemplo, no vale la siguiente combinación.
// invRep(x): true
// abs(x,rac): numerR(rac) == x.num/mcd(x.num, x.den) ∧
denomR(rac) == x.den/mcd(x.num, x.den)
Tomemos una instancia donde x.num == 1 y x.den == 0. Notar que
mcd(1, 0) == 1. Luego, por abs(x, rac) vale
denomR(rac) == x.den/1 == 0
// helper:
void Racional::reconstruir() {
int mcd = gcd(num, den);
num = num/mcd;
den = den/mcd;
}
Esto viola el invariante de tipo, que requiere denomR(r ) 6= 0 para todo r.
Luego, invRep y abs deben ser definidos de forma tal que
invRep(x) ∧ abs(x, rac) no contradigan el invariante de tipo sobre rac.
I El invariante de representación no necesariamente vale a la entrada y salida de
una función auxiliar.
I las funciones auxiliares deben ser privadas
23
24
Prueba con auxiliares
Con las auxiliares vamos a trabajar como con cualquier bloque de código, dando su
precondicion y su poscondición.
void Racional::reconstruir() {
// PAux : True
int mcd = gcd(num, den);
num = num/mcd;
den = den/mcd;
// implica QAux : mcd(num, den) == 1
}
por transformación de estados
void Racional:: suma(Racional r){
num = num*r.den + r.num * den;
den =den*r.den;
// implica PAux : True
reconstuir();
// vale QAux
}
25
Descargar