Ejerc EDTema 3
Ejerc EDTema 3
Ejerc EDTema 3
Relación de Ejercicios 3
Para realizar estos ejercicios necesitarás crear diferentes ficheros tanto en Haskell como en Java. En
cada caso crea un nuevo fichero (con extensión hs para Haskell y java para Java). Añade al principio de
tu fichero la siguiente cabecera, reemplazando los datos necesarios:
-------------------------------------------------------------------------------
-- Estructuras de Datos. 2º Curso. ETSI Informática. UMA
--
-- (completa y sustituye los siguientes datos)
-- Titulación: Grado en Ingeniería [Informática | del Software | de Computadores].
-- Alumno: APELLIDOS, NOMBRE
-- Fecha de entrega: DIA | MES | AÑO
--
-- Relación de Ejercicios 3. Ejercicios resueltos: ..........
--
-------------------------------------------------------------------------------
import Test.QuickCheck (Cuando se trate de Haskell)
1. Haskell. Escribe una función que permita determinar si una cadena de caracteres está bien
balanceada o equilibrada en lo que se refiere a los paréntesis, corchetes y llaves que contiene. El
resto de caracteres en la cadena no se tendrán en cuenta. Por ejemplo la cadena
"v(hg(jij)hags{ss[dd]dd})" está balanceada correctamente pero no así la cadena
"ff(h([sds)sds]ss)hags".
Para solucionar este problema, se utilizará una pila, de forma que, cada vez que aparezca un
signo de apertura en la cadena dada, éste se introducirá en la pila y, cada vez que aparezca un signo
de cierre en la entrada, se extraerá la cima de la pila, comprobando que éste corresponde con el
signo de apertura correspondiente. Si al finalizar de recorrer la cadena la pila está vacía entonces la
expresión estará equilibrada.
import DataStructures.Stack.LinearStack
2. Java. Escribe un programa Java que tome como argumento una cadena de caracteres y que escriba
por pantalla si ésta está equilibrada (usa el algoritmo descrito en el problema anterior)
package wellBalanced;
import dataStructures.stack.*;
1
Relación de Ejercicios 3 Estructuras de Datos. Ingeniería [Informática | del Software | de Computadores]
3. Java. Teniendo en cuenta la función postFix que aparece en las transparencias complementarias de
clase en Haskell, escribe un programa en Java que implemente la misma funcionalidad. Para ello,
crear las clases Item, Data, Operation, Add, Dif y Mul.
La clase Item será abstracta y definirá los siguientes métodos:
La clase Data será subclase de Item y permitirá almacenar un valor entero. Su constructor tomará
dicho valor. Redefinirá los métodos isData (que devolverá true) y getValue (que devolverá el
entero que almacena)
La clase Operation será también subclase de Item y abstracta, y debe redefinir el método
isOperation (que devolverá true).
Las clases Add, Dif y Mul serán subclases de Operation y deben redefinir el método
evaluate(int a1, int a2) de manera que en la primera clase se devuelva la suma de a1 y a2, en
la segunda la diferencia y en la tercera el producto.
Escribe un programa PostFix que incluya una función de clase, static int evaluate(Item[]
exprList) que toma un array de Items que representa una expresión posfija y la evalúa. Una
expresión ejemplo puede ser:
Item [] sample = {
new Data(5),
new Data(6),
new Data(2),
new Dif(),
new Data(3),
2
Relación de Ejercicios 3 Estructuras de Datos. Ingeniería [Informática | del Software | de Computadores]
new Mul(),
new Add() };
4. Haskell. Consideremos el siguiente módulo para representar expresiones como listas de operandos
o items:
module Expression (
Item (..)
, Expression
, value
, showExpr
, sample1, sample2
) where
data Item = Add | Dif | Mul | Value Integer | LeftP | RightP deriving Show
Donde Add representa la suma, Dif la resta, Mul el producto, Value n el entero n, LeftP el
paréntesis izquierdo y RightP el paréntesis derecho.
sample :: Expression
sample = [LeftP, LeftP, Value 4, Mul, Value 5,RightP, Dif, Value 6, RightP]
-- sample se corresponde con la expresión ( (4 * 5) - 6)
El algoritmo para la evaluación utilizará dos pilas: en una se guardan los datos enteros que van
apareciendo en la lista expresión y en la otra las operaciones. El algoritmo explora la lista. Si en la
cabeza aparece un paréntesis de apertura (LeftP) se ignora y se explora el resto. Si en la cabeza
aparece un paréntesis de cierre (RightP), se extrae el último operador de la pila de operadores y los
dos últimos datos de la pila de datos, se aplica el operador a estos dos datos (el primero sacado es
el segundo argumento) y el resultado se vuelve a colocar en la pila de datos; a continuación, se
explora el resto de la lista de ítems. Al acabar la lista de ítems puede ocurrir: (a) la pila de datos
contiene un único dato y la pila de operadores está vacía, en ese caso la expresión está bien
construida (parentizada) y el dato que queda es el resultado; (b) en caso contrario la expresión
original no está bien construida, por lo que no es posible calcular su valor.
package infix;
import dataStructures.stack.Stack;
import dataStructures.stack.ListStack;
3
Relación de Ejercicios 3 Estructuras de Datos. Ingeniería [Informática | del Software | de Computadores]
6. Java. Para cualquier implementación de la interface Stack, define el constructor de copia. Éste es un
constructor que toma como argumento otro Stack y copia todos sus valores al stack que se está
creando.
7. Java. Implementa la interface Queue usando dos pilas. La clase se llamará TwoStacksQueue y se
incluirá en el paquete dataStructures.queue. Para ello, observa la implementación TwoListsQueue
en las transparencias complementarias de clase en Haskell. Las listas allí utilizadas actúan como
pilas.
El comportamiento de una cola de prioridades es el mismo que el de una cola, con una sola
diferencia: cuando se encola un elemento se coloca detrás de los que son menores o iguales a él, y
delante de los que son mayores; así, la operación first devolverá el menor elemento de la cola
(que no necesariamente debe ser el primero introducido). Implementa el tipo abstracto QueueP
utilizando una estructura lineal enlazada similar a LinearQueue.
4
Relación de Ejercicios 3 Estructuras de Datos. Ingeniería [Informática | del Software | de Computadores]
11. Haskell. Un saco (o multiconjunto) es parecido a un conjunto salvo que un elemento puede estar
incluido varias veces. Por ejemplo, {‘b’, ‘a’, ‘d’, ‘d’, ‘a’, ‘c’ , b’, ‘a’} es un saco que
incluye tres ocurrencias del carácter ‘a’ , dos ocurrencias del ‘b’, una del ‘c’ y dos del ‘d’.
a) Implementa sacos en Haskell usando el siguiente tipo de datos:
data Bag a = Empty | Node a Int (Bag a)
de forma que en cada nodo aparece, además del saco restante, cada elemento junto con
su contador de ocurrencias, o sea, el número de veces que aparece. Para agilizar las
operaciones de inserción y borrado en un Bag, interesa que los nodos estén ordenados
atendiendo al orden de los elementos a incluir. Además, no deben aparecer elementos con
contador nulo (o negativo). Por ejemplo, el saco anterior se representa por:
Node ‘a’ 3 (Node ‘b’ 2 (Node ‘c’ 1 (Node ‘d’ 2 Empty)))
insert :: (Ord a) => a -> Bag a -> Bag a --Inserta una nueva ocurrencia
delete :: (Ord a) => a -> Bag a -> Bag a -- Borra una ocurrencia de un
-- elemento de un saco. Devuelve el mismo saco si el elemento no estaba incluido
b) Proporciona una especificación de Bag definiendo sus axiomas para las diferentes
operaciones y comprueba la implementación realizada con QuickCheck. Para ello,
incluye en el módulo la siguiente instancia para generar sacos aleatorios:
c) Añade al módulo las siguientes funciones para manipular sacos: unión, intersección,
diferencia y una función que determine si un saco está contenido en otro. Estas
5
Relación de Ejercicios 3 Estructuras de Datos. Ingeniería [Informática | del Software | de Computadores]
funciones son semejantes a las de los conjuntos pero teniendo en cuenta las
ocurrencias de cada elemento.
b) Define la clase LinkedBag que implementa la interfaz Bag manteniendo los elementos en
una lista enlazada de nodos de manera que cada nodo contiene un elemento y su número
de apariciones. Considera los nodos ordenados según sus elementos, que deben ser
ordenables. Proporciona un iterador para esta estructura de manera que un elemento con
n ocurrencias sea devuelto n veces por el iterador.
c) Define una clase ArrayBag que proporcione una implementación alternativa de la interfaz
Bag pero usando un array en lugar de una lista enlazada. Cada celda del array debe
mantener un elemento y su correspondiente número de apariciones.
13. Haskell. Consideremos la siguiente función de plegado para el tipo Stack vista en clase:
listToStack = foldr … …
stackToList = foldrStack … …
Por ejemplo:
*FoldStackQueue> listToStack [1..10]
LinearStack(1,2,3,4,5,6,7,8,9,10)
*FoldStackQueue> stackToList (listToStack [1..10])
[1,2,3,4,5,6,7,8,9,10]
6
Relación de Ejercicios 3 Estructuras de Datos. Ingeniería [Informática | del Software | de Computadores]
mapStack f = foldrStack … …
sizeStack = foldrStack … …
200
14. Haskell. Supongamos que añadimos al módulo del ejercicio anterior una función de plegado para el
tipo Queue:
foldrQueue f z q
| S.isEmpty q = z
Por ejemplo:
*FoldStackQueue> listToQueue [1..10]
LinearQueue(10,9,8,7,6,5,4,3,2,1)
[10,9,8,7,6,5,4,3,2,1]
LinearStack(10,9,8,7,6,5,4,3,2,1)
7
Relación de Ejercicios 3 Estructuras de Datos. Ingeniería [Informática | del Software | de Computadores]
Ejercicios Complementarios
Ayuda: Usa inducción sobre n, los axiomas de Stack y la propiedad (a) del apartado anterior.
c) Una pila está bien definida si: (1) o bien es empty; (2) o bien es de la forma push x s, siendo s
una pila bien definida; (3) o bien es de la forma pop s, donde s es una pila bien definida en la
cual aparecen menos operaciones pop que operaciones push, y éstas en número finito. Prueba
que cada pila bien definida se puede escribir de forma única como una secuencia
push x (push x (… (push x empty)…))
1 2 n
16. Haskell. Consideremos el tipo abstracto de datos Queue(colas FIFO sobre un tipo genérico a), con
a
las operaciones y axiomas habituales vistas en clase. Demuestra en el mismo orden en que
aparecen las siguientes propiedades (utilizamos enq y deq en lugar enqueue y dequeue para
abreviar):
a) Para cualquier n>0, si q = enq x (enq x (…
1 2 (enq x empty )…)),
n
c) Una cola está bien definida si: (1) o bien es empty; (2) o bien es de la forma enq x q,
siendo q una cola bien definida; (3) o bien es de la forma deq q, donde q es una cola bien
definida en la cual aparecen menos operaciones deq que operaciones enq, y éstas en número
finito. Prueba que toda cola bien definida se puede escribir de forma única como una
secuencia finita de operaciones enq:
8
Relación de Ejercicios 3 Estructuras de Datos. Ingeniería [Informática | del Software | de Computadores]
17. Haskell. Prueba que la implementación lineal LinearStack satisface los axiomas Ax1-Ax4.
18. Haskell. Prueba que la implementación lineal LinearQueue satisface los axiomas Ax1-Ax6.
y un único axioma
a) Demuestra que todo entero “finito” (secuencia finita bien construida) puede escribirse de
forma única en una de las formas siguientes:
cero suc . cero pre. cero
b) Consideremos que añadimos la operación suma con la signatura:
y los axiomas
(Ax2) cero + x = x
c) Demuestra que entonces se sigue verificando lo anterior, es decir, todo entero “finito”
(secuencia finita bien construida mezclando cero, suc, pre y +) puede escribirse de forma única
en una de las formas siguientes:
cero suc . cero pre. cero
20. Haskell. Consideremos el siguiente módulo para describir el TADs de los enteros:
module Entero Entero, cero, isCero, isPos, isNeg, suc, pre) where
cero :: Entero
cero = Cero
9
Relación de Ejercicios 3 Estructuras de Datos. Ingeniería [Informática | del Software | de Computadores]
suc (Pre n) = n
suc n = Suc n
Observa las ecuaciones de las funciones suc y pre (no las confundas con los constructores Suc y
Pre). Podríamos haber escrito ecuaciones sencillas para cada función:
suc = Suc
pre = Pre
pero nuestra intención es que cada llamada a las funciones suc y pre conserve la forma normalizada
de su argumento; es decir, cada entero resultante se escriba de una de las siguientes formas:
Cero Pre (Pre (...(Pre Cero)...) Suc (Suc (...(Suc Cero)...))
Esta propiedad la llamaremos invariante de la representación (IR).
a) Demuestra que la forma normal Haskell de cualquier secuencia finita de operaciones pre y suc que
empiece con cero está siempre normalizada. Es decir, que una tal secuencia no puede evaluarse a
una forma normal tal como Suc (Pre Cero). Para comprobarlo puedes generar la función show en
forma automática estructural con la declaración de instancia siguiente:
data Entero = Cero | Suc Entero | Pre Entero deriving (Eq, Show)
d) Consideremos que deseamos que la suma +, diferencia – y producto * satisfagan algunos axiomas
que debes completar, tales como
(Ax+1) x + cero == x
(Ax+2) x + suc y = suc ( x + y)
(Ax+3) x + pre y = pre (x + y)
(Ax*1) x * cero = cero
10
Relación de Ejercicios 3 Estructuras de Datos. Ingeniería [Informática | del Software | de Computadores]
NOTA: al escribir las ecuaciones debes tener en cuenta los axiomas y además que los operadores deben
conservar el Invariante de la Representación IR: los resultados deben satisfacer IR siempre que lo satisfagan los
argumentos.
e) Define ahora otras propiedades de los operadores aritméticos y compruébalos con quickCheck;
algunas de éstas son:
p_neutro n = cero + n == n
p_commu n m = n + m == m + n
p_csigno n m = n - m == - (m - n)
11