Tadtema 1
Tadtema 1
Tadtema 1
TAD: 04/05
Los factores que determinan la calidad del software pueden ser, pues:
- Externos: Los detectados por el usuario del producto: Corrección, robustez,
extensibilidad, compatibilidad, eficiencia, portabilidad, facilidad de uso y
verificación, integridad, etc.
- Internos: Los percibidos sólo por informáticos: modularidad, legibilidad,
mantenibilidad, reutilidad, facilidad de verificación formal, etc.
Resulta claro que un mismo factor de calidad puede ser percibido por usuarios y
profesionales desde puntos de vista diferentes, y por tanto no pueden enmarcarse únicamente en
una clase u otra.
CORRECCIÓN. (Externo)
Es la capacidad del producto software para realizar justamente aquello para lo que fue
creado, tal como quedó definido en los documentos de especificación y requerimientos; en
ningún caso menos.
ROBUSTEZ. (Externo)
1
Tema 1: Componentes reutilizables del software. TAD: 04/05
Es la capacidad del producto software para poder funcionar incluso en condiciones fuera
de lo normal. Un caso típico son los Sistemas en tiempo real, y los Sistemas tolerantes a fallos.
P. ej., cuando en Windows aparece el Error de protección general se trata de una falta de
robustez, puesto que, aunque el programa no se rompe, sí impide proseguir la tarea, sin proponer
ninguna solución ni intentar auto-solucionar el problema por sí mismo. Esto es inadmisible p.ej.
en un sistema de piloto automático en una aeronave.
Supóngase el estupor que le daría a un piloto de combate que en mitad de una lucha sin
cuartel, el sistema de control de misiles se parase por un error interno con el mensaje: Error 23.
FIABILIDAD. (Externo)
Es el grado en que un programa lleva a cabo sus funciones esperadas con la precisión
requerida.
Depende del uso que se le vaya a dar. No debe ser igual la fiabilidad de un sistema de
lanzamiento de misiles militares, o de seguimiento de una órbita para un satélite, que el cálculo
del espacio útil en un edificio según un determinado plano. Por tanto, la fiabilidad depende
principalmente de las especificaciones, y principalmente de la corrección. También de la
robustez, con objeto de que al usuario no se le rompa el programa ni le aparezcan mensajes raros.
EXTENSIBILIDAD. (Externo)
REUTILIDAD. (Interno)
COMPATIBILIDAD. (Externo)
Es la facilidad de los programas para combinarse entre sí. También se puede ver como
la posibilidad de usar los resultados de un programa como entrada a otro.
2
Tema 1: Componentes reutilizables del software. TAD: 04/05
del entorno Windows, que inicialmente permitía el transporte de información de una aplicación
a otra a través del portapapeles; esto se vio potenciado enormemente con la incorporación de la
tecnología OLE.
Consiste en hacer el mejor uso posible del espacio de memoria disponible, a la vez que
se consiguen las ejecuciones más rápidas. La eficiencia también es un aspecto interno, ya que hay
algunos aspectos, (como algunos casos de ocupación temporal de memoria) que no pueden ser
detectados por el usuario.
PORTABILIDAD. (Externo)
SEGURIDAD. (Externo)
Es la capacidad del software para proteger sus diferentes componentes (programas, datos
y documentos) contra accesos no autorizados.
INTEGRIDAD. (Externo)
Está muy relacionado con la robustez. El programa no debe corromperse por entradas de
datos excesivamente largas, o excesivamente complejas.
-----·o·-----
Entre todos estos factores debemos destacar los cinco primeros (corrección, robustez,
extensibilidad, reutilidad y compatibilidad) como claves, ya que reflejan las dificultades más
serias en el desarrollo del software. No obstante, la eficiencia debe ser lo suficientemente
satisfactoria como para no hacer prohibitivo el uso de un programa.
3
Tema 1: Componentes reutilizables del software. TAD: 04/05
La variable Censo es una estructura más compleja formada sobre otra (Persona), definida
por el usuario.
4
Tema 1: Componentes reutilizables del software. TAD: 04/05
ningún momento el comportamiento del TAD, ni, por supuesto, su interfaz con el exterior, que
será siempre la misma, (ya que formalmente se trata del mismo tipo de datos). Así pues, la
separación de la especificación de la implementación de un TAD permitiría la existencia de
varias implementaciones equivalentes, dándole al programador la posibilidad de cambiar la
implementación sin que esto tenga ningún efecto en el resto del sistema.
Nosotros vamos a utilizar una notación formal para especificar el comportamiento de los
TADes.
5
Tema 1: Componentes reutilizables del software. TAD: 04/05
Las propiedades de los tipos INTEGER o BOOLEAN son bien conocidas. Sin embargo,
en el momento en que intervienen otros tipos de datos, como pilas, listas, árboles o grafos, cuyo
comportamiento y propiedades no son tan bien conocidos, es necesario disponer de definiciones
adecuadas para las operaciones sobre dichos tipos, y, en general, sobre cualquier otro que se vaya
a utilizar en un programa, de manera que se permita:
- Usar dichas operaciones en los diseños, o sea, poder usarlos aunque se carezca de la
implementación.
- Establecer precondiciones y postcondiciones sobre las estructuras de ese tipo, con
objeto de fijar el comportamiento de los programas que hacen uso de ellos, y verificar los
diseños que se deben ajustar a dichos comportamientos.
Para ello, es necesario emplear una herramienta matemática cuya exactitud no dé lugar
a ambigüedades a la hora de entender el funcionamiento de una determinada operación.
La especificación formal más ampliamente extendida es la especificación algebraica, que
constituye un caso particular de las especificaciones axiomáticas, y que usaremos de forma
intensiva a lo largo del curso.
donde N y B son los Dominios, que pueden ser diferentes del TAD que estemos definiendo; ej.:
Re, Im: C R
6
Tema 1: Componentes reutilizables del software. TAD: 04/05
de una clase de equivalencia. Para ello, resulta indispensable utilizar variables. Una variable no
es más que una palabra que identifica cualquier posible término perteneciente a un dominio
concreto (el dominio de la variable). Cuando se va a aplicar una ecuación a un término concreto,
la variable toma los valores necesarios para que la parte izquierda de la ecuación coincida con
el término. A continuación, el término original se sustituye por el término de la parte derecha de
la ecuación, reemplazando cada variable por el valor que tomó en la parte izquierda.
Por tanto, más que ecuaciones, podríamos considerarlas reducciones, pues su utilidad
principal es llegar a formas canónicas de representación de los diferentes estados del TAD; tales
reducciones se efectúan siempre de izquierda a derecha. No obstante, estas ecuaciones también
nos servirán para hacer demostraciones por inducción; en tales casos no distinguiremos entre
parte izquierda y parte derecha de la ecuación.
Veamos un ej. para entender esto. Sea la siguiente especificación formal para describir
el comportamiento de los números Naturales:
tipo N (*Naturales*)
Operaciones
0: N
suc: N N
suma: N × N N
Ecuaciones a,b:N
suma(suc(a), b) == suc(suma(a, b)) <<1>>
suma(0,b) == b
Esta especificación por sí misma no es nada. Ahora bien, nos damos cuenta de que
podemos establecer una biyección entre los términos canónicos de la misma, y los números
naturales.
Así pues, las formas canónicas son: 0, suc(0), suc(suc(0)), suc(suc(suc(0))), etc., que
podemos asociar a los números naturales 0, 1, 2, 3, etc. respectivamente. Nótese que la
especificación de los números naturales pares sería exactamente igual: 0-0, suc(0)-2, suc(suc(0))-
4, etc. O sea, por un lado tenemos que distinguir entre los garabatos que escribimos, y los
conceptos que queremos representar. La potencia aparece cuando podemos establecer una
relación entre ambos elementos, ya que los garabatos y las reglas formales por las que se rigen,
nos permiten descubrir y demostrar propiedades de los conceptos.
De esta manera, suponemos que los términos (canónicos o no) son una forma de
representar en el papel los conceptos de la vida real que se desean formalizar. Cada valor de la
vida real tiene que tener una correspondiente representación mediante un término canónico y,
posiblemente, varios términos no canónicos (incluso infinitos). En la siguiente tabla se ve como
cada valor de la realidad (expresado en la cabecera de la columna), se corresponde con varios
términos, uno de los cuales es el canónico (en negrita) porque se expresa mediante funciones
generadoras: 0 y suc(0):
7
Tema 1: Componentes reutilizables del software. TAD: 04/05
Las ecuaciones nos permiten introducir en una misma clase de equivalencia o columna,
a todos aquellos términos que representar el mismo concepto. Las variables permiten expresar
con una única ecuación infinitas posibilidades en los términos izquierdo y derecho, estableciendo
unos patrones mínimos que deben coincidir para que la ecuación pueda aplicarse.
Pero no sólo los valores tienen una representación según nuestro formalismo, sino que
las operaciones de la vida real también tiene una representación. En nuestro caso, estamos
representando las siguientes:
0: N suc: N N suma: N × N N
Con esto conseguimos que todo concepto de la vida real, ya sea valor u operación, tenga
una representación formal y, además, conseguimos que esta representación sea matemática y no
informática, con lo que evitamos tener que referenciar al cómo implementarla, expresando tan
sólo los conceptos que queremos manejar.
Las álgebras que usaremos tienen las siguientes características:
- Son álgebras generadas por términos (los estados se representan por aplicaciones
sucesivas de diferentes funciones u operaciones). Las operaciones que conforman las formas
canónicas se llaman operaciones generadoras. En el ej. anterior, las funciones generadoras
serían la función constante 0, y la función suc().
- Son álgebras con el mayor número posible de valores (términos distintos), mientras no
se diga lo contrario a través de las ecuaciones.
Las especificaciones que se obtienen de este modo se dice que definen tipos abstractos
de datos (TADes); las implementaciones que de ellos se hagan serán tipos de datos concretos.
Para la creación de los estados o valores que puede tener un TAD, vamos a considerar el
estilo constructivo, y tales valores los vamos a generar a partir de la presentación, como la
aplicación sucesiva de un subconjunto de las funciones definidas, a las que vamos a llamar
funciones generadoras del tipo en cuestión. En el caso de los números naturales, las funciones
generadoras son la función constante 0, y la primitiva suc().
Entre las operaciones que soporta un TAD, hay que diferenciar entre las primitivas (o
de enriquecimiento), que son inherentes al propio tipo, y cuya especificación es imposible si no
se conoce la construcción de los términos finales o formas canónicas, y el resto de operaciones
(llamadas derivadas), que puede construirse sólo a través de primitivas. Esta distinción es muy
importante a la hora de implementar el tipo. Una operación primitiva debe formar parte
obligatoriamente del módulo de programa que conoce la estructura de datos interna que
representa al tipo; sin embargo una operación derivada puede formar parte de una biblioteca de
8
Tema 1: Componentes reutilizables del software. TAD: 04/05
nivel superior, que desconozca dicha estructura, y que opere exclusivamente a través de las
operaciones que suministra el módulo del tipo. Algunas veces, por cuestiones de eficiencia, las
operaciones derivadas también se incluyen en el módulo principal (en lugar de en una biblioteca
separada), para que hagan un uso más efectivo de las estructuras que componen al tipo.
A veces, las operaciones derivadas también se incluyen como parte del TAD, con objeto
de aumentar la potencia del juego de operaciones suministrado por el TAD.
9
Tema 1: Componentes reutilizables del software. TAD: 04/05
Por otro lado, las presentaciones que establezcamos deben ser completas y correctas:
- Completas: Cuando se define una función nueva sobre el TAD en cuestión, hay que
considerar todas las formas canónicas que ese TAD pueda tomar. Así, en el caso de los
naturales, no podríamos haber desechado <<1>>, porque es donde consideramos el único
valor de TAD que no empieza por suc(...).
Esta completitud viene derivada de la semántica asociada al tipo que estamos
definiendo. La completitud también implica que tras aplicar una operación a una forma
canónica, se pueda reducir siempre a otro término formado exclusivamente por
generadores.
- Correctas (Consistente): Una misma operación no puede dar lugar a dos formas
canónicas diferentes, y por tanto semánticamente diferentes, ya que según hemos dicho,
estamos ante álgebras en las que todos los términos son diferentes mientras no se
demuestre lo contrario (mientras no se puedan reducir a una forma común mediante las
ecuaciones). O sea, no posee contradicciones.
Ej.:
suma(suc(a), b) == suc(suma(a,b))
suma(a, suc(b)) == suc(suc(suma(a,b)))
suma(0, 0) == 0
suma(a,b) == suma(b,a)
Nótese la importancia de las constantes, ya que las formas canónicas se forman de manera
recursiva, y como en toda recursión, es necesario que se produzca un paso final, o definición
directa que dé un valor base sobre el que construir ff.cc. más complejas mediante vuelta atrás
(backtracking).
A poco que el tipo que estemos definiendo se complique, aparecerán no sólo operaciones
totales (definidas para cualquier valor de su dominio), sino también operaciones parciales,
definidas únicamente para ciertos valores de su dominio, valores que en general quedan
englobados en un subconjunto del dominio, que cumple ciertas propiedades que quedan
identificadas a través de un predicado. Así, en un nuevo apartado de nuestras especificaciones,
indicaremos las precondiciones o predicados que deben satisfacerse para poder aplicar una de
estas operaciones parciales. En la definición de ecuaciones que explique el comportamiento de
esa función parcial se omitirán aquellas formas canónicas que se salen fuera del dominio de la
operación parcial de que se trate. Ej.:
pre resta resta(m, suc(s)) : not (m=0)
10
Tema 1: Componentes reutilizables del software. TAD: 04/05
resta(m, 0) == m
resta(suc(m), suc(s)) == resta(m, s)
1.3.2 Resumen.
En resumidas cuentas, podemos decir que las especificaciones algebraicas que vamos a
utilizar siguen una sintaxis compuesta por los siguientes bloques:
- tipo T: Indica el nombre T con el que se va a identificar el TAD que se va a definir.
- dominios T1, T2,, ...: Indica los nombres de los TADes que van a ser referenciados en la
presente especificación. Si se desea puede incluirse el nombre del TAD que se va a definir.
- generadores: Agrupa las definiciones sintácticas en la forma:
f: Te1 ×...× Ten Ts1 ×...× Tsm, con n $ 0, m > 0,
donde f es el nombre de una función generadora, y Tei y Tsj son los dominios y los rangos
respectivamente.
- constructores. Agrupa las definiciones sintácticas de aquellas funciones en las que T 0 {Ts1 ,
Ts2,, ... Tsm}.
- selectores. Agrupa las definiciones sintácticas de aquellas funciones en las que T ó {Ts1 , Ts2,,
... Tsm}.
- auxiliares: Agrupa las definiciones sintácticas de aquellas funciones que no son propias de
TAD que se define, y que son necesarias tan sólo para facilitar la especificación de las demás.
A continuación aparecen otros bloques, que tienen como característica común el hacer
uso de variables. Una variable es un identificar asociado a un TAD declarado en los dominios
y/o en tipo, y que representa a cualquier término (canónico o no) de dicho TAD. Una variable
se utiliza, a su vez, dentro de otro término para denotar una familia de términos con ciertas
características comunes.
Las variables se declaran a continuación de la palabra que identifica el bloque, de la
forma:
v11, v12, ..., v1m 0 T1; v21, v22, ..., v2n 0 T2; ... vk1, vk2, ..., vkñ 0 Tk;
Los bloques referidos son:
- precondiciones: Establece los predicados que deben cumplir las funciones parciales para
representar un valor distinto de error. Por tanto, toda función definida de la forma:
q : Te1 ×...× Ten Ts1 ×...× Tsm, con n $ 0, m > 0,
deberá poseer una cláusula en este bloque de la forma:
término_con_q : predicado
que se interpreta como: «Si el término término_con_q no cumple el predicado entonces equivale
a un error».
«Para el caso en que término_con_q posea un rango formado por varios TADes, si no
cumple el predicado, equivaldrá a error en cada uno de dichos rangos».
«Si alguna de las variables, o de los valores a que hace referencia término_con_q
representa a un error, se considerará que término_con_q no cumple predicado».
- teoremas: Este bloque es opcional, y establece las ecuaciones que no pretenden reducir un
término a su forma canónica. En general, los teoremas los incluiremos dentro del bloque
siguiente.
- ecuaciones: Establece el conjunto de equivalencias de la forma:
término1 == término2
que agrupa a los términos término1 y término2 en la misma clase de equivalencia, representando
por tanto el mismo concepto. Las ecuaciones pueden verse, normalmente, como cláusulas de
reducción, de manera que leídas de izquierda a derecha, pretenden reducir cualquier término a
11
Tema 1: Componentes reutilizables del software. TAD: 04/05
su forma canónica.
- Booleano (Lógico).
- Número natural.
- Número entero.
- Número complejo.
1.3.3.1 BOOLEANO.
tipo B (* Booleano *)
generadores
V, F: B
constructores
not: B B
and, or: B×B B
->, <->: B×B B
ecuaciones, b:B
not(V) == F
not(F) == V
and(V, b) == b
and(F, b) == F
or(V,b)/V
(1) or(a,b)/not(and(not(a),not(b))) (2)
or(F,b)/b
->(a,b) == or(not(a), b)
<->(a, b) == and(->(a, b), ->(b, a))
NOTA: Si optamos por la opción (2), entonces las operaciones not y and son operaciones
primitivas, o sea, que se hallan definidas directamente en función de los términos finales o
formas canónicas, que en este caso son solamente dos constantes. La opción (2) no es más que
la equivalencia: acb/a1b .
Obsérvese que en este caso, todos los valores del tipo (Booleano en nuestro caso) están
definidos mediante operaciones constantes. Hay muchas situaciones en las que los valores se
especifican mediante una secuencia enumerada de identificadores u operaciones constantes,
como puede ocurrir por ejemplo con los días de la semana. En estos casos, cada valor
independiente vendrá dado por una operación constante cuyo dominio es, por tanto, inexistente,
12
Tema 1: Componentes reutilizables del software. TAD: 04/05
y cuyo rango es el del tipo que se define. El caso de los números de la semana quedaría:
tipo Día
dominios B
generadores
L, M, X, J, V, S, D: Día
selectores
EsFinDeSemana: Día B
ecuaciones
EsFinDeSemana(L) == F
EsFinDeSemana(M) == F
EsFinDeSemana(X) == F
EsFinDeSemana(J) == F
EsFinDeSemana(V) == F
EsFinDeSemana(S) == V
EsFinDeSemana(D) == V
selectores
=: Día × Día B
ecuaciones, a, b 0 Día
=(a, b) == =(b, a)
=(L, L) == V =(M, V) == F =(J, S) == F
=(L, M) == F =(M, S) == F =(J, D) == F
=(L, X) == F =(M, D) == F =(V, V) == V
=(L, J) == F =(X, X) == V =(V, S) == F
=(L, V) == F =(X, J) == F =(V, D) == F
=(L, S) == F =(X, V) == F =(S, S) == V
=(L, D) == F =(X, S) == F =(S, D) == F
=(M, M) == V =(X, D) == F =(D, D) == V
=(M, X) == F =(J, J) == v
=(M, J) == F =(J, V) == F
13
Tema 1: Componentes reutilizables del software. TAD: 04/05
tipo ù (* Natural *)
dominios B
generadores
0: ù
suc: ù ù
constructores
suma, producto : ù × ù ù
resta: ù × ù ù
selectores
<=, = : ù × ù B
precondiciones m,s 0 ù
resta(m, s): <=(s, m)
ecuaciones m,n:ù
<=(0, n) == V
<=(suc(n), 0) == F
<=(suc(n), suc(m)) == <=(n, m)
=(n, m) == and(<=(n, m), <=(m, n))
suma(0, m) == m
suma(suc(n), m) == suc(suma(n, m))
producto(0, m) == 0
producto(suc(n), m) == suma(m, producto(n, m))
resta(n, 0) == n
resta(suc(n), suc(m)) == resta(n, m)
NOTA: Aquí se observa la aparición de una función u operación parcial resta, que no está
definida para cualquier conjunto de valores de sus argumentos. Para dejar constancia de cuando
se puede proceder a su cálculo, se incluye un apartado de precondiciones, en el que se indica el
aserto (operación que se debe verificar siempre), antes de la ejecución de esa operación. A tal
aserto se le llama precondición.
Aunque no lo usaremos por ahora, también es muy útil el concepto de postcondición, que
no es más que un aserto para después de la ejecución de una determinada operación.
tipo Z
dominios Z, B, ù
generadores
0: Z
suc, pred: Z Z
constructores
14
Tema 1: Componentes reutilizables del software. TAD: 04/05
suma: Z × Z Z
resta: Z × Z Z
producto: Z × Z Z
auxiliares
nro_pred_y_suc: Z ù×ù
suc1, suc2: ù × ù ù×ù
selectores
<=, =: Z × Z B
ecuaciones n,m 0 Z
(1) pred(suc(n)) == n
(2) suc(pred(n)) == n
resta(n, 0) == n
resta(n, suc(m)) == pred(resta(n, m))
resta(n, pred(m)) == suc(resta(n, m))
suma(n, m) == resta(n, resta(0, m))
producto(n, 0) == 0
producto(n, suc(m)) == suma(n, producto(n, m))
producto(n, pred(m)) == resta(producto(n, m), n)
nro_pred_y_suc(0) == (0, 0)
nro_pred_y_suc(suc(n)) == suc2(nro_pred_y_suc(n))
nro_pred_y_suc(pred(n)) == suc1(nro_pred_y_suc(n))
suc1(n, 0) == (suc(n), 0)
suc1(n, suc(m)) == (n, m)
suc2(0, m) == (0, suc(m))
suc2(suc(n), m) == (n, m)
(3) <=(n, m) == <=(nro_pred_y_suc(resta(m,n)))
=(n, m) == and(<=(n, m), <=(m, n))
NOTA: En este ejemplo se introduce el concepto de operación auxiliar. Una operación auxiliar
es una funcionalidad (posibilidad de hacer algo) que permite especificar con mayor facilidad una
determinada operación.
En este caso surge el problema de que los parámetros de entrada a la función
<=:Z×Z Z, puede ser cualquier sucesión de funciones suc() y pred(); sin embargo, este par
de generadores no son libres, como se desprende de las ecuaciones (1) y (2), o sea, hay ciertas
combinaciones entre ellos, que denotan al mismo término o forma canónica.
La operación nro_pred_y_suc():Z NxN, devuelve un par de números Naturales, que
no es más que el número normalizado de operaciones pred() y suc() que constituyen a un Entero
(uno de los dos valores será 0). Así pues, la operación <=() de la parte derecha de (3), no es la
que estamos definiendo en esta especificación algebraica, sino la que definimos en el apartado
anterior para los números naturales. La confusión radica en que poseen el mismo nombre.
En este apartado vamos a estudiar los números complejos, cuyas características merecen
ser comentadas independientemente.
tipo ÷ (* Complejo *)
dominios Z,
15
Tema 1: Componentes reutilizables del software. TAD: 04/05
generadores
complejo: Z × Z
constructores
suma, resta, producto: ×
selectores
real, imaginaria: Z
ecuaciones r1, i1, r2, i2 0 Z
real(complejo(r1, i1)) == r1
imaginaria(complejo(r1, i1)) == i1
suma(complejo(r1, i1), complejo(r2, i2)) ==
complejo(suma(r1, r2), suma(i1, i2))
resta(complejo(r1, i1), complejo(r2, i2)) ==
complejo(resta(r1, r2), resta(i1, i2))
producto(complejo(r1, i1), complejo(r2, i2)) ==
complejo (
resta(producto(r1, r2), producto(i1, i2)),
suma(producto(r1, i2), producto(i1, r2))
)
Como puede observarse, en este caso, existe un único generador (llamado complejo), para
obtener cualquier término canónico del tipo , a diferencia de los casos anteriores, en los que
existía un generador constante, el 0, a partir del cual se iban construyendo el resto de los valores
mediante la adición de una nueva operación.
Este hecho nos hace poder considerar a cualquier valor de tipo como un par de valores
de tipo Z, lo cual coincide hasta cierto punto con nuestro concepto de registro propio de
cualquier lenguaje de programación. Por otro lado, todo tipo registro al que estamos
acostumbrados, posee operaciones que permiten:
1.- Construir un registro a partir de valores de cada uno de sus componentes.
2.- Extraer el valor de cada uno de sus componentes.
De esta manera, si consideramos el tipo como un registro, la operación complejo se
encarga del primer punto, mientras que las operaciones real e imaginaria se encargan de extraer
los valores de los componentes, tal y como exige el segundo punto.
Siguiendo este mismo esquema podemos construir cualquier tipo registro que se nos
antoje. P.ej., supongamos que deseamos un registro llamado MiRegistro que posee dos campos
de tipo boolean, otro campo de tipo natural, y un último campo de tipo complejo. Supongamos
que queremos llamar a dichos campos U, V, X e Y respectivamente. La especificación sería:
tipo MiRegistro
dominios B, , ù
generadores
miRegistro: B × B × ù × MiRegistro
selectores
U, V: MiRegistro B
X: MiRegistro ù
Y: MiRegistro
ecuaciones b1, b2 0 B, n 0 ù, c 0
16
Tema 1: Componentes reutilizables del software. TAD: 04/05
Así, la construcción de un registro pasa por crear un generador que admite como
parámetros los valores de cada uno de los campos, y por crear tantos selectores como campos
haya; cada selector extraerá uno de los valores del registro.
1.4.1 Pascal.
17
Tema 1: Componentes reutilizables del software. TAD: 04/05
- El tipo opaco, que permite dar al programador la posibilidad de hacer uso de un tipo sin
conocer para nada su estructura. En Ada, los tipos opaco pueden ser de varios tipos según
se puedan efectuar sobre ellos operaciones como la igualdad, etc.
- Ada también permite el uso de genéricos, que no son más que TADes incompletos en
los que falta el tipo base. Para poderlos utilizar, deben ser instanciados usando como
parámetro un tipo base.
Normalmente hay muchos programas o algoritmos que pueden resolver una misma tarea
o problema. Es necesario, por lo tanto, considerar los criterios que podemos usar para determinar
cuál es la mejor de las posibilidades en cada situación. Estos criterios pueden incluir
características de los programas tales como documentación, portabilidad, etc. Alguno de estos
criterios puede ser analizado cuantitativamente, pero en general, la mayoría no pueden ser
evaluados con precisión. Puede que el criterio más importante (después de la corrección, por
supuesto) es el que es normalmente conocido como eficiencia. Informalmente, la eficiencia de
18
Tema 1: Componentes reutilizables del software. TAD: 04/05
19
Tema 1: Componentes reutilizables del software. TAD: 04/05
Para hacer referencia a la velocidad de crecimiento de los valores de una función se usará
la notación conocida como asintótica (O).
Ejemplo: Decir que el T(n) (complejidad temporal) de un programa es O(n2) significa que
existen constantes enteras positivas c y n0 tales que para n $ n0, se tiene que T(n) 2 cn2.
De hecho, O(f(n)) es un conjunto, y viene definido por:
O(f(n)) = { g: ù E + c {0} | ª c 0 +, n0 0 ù. « n $ n0. g(n) 2 c·f(n) }
Así decir que T(n) es O(n2) equivale a T(n) 0 O(n2)
Ejemplo: La función T(n)=3n3+2n2 es O(n3). Para comprobar esto, sean n0=0 y c=5. Para
n $ 0, 3n3+2n2 2 5n3. También se podría decir que T(n) es O(n4) pero sería una aseveración más
débil. Así, buscamos la cota superior más baja para T(n).
Cuando se dice que T(n) es O(f(n)), se sabe que f(n) es una cota superior para la velocidad
de crecimiento de T(n), en el peor caso general. Para especificar una cota inferior para la
velocidad de crecimiento de T(n) en el mejor caso general, se usa la notación T(n) es Ω(g(n)),
que significa que existe una constante c, y otra n0 tal que T(n) $ c·g(n). « n $ n0. Formalmente:
Ω(f(n)) = { g: ù E + c {0} | ª c 0 +, n0 0 ù. « n $ n0. g(n) $ c·f(n) }
Cuando en un algoritmo f(n) se tiene que su T(n) 0 O(g(n)), y T(n) 0 Ω(g(n)), se dice que
es del orden exacto de g(n), y se denota por Θ(g(n)).
Ejemplo: T(n)=n3+2n2 es Ω(n3). Con c=1 tenemos T(n) $ c·n3 para c = 0,1,....
Por lo general no consideraremos las constantes de proporcionalidad, un programa con
O(n2) será mejor que uno con O(n3); sin embargo, tenemos que considerar el tamaño de la
entrada. Si el primer programa tiene T(n)=100n2 y el segundo tiene T(n)=5n3, para entradas
pequeñas el segundo será más rápido, y sólo en entradas grandes (n>20) será más rápido el
primero.
Es posible que T(n) 0 O(g(n)) y T(n) ó Ω(g(n)), como p.ej. en el caso:
n 3 si n es par
T(n)
n 2 si n es impar
resulta evidente que cuando n es par, en el peor caso se tiene T(n) = n2, por lo que T(n) 0 Ω(n2).
1.6 RECURSIVIDAD.
20
Tema 1: Componentes reutilizables del software. TAD: 04/05
potencia que ello nos suministra, especialmente en lo referente al concepto iterativo de bucle, que
permite ejecutar reiteradas veces un trozo de código.
Es por esto que en las especificaciones algebraicas toma especial relevancia el concepto
de recursividad, pues pasa a ser el único método disponible para aplicar reiteradas veces una
función sobre un valor o sobre una colección de valores.
Para comprender esto mejor, veamos como ejemplo el cálculo del factorial:
1 si n0
f(n)n!
n·f(n1) si n>0
a) Como caso base tenemos cuando n=0. Para tal entrada, la salida es directa: 1, sin
necesidad de llamadas a problemas más pequeños.
b) Si la entrada es un valor de n mayor que 0, ¿cómo calcular el valor de n! si no podemos
utilizar explícitamente bucles?. El método parte de que n! puede descomponerse en un problema
más pequeño s(n) = (n-1), cuya solución f(s(n)) podemos componer con el propio n para obtener
la solución a f(n), de la forma f(n) = c(n, f(s(n))) = n*f(s(n)).
c) Hacemos una llamada para solucionar el problema s(n).
d) Por último hacemos la composición c(n, f(n-1)) para calcular f(n)=n!.
Desde el punto de vista de la función de composición c(P, f(s(P))), podemos clasificar las
funciones recursivas en dos grandes bloques:
Aunque parezca que la recursividad final no tiene mucho sentido, existen numerosos
casos en los que es aplicable, como puede ser el algoritmo para saber si un elemento está o no
en una lista; otro ejemplo de recursividad final es el algoritmo de Euclides para el cálculo de
máximo común divisor entre dos números:
21
Tema 1: Componentes reutilizables del software. TAD: 04/05
c : INTEGER;
BEGIN
IF a=b THEN
RETURN a;
ELSIF a>b THEN
c := mcd(a-b, b);
RETURN c;
ELSE
c := mcd(a, b-a);
RETURN c;
END;
END mcd;
Como se observa en este ejemplo, el resultado de la función obedece a dos casos bien
distintos, el caso trivial cuando a=b, y el caso recursivo cuando a…b, momento en que aparece
la función c() cuyo valor depende únicamente de la solución al problema más pequeño.
Como ejemplo de recursividad no final tenemos el cálculo del factorial, en el que c() no
depende tan sólo de (n-1)!, sino también de n en sí.
donde _y son los parámetros adicionales de la función inmersora, y _z sus resultados adicionales;
22
Tema 1: Componentes reutilizables del software. TAD: 04/05
Un método eficaz cuando nuestro tad dispone de una operación de acceso directo a
cualquiera de sus elementos (p.ej., una lista posicional), consiste en hacer _z = [], y w _ =
[contadores de bucle de la versión iterativa, valores finales de los contadores]. De esta forma, la
ecuación f() quedará:
f(x_) == g(x
_, w
_ 0, w
_ f)
donde w_ 0 son los valores iniciales de los contadores de bucle, y w
_ f son las constantes que indican
el máximo valor que pueden alcanzar los contadores anteriores. Las ecuaciones de g() detectarán
cuando el parámetro w _ toma el valor final del bucle (caso trivial), en cuyo caso se devolverá un
valor base sobre el que se construirá el resultado. Para ilustrar un caso fácil, veamos cómo
podemos construir la lista suma de dos listas de igual longitud; la versión iterativa de informal
sería algo así como:
operaciones
Suma : Lista × Lista Lista
auxiliar
Suma2 : Lista × Lista × ù × ù Lista
precondiciones
pre : Suma(l1, l2) : Longitud(l1) = Longitud(l2)
ecuaciones
Suma(l1, l2) == Sumag(l1, l2, 1, Longitud(l1))
Sumag(l1, l2, i, n) == SI i > n ENTONCES
Crear
SI NO
Insertar(Sumag(l1, l2, suc(i), n), 1,
Elemento(i, l1) + Elemento(i, l2))
Vemos que la única ecuación que implica a Suma(), lo que hace es una transformación
a Sumag(), incluyendo dos nuevos parámetros, a saber, el valor inicial del bucle, y el valor final.
Sumag se encarga de llamarse a sí misma incrementando cada vez el valor actual del contador del
bucle, hasta llegar a un valor más allá del máximo en cuyo caso devuelve el resultado trivial: la
lista vacía. En cada paso no trivial, se obtiene la suma de los elementos situados en la posición
i de las listas originales. Como la lista actual está en construcción, dicho resultado se inserta
siempre en la cabeza de la lista, con lo que una vez construída del todo, habremos obtenido el
resultado deseado.
23
Tema 1: Componentes reutilizables del software. TAD: 04/05
Como ejemplo más completo, veamos como podemos abordar el cálculo del producto
entre dos matrices. Partimos de la siguiente especificación:
tipo Matriz
dominios ù
sintaxis
generadores
Crear : ù × ù × ù Matriz
Insertar : ù × ù × ù × Matriz Matriz
selectores
Dimensiones : Matriz ù×ù
Elemento : ù × ù × Matriz ù
constructores
Mult : Matriz × Matriz Matriz
auxiliares
primero, segundo : ù × ù ù
precondiciones n, n1, n2 0 ù
m, m1, m2 0 Matriz
pre Crear(n1, n2, n) : (n1 > 0) AND (n2 > 0)
pre Insertar(n1, n2, n, m) : (n1 > 0) AND
(n2 > 0) AND
(n1 2 primero(Dimensiones(m)) AND
(n2 2 segundo(Dimensiones(m))
pre Elemento(n1, n2, m) : (n1 > 0) AND
(n2 > 0) AND
(n1 2 primero(Dimensiones(m)) AND
(n2 2 segundo(Dimensiones(m))
pre Mult(m1, m2) : (segundo(Dimensiones(m1) = primero(Dimensiones(m2))
ecuaciones
Insertar(p11, p12, n, Insertar(p21, p22, n2, m)) ==
SI (p11 = p21) AND (p12 = p22) ENTONCES
Insertar(p11, p12, n, m)
SINO
Insertar(p21, p22, n2, Insertar(p11, p12, n1, m))
Dimensiones(Crear(n1, n2, n)) == n1, n2
Dimensiones(Insertar(p11, p12, n, m)) == Dimensiones(m)
Elemento(p11, p12, Crear(n1, n2, n)) == n
Elemento(p11, p12, Insertar(p21, p22, n2, m)) ==
SI (p11 = p21) AND (p12 = p22) ENTONCES
n
SINO
Elemento(p11, p12, m))
Mult(m1, m2) == Mult2(m1, m2, 1, 1)
Mult2(m1, m2, p1, p2) ==
SI p1 > primero(Dimensiones(m1))
Crear(primero(Dimensiones(m1)), segundo(Dimensionaes(m2)), 0)
SI NO SI p2 > segundo(Dimensiones(m2)) ENTONCES
Mult2(m1, m2, suc(p1), 1)
24
Tema 1: Componentes reutilizables del software. TAD: 04/05
SI NO
Insertar(p1, p2, Prod_parcial(p1, p2, m1, m2)), Mult2(m1, m2, p1, suc(p2)))
Prod_parcial(p1, p2, m1, m2) == Prod_parcial2(p1, p2, m1, m2, 1)
Prod_parcial2(p1, p2, m1, m2, n) ==
SI n > segundo(Dimensiones(m1)) ENTONCES
0
SINO
Elemento(p1, n, m1) * Elemento(n, p2, m2) + Prod_parcial2(p1, p2, m1, m2,
suc(n))
En este caso, hacemos uso de dos contadores: el primero para las filas, y el segundo para
las columnas. No es necesario aquí, pasar los valores finales de los contadores, ya que éstos
pueden obtenerse fácilmente mediante la operación Dimensiones() aplicada a cualquiera de las
dos matrices de entrada. El método que se sigue es el mismo, sólo que con dos bucles. Cuando
el bucle externo llega a su fin se retorna una matriz base, cuyos elementos se han inicializado a
cualquier valor, p.ej. 0. Cuando es el bucle interno el que llega a su final, se genera una nueva
llamada en la que se reinicia el bucle interno, y se incrementa el externo, convergiendo así al caso
final.
Si ambos contadores están en los límites adecuados, pasamos a calcular el valor de la
posición (p1, p2) de la matriz resultante, que se Inserta en el resultado parcial subsiguiente. Este
cálculo, dado por la operación Prod_parcial es, de nuevo, inherentemente iterativo, por lo que
su especificación se hace recursiva mediante una nueva inmersión, gracias a un solo contador de
bucle, que también se inicia a 1.
Aunque este tema se verá en mayor profundidad en asignaturas posteriores, daremos aquí
la formulación básica que expresa la complejidad de los algoritmos recursivos en función de la
forma de proceder particular de cada uno de ellos. También veremos la demostración de algunos
de estos cálculos.
Las funciones recursivas más simples son aquellas que reducen el problema de forma
mínima, o sea, pasan de calcular f(n) a calcular f(n-1), y que desarrollan a lo sumo una llamada
interna por cada llamada externa. La complejidad de estas funciones se puede expresar como:
k1 si n0
T(n)
T(n1)k2 si n>0
No obstante, podemos hacer nuestros cálculos más generales si suponemos que el tamaño
del problema no decrece en una sola unidad en cada llamada interna, sino que, en general,
decrece en b unidades. Asimismo, supondremos que cada ejecución del algoritmo recursivo tiene
una complejidad de nk, y que no estamos ante recursividad lineal, sino que se hacen p llamadas
recursivas internas por cada externa. En definitiva, consideraremos que nuestro algoritmo tiene
la siguiente estructura:
ALGORITMO f(n)
25
Tema 1: Componentes reutilizables del software. TAD: 04/05
PARA i1 := 1 A n HACER
PARA i2 := 1 A n HACER
: :
PARA ik := 1 A n HACER
FIN
:
FIN
FIN
SI caso_base ENTONCES DEVOLVER
Llamada nº 1 a f(n-b).
Llamada nº 2 a f(n-b).
:
Llamada nº p a f(n-b).
FIN ALGORITMO.
nk si 02n<b
T(n)
pT(nb)cn k si n$b
m
= j p ic(nib)k
i0
suponiendo que T(n-mb) = nk, o sea, que 0 2 n-mb < b, o lo que es lo mismo m = { n/b |. Así,
podemos hacer la siguiente transformación:
m m
(nib)k k n
i
jp c b = j cb kp i( i)k
i0 b k i0 b
Como m = { n/b |, se tiene m 2 n/b < m+1, por lo que
m m
cb k j p i(mi)k 2 T(n) < cb k j p i(m1i)k
i0 i0
Así, ahora distinguiremos dos casos. Actuaremos a grosso modo ya que lo que se desea
obtener es el O(f(n)).
1) p = 1.
m m
O cb k j p i(m1i)k = O j (m1i)k por ser cbk constante, y p=1.
i0 i0
m
Tenemos que j (m1i)k = (m+1)k + (m)k + (m-1)k + (m-2)k + ... + 2k + 1k / m+1
i0
26
Tema 1: Componentes reutilizables del software. TAD: 04/05
términos
cuyo lim es (m+1)k repetido m+1 veces, o sea, (m+1)k+1. Como m es función líneal de n,
n64
m
(m1i)k
O cb kp m1 j
i0 p m1i
sustituyendo m+1-i por j, tendremos:
m1 m1
jk jk
O cb kp m1 j , donde la suma j converge a una constante en el lim . Así
j1 pj j1 pj n64
m1
jk
O cb kp m1 j = O cb kp m1co = O p m1 , deduciendo así que si p > 1, entonces
j1 pj
n
T(n) 0 O p b
.
p=1 O(nk+1)
p>1 n
b
O p
Sin embargo, no todos los algoritmos recursivos reducen el problema original en una
cantidad constante. Cuando las llamadas recursivas reducen el problema siguiendo el esquema
de división (como p.ej. una búsqueda dicotómica), podemos establecer un esquema similar:
cn k si 12n<b
T(n) n
pT( )cn k si n$b
b
que puede corresponder a un algoritmo como:
ALGORITMO f(n)
PARA i1 := 1 A n HACER
PARA i2 := 1 A n HACER
: :
PARA ik := 1 A n HACER
FIN
:
FIN
FIN
SI caso_base ENTONCES DEVOLVER
27
Tema 1: Componentes reutilizables del software. TAD: 04/05
Llamada nº 1 a f(n/b).
Llamada nº 2 a f(n/b).
:
Llamada nº p a f(n/b).
FIN ALGORITMO.
p < bk O(nk)
p = bk O(nk logb n)
p > bk logb p
O(n )
En efecto, es posible. Crearemos una operación que tomará tres grupos de parámetros;
el primero será una valor lógico, y los otros dos será exactamente del mismo tipo o grupo de
tipos; si el parámetro lógico es Verdadero, nuestra operación devolverá el primero grupo de
28
Tema 1: Componentes reutilizables del software. TAD: 04/05
Existen determinadas operaciones que, por sus características, no pueden ser utilizadas
en todas las condiciones posibles, esto es, existen determinados valores de entrada para los cuales
no están definidas; p.ej.: la división por 0, o la resta en los números naturales cuando el
sustraendo es mayor que el minuendo. Este hecho da lugar al concepto de operación parcial.
tipo N (* Natural *)
dominios N, B
generadores
0: ù
suc: ù ù
error_N: ù
constructores
29
Tema 1: Componentes reutilizables del software. TAD: 04/05
suma, producto : ù × ù ù
resta: ù × ù ù
selectores
<=, = : ù × ù B
objeto_erróneo: ù B
ecuaciones m,n:ù
suc(error_N) == error_N
objeto_erroneo(0) == F
objeto_erroneo(error_N) == V
objeto_erroneo(suc(n)) == objeto_erroneo(n)
<=(0, n) == SI objeto_erroneo(n) ENTONCES
error_B
SI NO
V
<=(suc(n), 0) == SI objeto_erroneo(n) ENTONCES
error_B
SI NO
F
<=(suc(n), suc(m)) == <=(n, m)
=(n, m) == and(<=(n, m), <=(m, n))
suma(0, m) == m
suma(suc(n), m) == suc(suma(n, m))
suma(error_N, m) == error_N
producto(0, m) == SI objeto_erroneo(m) ENTONCES
error_N
SI NO
0
producto(suc(n), m) == suma(m, producto(n, m))
producto(error_N, m) == error_N
resta(n, 0) == n
resta(suc(n), suc(m)) == resta(n, m)
resta(0, suc(n)) == error_N
resta(n, error_N) == error_N
resta(error_N, m) == error_N
Para evitar este problema, tomaremos algunas convenciones con objeto de simplificar la
escritura de la especificación, pero sin que el resultado final se vea alterado:
1.- Todo dominio T introducido en la especificación ofrece automáticamente dos
operaciones visibles: la constante errorT, y el predicado de corrección convenientemente
especificado: objeto_erroneo: T B.
2.- Las ecuaciones que puedan producir errores, se colocarán en una cláusula especial de
precondiciones, en la que se indicará el término, y la precondición que debe cumplir para
no ser considerado un objeto erróneo.
3.- Durante la evaluación de términos, los errores se propagan automáticamente; así, no es
necesario ecuaciones propagación de la forma op_x(errorT) == errorT.
4.- El resto de ecuaciones que se indiquen en el apartado de semántica ecuacional,
supondremos que tanto la parte derecha como la izquierda, están libres de errores. P.ej.:
una ecuación como: op_x(t) == op_y(t) equivaldrá realmente a:
30
Tema 1: Componentes reutilizables del software. TAD: 04/05
Siguiendo este criterio, nos quedaría la especificación de los naturales que hemos visto
a lo largo de este capítulo, y que repetimos a continuación a modo de recordatorio.
tipo ù (* Natural *)
dominios B
generadores
0: ù
suc: ù ù
constructores
suma, producto : ù × ù ù
resta: ù × ù ù
selectores
<=, = : ù × ù B
precondiciones m,s ? ù
resta(m, s): <=(s, m)
ecuaciones m,n:ù
<=(0, n) == V
<=(suc(n), 0) == F
<=(suc(n), suc(m)) == <=(n, m)
=(n, m) == and(<=(n, m), <=(m, n))
suma(0, m) == m
suma(suc(n), m) == suc(suma(n, m))
producto(0, m) == 0
producto(suc(n), m) == suma(m, producto(n, m))
resta(n, 0) == n
resta(suc(n), suc(m)) == resta(n, m)
EJERCICIOS.
2.- Crear la función Siguiente_Par: ù ù, que devuelva el número par más cercano y mayor
que su argumento. Emplear la estructura SI .. ENTONCES .. SINO ...
3.- Crear la función Neg: Z Z, que dado un entero, le cambia su signo. Ej.: Neg(-3) = 3;
Neg(8) = -8.
31
Tema 1: Componentes reutilizables del software. TAD: 04/05
5.- Especificar la función raizC: ù ù que permita calcular la raíz cúubica de un natural.
El resultado de esta operación debe ser el natural más cercano y menor o igual a la raíz cúbica
de su parámetro. Ej.: raizC(8)=2, raizC(70)=4.
6.- Realizar la especificación del tipo abstracto Par, que representa los números pares positivos,
incluído el cero. Indicar cuáles son las operaciones generadores y especificar las siguientes
operaciones:
• parANatural: Par ù
• naturalAPar: ù Par
• suma: Par × Par Par
• producto: Par × Par Par
Indicar qué operaciones son parciales y cuál es su precondición.
10.- Crear el tipo abstracto de datos Módulo_8 (M_8), que tenga como valores 0, suc(0), ...
suc6(0), suc7(0). Los generadores serán 0 y suc(). Especificar las funciones Suma, Producto,
Resta: M_8×M_8 M_8.
11.- Construir el tipo Racional haciendo uso del tipo Entero. Se han de especificar las
32
Tema 1: Componentes reutilizables del software. TAD: 04/05
operaciones generadoras y funciones para sumar, restar, multiplicar y dividir dos números
racionales. Asimismo debe especificarse otra operación que diga si un racional es menor que
otro.
12.- Especificar el tipo Fecha que contiene la siguiente información: día del mes, mes,
representado por un número, de manera que enero sea el 1 y diciembre el 12, y año. Por ejemplo,
un valor de tipo fecha sería de la forma 20 9 2001. Se han de especificar las siguientes
operaciones:
• Las operaciones generadoras necesarias.
• Las operaciones necesarias para consultar cada uno de los valores de una fecha.
• valida: Fecha B, que devuelve verdadero si el valor corresponde a una fecha
correcta y falso en caso contrario.
• sigDia: Fecha Fecha, que devuelve la fecha correspondiente al día siguiente de la
fecha
• que se pasa como parámetro
• posterior: Fecha × Fecha B, que devuelve verdadero si la segunda fecha es posterior
a la primera y falso en caso contrario.
• diaSemana: Fecha × ù × Fecha ù, que, dada una fecha y el día de la semana al que
corresponde, devuelve el día de la semana al que corresponde la segunda fecha. El día de
la semana está representado por un natural, de manera que el lunes es el 1 y el domingo
el 7. La precondición de esta operación es que la segunda fecha es posterior a la primera.
13.- La función f(n, z), con n y z de tipo natural, devuelve un valor también de tipo natural y se
define de la siguiente manera:
z z2 z3 z n− 2 z n −1 zn
f ( n, z ) = + + + ...+ + +
1 2! 3! ( n − 2) ! ( n − 1) ! n!
Especificar algebraicamente f(n, z). Nota: En el cálculo de f(n, z) sólo se usan valores naturales,
incluso como resultado de las divisiones.
14.- Sea el TAD CCC (Color-Color-Color), que sólo puede tomar uno de los 8 valores siguientes:
BVA, BAR, BRM, BMV, NVA, NAR, NRM, NMV. (B-Blanco, N-Negro, V-Verde, R-Rojo,
A-Azul, M-Magenta). Crear la función
Todos_Distintos: CCC×CCC×CCC×CCC×CCC×CCC×CCC×CCC B
que devuelve V si todos sus argumentos son diferentes, y F en caso contrario.
Nota: Veamos el significado de esto. Vamos a tener un cubo mágico con los seis colores
propuestos. Cada cara estará dividida en 4
trozos (en lugar de 9 como en el caso
B M
normal). El TAD CCC representa los
colores que tiene cada una de las 8 esquinas
que forman el cubo. Vamos a numerar estas
esquinas de la forma siguiente:
R
V
33
Tema 1: Componentes reutilizables del software. TAD: 04/05
16.- Supongamos la existencia de un tipo Color, que posee las constantes: Blanco, Negro, Azul,
Amarillo, Rojo, Verde, Magenta. En base a este tipo, vamos a implementar unas cuantas
operaciones del famoso juego Mastermind, (parecido al Lingo de la TV). En este juego, un
jugador propone secretamente una secuencia de cinco colores diferentes, mientras que el jugador
contrario debe acertar la combinación correcta. Para ello, lanza a su interlocutor una serie de
tentativas. Éste último, ante cada tentativa indica el número de colores que se hallan en la misma
posición en la jugada tentativa y en la jugada secreta. Asimismo, indica también qué colores se
han acertado, pero cuya posición no coincide plenamente con la de la jugada secreta. P. ej., si la
jugada secreta es
Blanco Negro Rojo Azul Amarillo
Si el otro jugador hace la tentativa:
Blanco+ Verde Rojo+ Negro* Magenta
el resultado será:
2 colores en la misma posición+.
1 color existente no en la misma posición*.
17.- Crear el tipo Polinomio,en el que tanto los coeficientes como los exponentes son números
naturales, o sea, de la forma:
p(x) = a0 + a1 x + a2 x2 + a3 x3 + ... + an xn n, ai ? ù
con las operaciones:
cero: Polinomio
añadir: Polinomio × ù × ù Polinomio
coeficiente : Polinomio × ù ù
evaluar: Polinomio × ù ù
sumar: Polinomio × Polinomio Polinomio
Crear cuantas operaciones auxiliares se necesiten.
34
Tema 1: Componentes reutilizables del software. TAD: 04/05
se pide expresar todas las ecuaciones de equivalencia necesarias para que la especificación sea
correcta y completa con respecto a los números naturales. Resulta muy útil aclarar de antemano
si la familia de generadores es libre o no y, en caso de no serlo, cuáles van a ser exactamente los
términos canónicos.
20.- Vamos a solucionar el problema de las torres de Hanoi. Este problema consiste en tres
varillas verticales clavadas en el suelo, de manera que en una de ellas se han ensartado una serie
de rosquillas todas de diferente tamaño, de manera que las más grandes están abajo, y las de
menor diámetro están arriba.
El problema consiste en pasar todas las rosquillas a la 3ª varilla (usando la segunda como
almacenamiento temporal), de manera que nunca podemos situar una rosquilla grande sobre una
más pequeña.
A este problema, se le pasa un parámetro que es el número de rosquillas que hay que
trasladar, y debe retornar la secuencia de operaciones de traslación necesarias para resolver el
problema. Ej.:
Resolver(3) daría:
Pasar(a, c, Pasar(b, c, Pasar(b, a, Pasar(a, c, Pasar(c, b, Pasar(a, b, Pasar(a,c, Inicio)))))))).
Podemos considerar que estamos creando en TAD Hanoi, de manera que tenemos el
constructor: Solución_Parcial: Varilla x Varilla x Varilla x ù x Hanoi --> Hanoi, donde la
primera varilla representa la inicial, la seguna la intermediaria, y la tercera la final en la que
deben acabar las rosquillas..
Se tendría el generador Inicio, que representaría la posición inicial de las rosquillas, y el
generador Pasar: Varilla × Varilla × Hanoi Hanoi.
Como puede verse, el TAD Hanoi no representa el estado de las varillas y de las
rosquillas, sino la secuencia de movimientos que se han efectuado. Inicio() representa la
situación en la que todavía no se ha movido ninguna rosquilla y, se supone, que todas están en
su posición inicial. Pasar(vi, vf, h) significa que, tras haber realizado todos los movimiento que
35
Tema 1: Componentes reutilizables del software. TAD: 04/05
36