| |
Trabajos
Ejercicio 3: diseño de
un tipo abstracto de datos
Plazo de entrega: 4ª semana
Hoja E3
Le aconsejamos que todos los
problemas antes de ponerse a trabajar.
Introducción
En este ejercicio, diseñará
e implementará un tipo abstracto. Para diseñar
un tipo abstracto hay que determinar primero
su utilidad y
su comportamiento, es decir, hay que escribir una especificación.
Materializar un tipo abstracto supone escoger una representación
y algoritmos, y plasmarlos en código.
El diseño es el interés principal de este
ejercicio, por lo que tiene que dedicarle una cantidad
de tiempo considerable. Para comprobar que su diseño
es factible, utilizará un tipo de datos diseñado
por usted para crear un programa sencillo que encuentre
direcciones mediante un mapa del metro de Boston que
le facilitaremos.
Grafos
El tipo abstracto que diseñará
es un multi-grafo dirigido y etiquetado. Un grafo
es simplemente un conjunto de nodos con aristas entre
ellos. Cada arista conecta un nodo con otro. Puede haber
nodos sin aristas pero no aristas sin nodos. Un nodo
puede estar conectado a él mismo. Puede haber
cero, una o más aristas entre un par de nodos.
Cada arista tiene una etiqueta; aristas distintas pueden
tener la misma etiqueta.
Aquí tiene algunos ejemplos
de aplicaciones con multi-grafos dirigidos y etiquetados.
- Un compilador puede representar
el flujo de control de un programa como un grafo cuyos
nodos son puntos en el programa y cuyas aristas son
enunciados del programa. El grafo se utilizaría
para hacer análisis, como la propagación
del valor de las constantes, y para llevar
a cabo transformaciones, tales como extraer
un enunciado fuera de un bucle.
- Una herramienta para el diseño
de una página web puede representar una página
web como un grafo cuyos nodos son documentos y cuyas
aristas son enlaces. La herramienta puede examinar
el sitio para comprobar la conectividad, encontrar
enlaces que no funcionan, actualizar todos los documentos
cuando uno de ellos se ha movido, y otros temas de
esta índole.
- Una herramienta para el diseño
de un plan de estudios puede utilizar un grafo para
mostrar qué condiciones
previas se establecen para poder cursar una asignatura,
para
encontrar imperfecciones y determinar programas de
estudio viables.
- Un programa que genere direcciones
mientras conduce puede usar un grafo para representar
un callejero y calcular el camino más corto
para encontrar direcciones de un punto a otro.
- Un compilador Java puede
utilizar un grafo para representar dependencias entre
los ficheros del código fuente, y para determinar
un orden de compilación aceptable.
El tipo que usted diseñe
se utilizará para encontrar direcciones en el
metro de Boston, los nodos representarán las
estaciones y las aristas representarán los segmentos
de las vías. Se aconseja que el diseño
y la implementación sean polimórficos,
con lo cual es mejor utilizar un tipo de nodo genérico
y que sea posible utilizar el diseño y la implementación
del grafo en aplicaciones distintas.
La implementación del grafo debe ser eficaz,
es decir, debería funcionar
razonablemente con grafos de tamaño
medio: los que tienen miles (pero no millones) de nodos
y aristas. No presuponga que el grafo
estará disperso (esto es, que contenga
muy pocas aristas en comparación al número
de nodos) o denso (esto es, con la mayoría
de los pares de nodos conectados).
Ideas
Para darle una idea del tipo
de puntos que debería tener en cuenta en su diseño,
he aquí algunas cuestiones que tal vez le interese
examinar. En general, no tienen respuestas sencillas.
Tendrá que sopesarlas con cuidado y pensar detenidamente
en cómo las decisiones que tome interferirán
unas con otras.
- ¿será el grafo
mutable o inmutable? ¿será posible cambiar
la etiqueta de una arista?
- ¿permitirá el
grafo aristas sin etiquetas?
- ¿serán las etiquetas
de la arista cadenas u objetos genéricos? si
son objetos, ¿será correcto utilizar
un nodo o una arista como etiqueta?¿o incluso
un grafo?
- ¿serán los nodos
necesarios únicamente para satisfacer la interfaz
de java.lang.Object? o ¿diseñará
una interfaz Java para los nodos?
- ¿se implementará
el grafo como una clase única o habrá
una interfaz de Java distinta para la especificación
del grafo y una clase para la implementación?
- ¿serán las aristas
objetos por derecho propio? ¿estarán
visibles para un cliente de tipo abstracto?
- ¿será posible
encontrar el sucesor de un nodo únicamente
desde el nodo o
será necesario también el gráfico?
¿puede un nodo pertenecer a grafos múltiples?
- ¿qué clase de
iteradores proporcionará el tipo?
- ¿deberían incluirse
las operaciones de búsqueda
de ruta como
métodos del grafo, o deberían implementarse
en código cliente encima del grafo?
- ¿facilitará el
tipo algunas vistas, como el conjunto de vistas devueltas
por el método entrySet de Java.util.Map?
- ¿implementará
el tipo algunas de las interfaces
de la colección standard de Java?
- ¿utilizará el
tipo algunas de las colecciones
standard de Java en su implementación?
Problema 1: especificación
de un grafo (60 puntos)
Diseñe un tipo de datos
abstracto para un grafo dirigido y etiquetado. Le aconsejamos
que guarde su trabajo de diseño en uno o más
ficheros distintos del propio código fuente e incluya
al menos los siguientes elementos:
- Un breve párrafo que
exprese una visión general de qué
clases o interfaces son visibles a los clientes
del grafo y qué papel desempeñan, así
como un diagrama de dependencia de módulos
que muestre las relaciones entre ellos.
- Un comentario sobre la complejidad
computacional del grafo: diga qué métodos
espera que sean computacionalmente intensivos y denos
una aproximación de su coste asintótico
y de las necesidades de espacio. Por ejemplo, puede
decir (de manera bastante inverosímil) que
addNode será el método más
costoso, que, a medida de que el grafo va creciendo,
su coste debería aumentar como máximo
cuatro veces con el tamaño del grafo y que
la memoria que se necesita para representar el grafo
aumentará de forma exponencial con el número
de aristas.
- Para cada clase o interfaz público:
- un párrafo general
en el que explique de manera resumida qué
objetos están representados por esa clase
o interfaz, y si son o no mutables;
- una especificación
de cada método.
- Un breve listado del diseño
preliminar (no más de media página),
como puntos separados, una variedad de características
alternativas de diseño que usted tuvo en cuenta
pero que al final rechazó, motivando el porqué
del rechazo.
Problema 2: implementación
de un grafo (20 puntos)
Implemente el grafo y ensaye su
implementación con JUnit.
Debería entregar:
- Una breve visión global
de la representación que usted escogió,
con un breve fundamento que defienda la suya en comparación
con otra distinta (aunque plausible).
- Un diagrama de dependencia
de módulos que muestre las clases y las
interfaces que forman parte de su implementación
y la relación que mantienen entre ellas.
- El código fuente
debe estar comentado detalladamente.
- Un
código para
sus casos de prueba.
- Un enunciado que indique
si su código pasa o no todos los tests. Asumiremos
que el código falla si no dice nada.
Para este ejercicio bastará
con un pequeño conjunto de casos de prueba. En
el próximo ejercicio, volverá al código
y lo examinará más detenidamente. Aquí,
la prueba servirá para que se familiarice con
JUnit y para eliminar errores importantes que le impedirían
acabar este ejercicio.
Problema 3: utilización
del grafo (20 puntos)
Cree un programa que genere
direcciones para el sistema del metro de Boston. La idea
es que se introduzcan dos nombres de estaciones en la
línea de comandos y se generen los pasos que deben
adoptarse.
Basta con una sencilla búsqueda a lo ancho. No
se preocupe por controlar los tipos de complicaciones
que un programa real tendría que controlar (como
seleccionar un tren de la línea verde en Park Street
y juntar
todos los pasos en trayectos de líneas individuales).
Hemos escrito algún código que analiza el
fichero que describe el sistema del metro de Boston. Este
fichero bostonmetro.txt
debería introducirse en el programa. El código
en cuestión se encuentra en MetroMapParser.java.
Le aconsejamos que modifique este fichero, insertando
llamadas a los métodos de
grafo
TAD.
Dependiendo de cómo haya diseñado
su TAD, estas llamadas serán diferentes. Por ejemplo,
si ha creado un grafo inmutable, tendría que haber
capturado toda la información del fichero antes
de crear el grafo. Sin embargo, si ha creado un grafo
mutable, debería ser capaz de aumentarlo de forma
incremental a medida de que el fichero es analizado. En
cualquier caso, tendría que leer detenidamente
el código del parser (analizador), y facilitar
una documentación clara en la que explique las
modificaciones que está llevando a cabo.
Debería entregar:
- El código fuente
comentado con detalle.
- Muestras de salida para algunos
casos de prueba.
Código facilitado
Le hemos proporcionado las siguientes
clases en código fuente:
- <MetroMapParser.java>
<BadFileException.java>
Cómo empezar
Asegúrese de que antes de
comenzar a desarrollar la programación ha seguido
las instrucciones concernientes al
directorio de instalación y al manejo
de Java. del menú Herramientas.
Cree un directorio ex3 y copie
en él los ficheros que se le han proporcionado:
mkdir ~/6.170/ex3
cp -p /mit/6.170/www/psets/ex3/ex3-spec/* ~/6.170/ex3
Consejos
Consulte los consejos
y directrices sobre los boletines de ejercicios.
Tal vez el método Integer.parseInt(String
s)le sea
útil
para la conversión
de cadenas a números enteros.
Aunque por lo general no es aconsejable
empezar a escribir código antes de haber realizado
un análisis profundo, a veces tiene sentido trabajar
de forma incremental, intercalando diseño y código.
Una vez que tenga un borrador de su especificación,
quizás desee escribir algún tipo de código
experimental, lo que le serviría para darse cuenta
de la poca dificultad que tiene el implementar los métodos
que ha especificado. Puede que incluso quiera comenzar
por el final, y escribir el código que utilice
el tipo diseñado por usted, de modo que pueda
estar seguro de que los métodos que proporciona
serán suficientes.
Esta estrategia puede salir mal y degenerar en una escritura
sin sentido, dejándole con un montón de
código de baja calidad y una especificación
falta de coherencia. Para evitar esto, tenga presente
dos cosas. Primero, debe estar dispuesto a empezar de
nuevo: un código experimental no es experimental
si no está preparado para desecharlo. Segundo,
cuando empiece a escribir código, debe tener
una idea muy clara de lo que está intentando
implementar. No tiene sentido empezar a codificar una
especificación que es imprecisa y que olvida
detalles cruciales. Esto no significa que su especificación
deba estar terminada y pulida, pero sí que no
es aconsejable comenzar a codificar un método
hasta por lo menos tener escrita su propia especificación.
Tercero, debe escribir la especificación
de un método y no simplemente imaginarla; es
muy fácil engañarse a sí mismo.
Intente escribirla en papel y reflexionar sobre ella
antes de empezar cualquier codificación. Sentarse
delante de un editor, escribir alguna especificación
en forma de comentarios y empezar a codificar en torno
a estos, resulta tentador, aunque suele ser poco eficaz.
Pida ayuda a su monitor de prácticas cuando sea
necesario. Entregue su especificación en las
horas de consulta antes de asistir a las prácticas
y recabe alguna opinión o comentario
sobre su estilo y diseño. Gracias a ello
es probable que ahorre mucho tiempo.
Recuerde el consejo de Hoare de la Clase 1.
Erratas
-
- El fichero bostonmetro.txt
del Ejercicio 3 tiene un error. La línea
41 Copley GreenB
51 34 GreenC 51 34 GreenD 51 34 GreenE 54 34
debería leerse
41 Copley GreenB
51 34 GreenC 51 34 GreenD 51 34 GreenE 53 34
El error acaba la línea de llegada GreenE antes
de tiempo en Prudential.
Le agradecemos a Ben Brown que nos haya avisado de
esto.
Preguntas y Respuestas
Esta sección enumerará
las aclaraciones y respuestas a preguntas frecuentes.
Intentaremos mantenerla lo más actualizada posible,
por lo que le sugerimos que consulte este apartado en
primer lugar (después de haber repasado detenidamente
la hoja) ante cualquier duda que le surja.
Volver arriba
|