Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las...

53
Capítulo 4 Listas E n este capítulo se discute la estructura de datos lista como ejemplo particular de estructura dinámica para el almacena- miento y gestión de datos. El principal objetivo que se pretende alcanzar consiste en ofrecer una visión general de las listas, haciendo especial hincapié en la perspectiva práctica, es decir, en una posible implementación de la estructura de datos y en la discusión en profun- didad de problemas reales, junto con posibles soluciones. 4.1. Objetivos Las listas representan una de las estructuras de datos más utiliza- das por su versatilidad y flexibilidad. Tal es su relevancia, que las listas forman parte de los propios lenguajes de programación, como el caso de Java, o están integradas en bibliotecas estándar como es el caso de C++ y la biblioteca STL (Standard Template Library). En este capítulo se pretende dar una visión práctica de la aplica- ción de listas como arma fundamental para la resolución de problemas reales planteados en la asignatura de Estructuras de Datos y de la In- formación. De este modo, el lector podrá asimilar y reflexionar sobre el uso de las listas en determinados problemas utilizando el lenguaje Java, siendo capaz de distinguir cuándo dicho uso resulta recomen- dable o cuándo no es así y, por lo tanto, es más adecuado utilizar otro tipo de estructuras de datos. Figura 4.1: Las listas se de- finen como contenedores de secuencia, ya que existe una relación de orden entre los contenidos que almacenan. El resto del capítulo se estructura de la siguiente forma. La sec- ción 4.2 describe los conceptos fundamentales de las listas, diferen- ciando entre los enfoques recursivo e iterativo. La sección 4.3 intro- duce el uso de las listas como solución a problemas reales. A conti- nuación, la sección 4.4 discute en profundidad una implementación de listas basada en un esquema recursivo. La sección 4.5 muestra al- gunos problemas sencillos que el lector puede usar como referencia de utilización de listas. La sección 4.6 contiene una serie de cuestiona- rios de autoevaluación que el lector puede utilizar para comprobar los conocimientos adquiridos. La sección 4.7 plantea diversos problemas reales junto con una posible solución detallada y justificada. 47

Transcript of Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las...

Page 1: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

Capítulo4Listas

E n este capítulo se discute la estructura de datos lista comoejemplo particular de estructura dinámica para el almacena-miento y gestión de datos. El principal objetivo que se pretende

alcanzar consiste en ofrecer una visión general de las listas, haciendoespecial hincapié en la perspectiva práctica, es decir, en una posibleimplementación de la estructura de datos y en la discusión en profun-didad de problemas reales, junto con posibles soluciones.

4.1. Objetivos

Las listas representan una de las estructuras de datos más utiliza-das por su versatilidad y flexibilidad. Tal es su relevancia, que laslistas forman parte de los propios lenguajes de programación, como elcaso de Java, o están integradas en bibliotecas estándar como es elcaso de C++ y la biblioteca STL (Standard Template Library).

En este capítulo se pretende dar una visión práctica de la aplica-ción de listas como arma fundamental para la resolución de problemasreales planteados en la asignatura de Estructuras de Datos y de la In-formación. De este modo, el lector podrá asimilar y reflexionar sobreel uso de las listas en determinados problemas utilizando el lenguajeJava, siendo capaz de distinguir cuándo dicho uso resulta recomen-dable o cuándo no es así y, por lo tanto, es más adecuado utilizar otrotipo de estructuras de datos.

Figura 4.1: Las listas se de-finen como contenedores desecuencia, ya que existe unarelación de orden entre loscontenidos que almacenan.

El resto del capítulo se estructura de la siguiente forma. La sec-ción 4.2 describe los conceptos fundamentales de las listas, diferen-ciando entre los enfoques recursivo e iterativo. La sección 4.3 intro-duce el uso de las listas como solución a problemas reales. A conti-nuación, la sección 4.4 discute en profundidad una implementaciónde listas basada en un esquema recursivo. La sección 4.5 muestra al-gunos problemas sencillos que el lector puede usar como referencia deutilización de listas. La sección 4.6 contiene una serie de cuestiona-rios de autoevaluación que el lector puede utilizar para comprobar losconocimientos adquiridos. La sección 4.7 plantea diversos problemasreales junto con una posible solución detallada y justificada.

47

Page 2: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

[48] CAPÍTULO 4. LISTAS

4.2. Conceptos Fundamentales

Una lista es una estructura de datos que se caracteriza por ser unasecuencia dinámica de 0 ó más elementos de un tipo determinado,en la que el orden de los elementos que forman parte de la lista esrelevante. De este modo, la lista [’a’, ’b’, ’c’] difiere de la lista [’b’, ’a’, ’c’],ya que aunque está formada por los mismos elementos, éstos ocupanposiciones distintas. Por lo tanto, los elementos de una lista estánordenados linealmente en función de la posición que ocupan dentrode la lista. La figura 4.2 representa de manera gráfica una lista quealmacena caracteres.

a b c d e

0 1 2 3 4

z

n - 1

...

Figura 4.2: Representación gráfica de una lista.

La concepción de una lista se puede estudiar desde distintos en-foques, destacando especialmente, por una parte, el planteamientorecursivo y, por otra, el planteamiento iterativo o no recursivo. Enfunción del enfoque elegido, tanto la especificación como la implemen-tación de la estructura de datos lista variarán considerablemente.

Según el enfoque recursivo, una lista se define como una listavacía, es decir, que no contiene ningún elemento, o como un par orde-nado <cabeza, resto>, donde cabeza es un elemento de un tipo deter-minado y resto representa otra lista (definida, así mismo, de manerarecursiva).

Según el enfoque no recursivo o iterativo, el contenido de unalista viene determinado por la posición de los elementos que ocupandentro de la misma, siendo posible iterar sobre sus elementos y ope-rar directamente sobre ellos indexándolos en la propia lista. Por lotanto, en este enfoque una lista está determinada por la secuencia deelementos que la componen.

a b c d

Nodocabecera

b) Enfoque no recursivo

c da bCabeza Resto

a) Enfoque recursivo

Figura 4.3: Representación gráfica de una lista que sigue un enfoque recursivo y norecursivo, respectivamente.

Page 3: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

4.2. Conceptos Fundamentales [49]

A continuación se expone la especificación algebraica de las ope-raciones fundamentales de la estructura de datos lista, siguiendo unplanteamiento recursivo. En primer lugar, se plantea la especificaciónsintáctica para denotar la signatura de dichas operaciones.

Tipos: Lista, Elemento, Lógico

Operaciones:

Tipos de operaciones

La especificación plantea-da contempla tres tipos deoperaciones: generadoras (g),que sirven para crear cual-quier valor del tipo de datos,de acceso (a), que sirven pa-ra consultar el contenido oel estado del tipo de datos ymodificadores (m), que sirvenpara alterar el contenido deltipo de datos.

g) ListaVacía: −→ Lista

g) AñadirPpio: Elemento x Lista −→ Lista

a) EsVacía: Lista −→ Lógico

a) Primero: Lista −→ Elemento

m) Resto: Lista −→ Lista

m) Borrar: Lista x Elemento −→ Lista

A partir de la especificación sintáctica previamente planteada, elejemplo concreto de lista de la figura 4.2 se generaría aplicando lassiguientes operaciones:

Apio(a, Apio(b, Apio(c, Apio(d, Apio(e, Apio(..., Apio(z, ListaVacía)))))))

A continuación, se plantea la especificación semántica de listas,cuyo objetivo es describir el comportamiento interno de los axiomasde acceso y de modificación anteriormente presentados. Dicha especi-ficación es independiente del lenguaje de programación que se utilicepara llevar a cabo la implementación de la estructura de datos lista.

∀l ∈ Lista,∀e ∈ Elemento,

a) Axiomas de acceso

EsVacía (ListaVacía) = Verdadero

EsVacía (AñadirPpio (e, l)) = Falso

Primero (ListaVacía) = Error

Primero (AñadirPpio (e, l)) = e

b) Axiomas de modificación

Resto (ListaVacía) = Error

Resto (AñadirPpio (e, l)) = l

Borrar (ListaVacía, e) = Error o ListaVacía

Borrar (AñadirPpio (e, l), f) =

Si e = f⇒ Borrar (l, f)

Si no,⇒ AñadirPpio (e, Borrar(l, f))

Page 4: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

[50] CAPÍTULO 4. LISTAS

4.3. Usos y Aplicaciones

Las listas son estructuras de datos muy generales que se puedenutilizar en un amplio dominio de aplicaciones. De hecho, las listasse pueden utilizar para implementar otras estructuras de datos discu-tidas anteriormente, como por ejemplo las pilas o las colas.

Así mismo, resulta especialmente relevante destacar la diferenciade un array con respecto a una lista. En este contexto, la principalventaja de una lista es que posibilita que el orden en el que se enlazanlos elementos que la componen es diferente al orden de almacena-miento, lo cual implica que el orden de recorrido sea distinto al ordende almacenamiento.

Desde un punto de vista general, las listas se pueden utilizar enaquellos problemas donde resulta necesario gestionar una coleccióndinámica de elementos que varían a lo largo del tiempo, es decir,una colección dinámica en la que se efectuan tanto inserciones comomodificaciones de datos. Sin embargo, en determinadas situacionespuede no ser recomendable utilizar listas. Por ejemplo, en un proble-ma donde sea necesario gestionar una estructura de datos del tipodiccionario, en la que se indexan valores por una clave, es recomen-dable utilizar estructuras del tipo árbol, como por ejemplo la que sediscute en el siguiente capítulo.

Ante determinados problemas, se puede plantear la posibilidad deutilizar un array o vector en lugar de una lista. Ambas solucionestienen sus ventajas y desventajas derivadas de la naturaleza estáticay dinámica de las mismas, respectivamente. Por ejemplo, una ventajadel array respecto a la lista es que posibilita el acceso directo a unelemento, mientras que en la lista es necesario realizar un recorridode la misma para llegar a una determinada posición. Sin embargo,los arrays son estructuras estáticas, es decir, inicialmente hay quereservar una cantidad de memoria que puede que no sea adecuadapara un problema particular, bien por defecto o por exceso. Además,redimensionar un array suele resultar excesivamente costoso debidoa la necesidad de mover los datos que almacena de manera previa adicho redimensionamiento.

Algunas de las desventajas asociadas a la estructura de lista sepueden solventar mediante información redundante, como por ejem-plo la inclusión de enlaces adicionales entre los nodos que formanparte de una lista. Las listas doblemente enlazadas o las listas circu-lares son algunos casos.

Page 5: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

4.4. Implementación [51]

4.4. Implementación

En esta sección se discute una posible implementación, utilizandoel lenguaje de programación Java, de la estructura de datos lista ba-sada en un esquema recursivo, tal y como se discutió en la sección4.2.

4.4.1. La interfaz Lista

Antes de pasar a los aspectos específicos de la implementación, eldiseño planteado se basa en especificar una interfaz Lista, la cual utili-zará la implementación de lista basada en un esquema recursivo. Esteenfoque garantiza la escalabilidad a la hora de implementar listas uti-lizando otras alternativas que se discutirán brevemente más adelante,como por ejemplo las listas con nodos enlazados. De este modo, to-das las implementaciones de listas que implementen la interfaz que sepresenta a continuación mantienen una única representación a nivelfuncional, permitiendo que el usuario elija cualquier implementaciónque use dicha interfaz.

Listado 4.1: Interfaz de la estructura de datos Lista.

1 package listas;23 public interface Lista<E> {45 public E elemento (int pos) throws IndiceFueraDeRangoException;6 public void eliminarN (int pos) throws

IndiceFueraDeRangoException;7 public boolean esVacia ();8 public void insertar (E e) throws NullPointerException;9 public void insertarFinal (E e) throws NullPointerException;10 public void insertarN (int pos, E e) throws

IndiceFueraDeRangoException, NullPointerException;11 public void modificarN (int pos, E e) throws

IndiceFueraDeRangoException, NullPointerException;12 public boolean pertenece (E e);13 public E primero () throws ListaVaciaException;14 public int posicion (E e) throws NullPointerException;15 public int tamanyo ();1617 public String toString ();18 public boolean equals (Object obj);1920 }

Como se puede apreciar en el anterior listado de código, la interfazLista está parametrizada para manejar cualquier tipo de datos, ya seaprimitivo o no. Para ello, la interfaz se parametriza con un tipo genéricoE, tal y como se especifica en la línea

�� ��3 de dicho listado.

En términos generales, la posibilidad de manejar cualquier tipode dato en una estructura de datos es una buena decisión dediseño.

A grandes rasgos, la interfaz Lista define dos tipos de operaciones.Por un lado, operaciones de acceso que permiten consultar el es-tado de una lista, como por ejemplo la operación elemento. Por otro

Page 6: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

[52] CAPÍTULO 4. LISTAS

lado, operaciones de modificación que permiten alterar el estado deuna lista, como por ejemplo la operación eliminarN. A continuaciónse describe brevemente la funcionalidad asociada a cada una de lasoperaciones de la interfaz Lista.

Recuerde que hasta el momento sólo se está discutiendo la fun-cionalidad de la interfaz Lista. Dicha interfaz será posterior-mente implementada por algún tipo particular de lista, comopor ejemplo la que se basa en una implementación recursiva.

La operación elemento (ver línea�� ��5 ) permite acceder al elemento

de la lista que ocupa la posición especificada por el parámetro pos. Laoperación arroja una excepción del tipo IndiceFueraDeRangoException,diseñada para contemplar el caso particular en el que el valor de dichoparámetro no está dentro del rango de elementos de la lista.

Listado 4.2: Excepción IndiceFueraDeRangoException.

1 package listas;23 public class IndiceFueraDeRangoException extends Exception { }

La operación eliminaN (ver línea�� ��6 ) permite eliminar de la lista el

elemento que ocupa la posición especificada por el parámetro pos. Aligual que en el caso de la operación anterior, la operación contemplala posibilidad de que el valor de dicho parámetro no está dentro delrango de elementos de la lista.

La operación esVacia (ver línea�� ��7 ) permite comprobar si la lista

tiene elementos o no, devolviendo un valor lógico.

La operación insertar (ver línea�� ��8 ) permite añadir al principio de

la lista el elemento especificado por el parámetro e. Como se puedeapreciar, dicho parámetro es del tipo génerico E, posibilitando el ma-nejo de listas de cualquier tipo de datos. La operación insertar arrojauna excepción del tipo NullPointerException (propia del lenguaje Java),contemplada para el caso en el que el parámetro de entrada, es decir,el elemento a insertar en la lista, sea una referencia a null. Las opera-ciones insertarN (ver línea

�� ��9 ) e insertarN (ver línea�� ��10 ) representan el

caso específico y general, respectivamente, de la operación insertar.

En general, resulta aconsejable utilizar los elementos propiosde un lenguaje para contemplar situaciones específicas, comopor ejemplo el uso de la excepción NullPointerException en lasoperaciones de inserción de datos en una lista.

La operación modificaN (ver línea�� ��11 ) permite modificar el valor

del elemento que ocupa la posición pos en la lista, sobreescribiéndolocon el valor asociado al parámetro e. En la implementación de estaoperación se espera que se controlen los casos específicos de valornulo e índice fuera de rango.

La operación pertenece (ver línea�� ��12 ) permite conocer si el elemento

e, especificado como parámetro de entrada, se encuentra contenido enla lista o no. Dicha operación devuelve un valor lógico.

Page 7: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

4.4. Implementación [53]

La operación primero devuelve el contenido, si existe, del primerelemento de la lista. Por lo tanto, el tipo del valor de retorno ha de serde tipo genérico E. Esta operación contempla el caso excepcional en elque la lista esté vacía. Ante la ocurrencia de dicho caso, la implemen-tación que haga uso de esta interfaz debería lanzar una excepción deltipo ListaVaciaException.

La operación posicion devuelve la posición de la primera ocurrenciadel elemento e, pasado como parámetro, en la lista, siempre y cuandoexista. Por lo tanto, el tipo de retorno de esta operación es del tipoint. La operación posicion contempla el caso excepcional en el que elelemento pasado como parámetro sea una referencia a null mediantela excepción NullPointerException.

La operación tamanyo devuelve la longitud de la lista. En el casoparticular en el que la lista esté vacía, entonces la implementación dedicha operación devolvería 0.

Finalmente, las operaciones toString y equals se especifican paraobtener una representación textual de la lista y para comparar la listacon otro elemento pasado como parámetro, respectivamente.

4.4.2. Implementación con recursividad

En este apartado se discute una implementación de listas hacien-do uso de un esquema recursivo y utilizando la interfaz definida en lasección anterior. El hecho de implementar dicha interfaz implica quela implementación de listas, a partir de ahora referida como Lista Re-cursiva, ha de garantizar la implementación de todos y cada uno delas operaciones definidas en la interfaz. En otras palabras, la imple-mentación de la interfaz Lista representa un contrato funcional que seimpone a las implementaciones que la utilicen.

Tal y como se introdujo en la sección 4.2 (ver figura 4.3), una listabasada en un esquema recursivo se puede definir como un par orde-nado <cabeza, resto>, donde cabeza representa el primer elementode la lista y resto es a su vez otra lista que puede contener más ele-mentos.

En la implementación que se plantea en este apartado se sigueeste esquema, utilizando sendas variables de clase para representara dichos elementos. En el siguiente listado de código se muestra ladefinición de la clase ListaRecursiva y de sus variables.

Listado 4.3: Clase ListaRecursiva. Definición.

1 package listas;23 public class ListaRecursiva<E> implements Lista<E>, Cloneable {45 protected E _primero;6 protected ListaRecursiva<E> _resto;78 // ...9 // ...1011 }

Como se puede apreciar en la línea�� ��3 del listado, la clase ListaRe-

cursiva implementa la interfaz Lista, así como la interfaz Cloneable. Laimplementación de la clase se realiza posibilitando el tratamiento detipos genéricos (tipo E). Así mismo, la implementación de la interfaz

Page 8: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

[54] CAPÍTULO 4. LISTAS

de clonación permite realizar copias campo a campo de las instanciasde la clase.

Por otra parte, ListaRecursiva consta de dos variables de clase,ambas con una visibilidad protegida, denominadas _primero, de tipogenérico E, y _resto de tipo ListaRecursiva<E>. La primera de ellasrepresenta la cabeza de la lista o el primer elemento de la misma,mientras que la segunda representa el resto del contenido de la lista.

Es una buena práctica de programación seguir un esquemaadecuado de nombrado de variables de clase (y en general decualquier variable). Se recomienda utilizar nombres de varia-bles que permitan conocer, desde un punto de vista semántico,qué representan y usar una nomenclatura consistente, comopor ejemplo nombrar las variables de clase con mayúscula ocomenzando con un subrayado.

En el listado que se expone a continuación se incluye la implemen-tación de los métodos constructores de la clase ListaRecursiva. Elprimero de ellos no acepta parámetros de entrada y, por lo tanto, sirvepara instanciar listas vacías. El segundo de ellos permite inicializartodo el contenido de una lista, es decir, tanto el primer elemento comoel resto. Finalmente, el tercer constructor permite instanciar listas conun único elemento.

Listado 4.4: Clase ListaRecursiva. Constructores.

1 package listas;23 public class ListaRecursiva<E> implements Lista<E>, Cloneable {45 public ListaRecursiva () {6 _primero = null;7 _resto = null;8 }9

10 public ListaRecursiva (E primero, ListaRecursiva<E> resto) {11 _primero = primero;12 _resto = resto;13 }1415 public ListaRecursiva (E primero) {16 this(primero, new ListaRecursiva<E>());17 }1819 // ...20 // ...2122 }

A continuación se discutirán uno a uno los métodos que conformanla implementación de la clase ListaRecursiva.

En primer lugar, el siguiente listado muestra la implementación delmétodo insertar, cuya funcionalidad consiste en añadir un elementoal principio de la lista.

Page 9: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

4.4. Implementación [55]

Listado 4.5: Clase ListaRecursiva. Método insertar.

1 public void insertar (E e) throws NullPointerException {2 if (e == null)3 throw new NullPointerException();45 if (esVacia())6 _resto = new ListaRecursiva<E>();7 else8 _resto = new ListaRecursiva<E>(_primero, _resto);910 _primero = e;1112 }

La operación insertar comprueba en primer lugar si el elementopasado como parámetro es una refencia a un valor nulo. En tal caso,lanza una excepción del tipo NullPointerException (ver líneas

�� ��2-3 ) yfinaliza la ejecución del método. En la implementación discutida enesta sección no se permite la inserción de elementos nulos en la lista.A continuación, se distinguen dos casos particulares:

1. Si la lista es vacía, entonces a la variable _resto se la asigna unanueva lista vacía (línea

�� ��6 ) y a _primero se la asigna como referen-cia el elemento pasado como parámetro e (línea

�� ��10 ).

2. Si la lista no es vacía, entonces _resto se actualiza para que ten-ga como primer elemento el que lo era hasta entonces, es decir,_primero, y como resto lo que hasta ahora representaba el restode la lista, es decir, _resto (línea

�� ��8 ). Por otra parte, _primero seactualiza con el elemento pasado como parámetro e (línea

�� ��10 ).

Con el objetivo de mantener un esquema flexible relativo a la in-serción de elementos en la lista, en el siguiente listado de código seplantean dos métodos de inserción: i) al final de la lista y ii) en cual-quier posición de la misma.

Listado 4.6: Clase ListaRecursiva. Otros métodos de inserción.

1 public void insertarFinal (E e) throws NullPointerException {2 if (e == null)3 throw new NullPointerException();45 if (esVacia())6 insertar(e);7 else8 _resto.insertarFinal(e);910 }1112 public void insertarN (int pos, E e) throws

IndiceFueraDeRangoException, NullPointerException {13 if (e == null)14 throw new NullPointerException();1516 if ((pos <= 0) || (pos - 1 > tamanyo()))17 throw new IndiceFueraDeRangoException();18 else19 if (pos == 1)20 insertar(e);21 else22 _resto.insertarN(pos - 1, e);23 }

El método insertarFinal sigue la misma filosofía que insertar res-pecto al tratamiento de valores nulos. Sin embargo, la implementación

Page 10: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

[56] CAPÍTULO 4. LISTAS

varía debido a que ahora los elementos se insertar por el otro extremode la lista, es decir, por el final. En este contexto, se distinguen doscasos:

1. Si la lista es vacía, entonces insertar al final será exactamentelo mismo que insertar al principio. Por lo tanto, en este caso sedelega directamente en el método insertar (ver líneas

�� ��5-6 ).

2. Si la lista no es vacía, entonces se inserta recursivamente al finalde la lista, utilizando para ello la variable de clase _resto (líneas�� ��7-8 ).

Inserción en listas

Existen planteamientos al-ternativos que permiten re-ducir el tiempo empleado enla inserción de elementos,por ejemplo al final de lis-ta, incrementando el coste enmemoria de dichas plantea-mientos. Un ejemplo particu-lar son las listas con nodoscabecera y final.

Mientras que el método insertar tiene una complejidad constanteO(1) debido a que es posible acceder directamente al primer elemen-to de la lista, el método insertarFinal tienen una complejidad linealO(n) (respecto al número de elementos de la lista). En otras palabras,insertar al final implica recorrer todos los elementos de la lista. Estehecho es consecuencia directa del enfoque recursivo planteado para laimplementación de ListaRecursiva, la cual está compuesta por un parordenado <cabeza, resto>, lo que implica un acceso a la lista desde elprimer elemento.

Por otra parte, el método insertarN es una generalización de lainserción en listas y posibilida añadir nuevos elementos en cualquierposición de la lista (siempre y cuando no sobrepase los límites de lamisma). En este caso, dicho método necesita tanto el elemento a in-sertar como la posición en la que el usuario desea insertarlo (línea�� ��12 ). Precisamente, debido al manejo del parámetro de entrada pos, esnecesario controlar que dicho parámetro tiene un valor que no excedalos límites de inserción de la lista. En otro caso, el método lanzará unaexcepción del tipo IndiceFueraDeRangoException (líneas

�� ��16-17 ).

Si tanto el elemento a insertar no es una referencia nula y la posi-ción es válida, nuevamente se distinguen dos casos:

1. Si el valor de pos es 1, entonces insertarN es exactamente igualque insertar al principio (líneas

�� ��19-20 ). Es decir, representaría elcaso base de la recursividad en el que se inserta al principio.

2. Si el valor de pos es mayor que 1 (y no excede el límite superior dela lista), entonces se realiza una llamada recursiva sobre _restocon insertarN, pero decrementando el valor de la posición en unaunidad (líneas

�� ��21-22 ).

A continuación se muestra el código del método elemento, que dadauna determinada posición dentro de la lista devuelve el contenido delelemento que la ocupa.

Listado 4.7: Clase ListaRecursiva. Método de acceso a un elemento.

1 public E elemento (int pos) throws IndiceFueraDeRangoException {2 if ((pos <= 0) || (pos > tamanyo()))3 throw new IndiceFueraDeRangoException();4 else5 if (pos == 1)6 return _primero;7 else8 return _resto.elemento (pos - 1);9 }

Page 11: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

4.4. Implementación [57]

Debido a la naturelaza recursiva de la implementación planteada,de nuevo se puede apreciar la sencillez del método expuesto anterior-mente. El caso base está definido por el valor 1 del parámetro de en-trada pos (líneas

�� ��5-6 ), mientras que el caso recursivo está vinculado avalores mayores a 1 (líneas

�� ��7-8 ).

En este caso particular, el acceso directo al elemento que ocupa laposición i de la lista se traduce en realizar i-1 llamadas recursivas almétodo elemento, hasta alcanzar el caso base en cuyo caso se devolve-rá la referencia al primer elemento de la lista, vinculada a la variablede clase _primero (línea

�� ��6 ).

Otro grupo de operaciones relevantes para manipular listas son laseliminaciones, cuya posible implementación se muestra en el siguientelistado de código. Como se puede apreciar, se han implementado dosoperaciones dependiendo si se desee eliminar el primer elemento o,desde un punto de vista general, el i-ésimo elemento.

Listado 4.8: Clase ListaRecursiva. Métodos de eliminación.

1 protected void eliminarPrimero () {2 if (!esVacia()) {3 _primero = _resto._primero;4 _resto = _resto._resto;5 }6 }78 public void eliminarN (int pos) throws IndiceFueraDeRangoException

{9 if ((pos <= 0) || (pos > tamanyo()))10 throw new IndiceFueraDeRangoException();11 else12 if (pos == 1)13 eliminarPrimero();14 else15 _resto.eliminarN(pos - 1);16 }

La implementación de eliminarPrimero es muy sencilla y se basasimplemente en actualizar las referencias al primer elemento de la lis-ta y al resto. Dichas actualizaciones se realizarán siempre y cuandohaya algún elemento en la lista, comprobación que se efectúa utilizan-do la operación esVacía de listas (línea

�� ��2 ). En este caso particular,la visibilidad del método es protegida, ya que se utiliza internamen-te desde la versión general del método de eliminar que se describe acontinuación.

El método eliminarN sigue el mismo esquema que algunos de losanteriores previamente discutidos, siendo necesario controlar que elvalor del parámetro de entrada pos es válido, es decir, está definidoentre 1 y el tamaño de la lista (líneas

�� ��9-10 ).

Para comprobar si una lista es vacía, simplemente hay que com-probar si la lista tiene al menos un elemento. Para ello, se hace uso dela variable de clase _primero, de tal forma que si referencia a un valorno nulo, entonces la lista tiene al menos un elemento (la cabeza). Laimplementación se expone en el siguiente listado.

Listado 4.9: Clase ListaRecursiva. Métodos esVacía.

1 public boolean esVacia () {2 return _primero == null;3 }

Page 12: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

[58] CAPÍTULO 4. LISTAS

La modificación del contenido de la lista sigue la misma filosofíaque la operación general de modificación, pero necesita un parámetrode entrada más que se utiliza para modificar o sobreescribir el valorque previamente se almacenaba en una determinada posición.

Listado 4.10: Clase ListaRecursiva. Método de modificación.

1 public void modificarN (int pos, E e) throwsIndiceFueraDeRangoException, NullPointerException {

2 if (e == null)3 throw new NullPointerException();45 if ((pos <= 0) || (pos > tamanyo()))6 throw new IndiceFueraDeRangoException();7 else8 if (pos == 1)9 _primero = e;

10 else11 _resto.modificarN(pos - 1, e);12 }

El siguiente listado muestra el código relativo a la operación per-tenece, que como su propio nombre indica se utiliza para comprobarsi un determinado elemento está contenido en la lista, independiente-mente del número de ocurrencias que haya en la misma. Por lo tanto,el tipo de valor de retorno del método es lógico.

Listado 4.11: Clase ListaRecursiva. Método pertenece.

1 public boolean pertenece (E e) {2 return (posicion(e) > 0);3 }

En este caso particular, el método pertenece delega su funcionali-dad en el método posicion, basándose para ello en la premisa de queeste último devolverá un valor de -1 si el elemento que se le pasa co-mo parámetro no se encuentra en la lista, mientras que devolverá unmayor valor que 0 en caso contrario (línea

�� ��2 ).

Getters y setters

Normalmente, los métodosde acceso se suelen deno-minar getters, y suelen se-guir el esquema de nombradogetNombreVariable (). El casocontrario, es decir, los méto-dos que se utilizan para es-tablecer las variables de cla-se, se denominan setters ysuelen seguir el esquema set-NombreVariable ().

A continuación se muestra una posible implementación del métodoposicion, la cual se basa en devolver el valor de -1 en caso de que elelemento no se encuentre en la lista, condición que se controla cuandose llegue a una lista vacía (líneas

�� ��2-3 ).

Listado 4.12: Clase ListaRecursiva. Método que devuelve la posiciónde un elemento.

1 public int posicion (E e) {2 if (esVacia())3 return -1;4 else5 if (_primero.equals(e))6 return 1;7 else8 return 1 + _resto.posicion(e);9 }

Tal y como se discutió anteriormente, existe otro tipo de métodosdenominados de acceso, los cuales sirven para acceder a las variablesde clase, que suelen mantener una visibilidad privada para garantizarel principio de encapsulación. En el caso de la implementación discu-tida en esta sección, dichas variables son las relativas a los elementosprimero y resto de la lista.

Page 13: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

4.4. Implementación [59]

Listado 4.13: Clase ListaRecursiva. Métodos de acceso.

1 public E primero () throws ListaVaciaException {2 if (esVacia())3 throw new ListaVaciaException();4 else5 return _primero;6 }78 public ListaRecursiva<E> resto () {9 return _resto;10 }

Otro método a considerar dentro de la implementación discutida esaquél que permite obtener la longitud o tamaño de una lista, es decir,el número de elementos que forman parte de la misma. El siguientelistado muestra una posible alternativa que sigue, nuevamente, unesquema recursivo.

Listado 4.14: Clase ListaRecursiva. Método de consulta de tamaño.

1 public int tamanyo () {2 if (esVacia())3 return 0;4 else5 return 1 + _resto.tamanyo();6 }

Finalmente, se discutirán los tres últimos métodos de la implemen-tación de listas: i) el método toString que permite obtener una repre-sentación textual del contenido de una lista, ii) el método equals quepermite comparar listas en función de su contenido y el orden queocupan sus elementos, y iii) el método clone que posibilita la clona-ción de listas para obtener copias exactamente iguales que la original.En este punto, resulta importante destacar que la clase ListaRecursi-va implementaba la interfaz Cloneable, cuestión que resulta necesariapara la implementación de este último método.

El método toString es relativamente sencillo gracias de nuevo alenfoque recursivo, y su implementación se basa en ir acumulando enuna cadena de texto el contenido del primer elemento e ir llamandorecursivamente a toString sobre _resto. Recuerde que _resto es, a suvez, una instancia de la clase ListaRecursiva.

Listado 4.15: Clase ListaRecursiva. Método toString.

1 public String toString () {2 String s = "";34 if (!esVacia())5 s += (_primero.toString() + " " + _resto.toString());67 return s;8 }

Debido a que la lista puede contener elementos de cualquier tipode dato, incluso listas, se utiliza el método toString para acceder alcontenido de cada uno de los elementos de la lista. Esta implemen-tación asume que la clase de la que se instancian los elementos quese incluirán en la lista implementa el método toString. Si no es así,simplemente se almacenaría una representación textual de la propiareferencia.

Page 14: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

[60] CAPÍTULO 4. LISTAS

El método equals permite comparar dos listas, de manera recursi-va, para comprobar si tienen exactamente los mismos elementos, enel mismo orden, o no. Debido a que Java impone a que este métodoreciba como parámetro de entrada un objeto de tipo Object, la primeracomprobación que se hace en la implementación consiste en asegurar-se que el tipo derivado de dicho parámetro es del tipo ListaRecursiva(línea

�� ��4 ).

Listado 4.16: Clase ListaRecursiva. Método equals.

1 public boolean equals (Object obj) {2 boolean iguales = false;34 if (obj instanceof ListaRecursiva) {5 ListaRecursiva<E> aux = (ListaRecursiva<E>)obj;6 if ((esVacia() && aux.esVacia()) || this == aux)7 iguales = true;8 else9 if ((esVacia() && !aux.esVacia()) || (!esVacia() && aux.

esVacia()))10 iguales = false;11 else12 iguales = ((_primero.equals(aux._primero)) && (_resto.

equals(aux._resto)));13 }1415 return iguales;16 }

A continuación se realizan una serie de comprobaciones con el ob-jetivo de hacer más eficiente este método ante determinados casosespecíficos. Estos casos son dos:

1. Si las dos referencias (la propia referencia this y la pasada comoparámetro obj) son iguales, entonces las listas manejadas seránexactamente las mismas (líneas

�� ��6-7 ) y, por lo tanto, no hay ne-cesidad de comprobar su contenido.

2. Si alguna de las dos listas es vacía, entonces el método devuelvedirectamente el valor de falso.

Por ello, resta por contemplar la situación en la que las dos listastienen el mismo número de elementos. En tal caso, se compara elprimer elemento de las dos listas, utilizando el método equals ya quepueden ser elementos de cualquier tipo, y se llama recursivamente conel resto de las listas.

Si las listas iniciales tienen los mismos elementos, en el mismoorden, y la misma longitud, entonces el método equals devolverá ver-dadero.

Por último, el método clone permite devolver una copia exacta dela lista. Para ello, de nuevo se plantea un esquema recursivo copiandoel resto de la lista hasta que se llegue al caso base (línea

�� ��4 ), es decir,hasta llegar a una lista vacía. Así, a la vuelta de la recursividad se iráinsertando el contenido de la lista original (línea

�� ��6 ).

Page 15: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

4.4. Implementación [61]

Listado 4.17: Clase ListaRecursiva. Método de clonación de listas.

1 public ListaRecursiva<E> clone() throws CloneNotSupportedException{

2 ListaRecursiva<E> copia = new ListaRecursiva<E>();34 if(!esVacia()) {5 copia = _resto.clone();6 copia.insertar(_primero);7 }89 return copia;10 }

4.4.3. La Lista Ordenada

En determinadas situaciones puede resultar necesario manteneruna lista de elementos constatemente ordenada debido a los requisi-tos impuestos por un problema en particular. Esto implica que se hade garantizar que la lista de elementos esté totalmente ordenada des-pués de realizar cualquier tipo de modificación, como por ejemplo unainserción, en cualquier posición de la lista.

Si se utiliza la implementación discutida anteriormente para man-tener una lista con estas características, entonces es necesario delegarfuncionalidad en alguna operación de ordenado implementada en unaclase auxiliar.

Sin embargo, otra opción posible para mantener una lista cons-tantemente ordenada es garantizar que cualquier inserción se realiceen orden y que tras una modificación la lista se mantenga ordena-da. La eliminación de elementos sobre una lista ordenada no implicaoperaciones adicionales, ya que la lista se mantiene ordenada en todomomento.

En el siguiente listado de código se muestra una clase ListaOrdena-da (línea

�� ��3 ), que extiende la funcionalidad de la anterior implementa-ción para proporcionar listas ordenadas. Como se puede apreciar, losdos primeros constructores delegan en la implementación de la clasepadre debido al uso de super.

Listado 4.18: Clase ListaOrdenada. Métodos constructores.

1 package listas;23 public class ListaOrdenada<E extends Comparable<E>> extends

ListaRecursiva<E> {4 // Se omite parte del código fuente.5 public ListaOrdenada () { super(); }67 public ListaOrdenada (E e) { this(e, new ListaOrdenada<E>()); }89 public ListaOrdenada (E e, ListaOrdenada<E> l) {10 super(e, l);11 eliminarPrimero();12 insertarOrdenado(e);13 }1415 }

Sin embargo, el tercer constructor (línea�� ��9 ) está asociado al caso

particular en el que se especifican tanto el primer elemento de la lista(e) como el resto (l). En este caso, este primer elemento podría no estarordenado con respecto al resto de la lista, lo cual representaría el caso

Page 16: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

[62] CAPÍTULO 4. LISTAS

más común, por lo que es necesario insertarlo de manera ordenada(línea

�� ��12 ) posteriormente a su eliminación (línea�� ��11 ).

Evidentemente, el método más importante de la implementación delista ordenada es el método insertar de manera ordenada. Debido ala naturaleza recursiva de la implementación planteada, se distinguendos casos particulares. En primer lugar, si la lista es vacía, entoncesla inserción es trivial (líneas

�� ��7-10 ), ya que sólo hay que actualizar _pri-mero con el elemento pasado como parámetro e instanciar una nuevalista ordenada en _resto. En segundo lugar, hay que considerar el casoen el que la lista ya tenía elementos de manera previa a la inserción.

Debido a que las implementaciones discutidas son genéricas y per-miten el manejo de cualquier tipo de dato, el criterio de comparaciónde elementos para mantener una lista ordenada también ha de ser ge-nérico. La solución planteada pasa por delegar esta comparación en elmétodo compareTo de la interfaz Comparable (ver línea

�� ��3 del anteriorlistado de código).

En el caso recursivo (línea�� ��11 ), se distinguen a su vez dos casos.

Si el elemento a insertar es menor que el primero de la lista (línea�� ��16 ), entonces se insertar al principio, modificando tanto _primero co-mo _resto (líneas

�� ��13-14 ). Si no es así, entonces el elemento se insertade manera ordenada en el resto de la lista (línea

�� ��18 ).

Listado 4.19: Clase ListaOrdenada. Método insertarOrdenado.

1 public class ListaOrdenada<E extends Comparable<E>> extendsListaRecursiva<E> {

2 // Se omite parte del código fuente.3 private void insertarOrdenado (E e)4 throws NullPointerException {5 if (e == null) throw new NullPointerException();67 if (esVacia()) {8 _primero = e;9 _resto = new ListaOrdenada<E>();

10 }11 else12 if (_primero.compareTo(e) > 0) {13 _resto = new ListaOrdenada<E>14 (_primero, (ListaOrdenada<E>)_resto);15 _primero = e;16 }17 else18 ((ListaOrdenada<E>)_resto).insertarOrdenado(e);19 }2021 }

Sobreescritura

En Java se utiliza el metada-to @override para informar alcompilador de manera explí-cita que el método en cues-tión se está sobreescribien-do.

Por otra parte, y debido a la necesidad de mantener la lista orde-nada en todo momento, el resto de métodos de inserción que se in-trodujeron en la sección 4.4.1, en la que se presentó la interfaz Lista,han de mantener dicha relación de orden. Para ello, y tal y como sepresente en el siguiente listado de código, la solución más práctica ydirecta consiste en simplemente delegar en insertarOrdenado.

Por ejemplo, el método insertar (línea�� ��3 ), que inserta al principio de

la lista, realiza una llamada al método insertarOrdenada para insertarel elemento pasado como parámetro (línea

�� ��4 ). Del mismo modo, inser-tarN (línea

�� ��7 ) se basa en la misma idea, previa comprobación de quela posición se encuentra en los límites de la propia lista (líneas

�� ��8-9 ).

Page 17: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

4.4. Implementación [63]

Listado 4.20: Clase ListaOrdenada. Métodos de inserción.

1 public class ListaOrdenada<E extends Comparable<E>> extendsListaRecursiva<E> {

2 // Se omite parte del código fuente.3 @Override public void insertar (E e) throws

NullPointerException {4 insertarOrdenado(e);5 }67 @Override public void insertarN (int pos, E e) throws

IndiceFueraDeRangoException, NullPointerException {8 if ((pos <= 0) || (pos - 1 > tamanyo()))9 throw new IndiceFueraDeRangoException();10 else11 insertarOrdenado(e);12 }1314 @Override public void insertarFinal (E e) throws

NullPointerException {15 insertarOrdenado(e);16 }17 }

Finalmente, el siguiente listado muestra el método modificarN (lí-nea

�� ��3 ), en el que se ha de garantizar que el nuevo elemento que pasaa formar parte de la lista queda ordenado, lo cual implica que dichoelemento no permanezca en la posición n. Para ello, la solución plan-teada pasa por eliminar el elemento que se encontraba en la posición n(línea

�� ��4 ) para, a continuación, insertar de manera ordenada el nuevoelemento (línea

�� ��5 ).

Listado 4.21: Clase ListaOrdenada. Resto de métodos.

1 public class ListaOrdenada<E extends Comparable<E>> extendsListaRecursiva<E> {

2 // Se omite parte del código fuente.3 @Override public void modificarN (int pos, E e) throws

IndiceFueraDeRangoException, NullPointerException {4 super.eliminarN(pos);5 insertarOrdenado(e);6 }78 @Override public boolean pertenece (E e) {9 return (posicion(e) > 0);10 }11 }

Page 18: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

[64] CAPÍTULO 4. LISTAS

4.5. Aplicaciones de Referencia

4.5.1. Listas. Manejo básico

Cree un sencillo programa para generar y mostrar una lista de fo-tos, suponiendo la siguiente implementación de la clase Foto:

Listado 4.22: Clase Foto.

1 package utilidades;23 public class Foto {45 private String _autor;6 private double _resolucion;78 public Foto (String autor, double resolucion) {9 _autor = autor;

10 _resolucion = resolucion;11 }1213 public String getAutor () {14 return _autor;15 }1617 public double getResolucion () {18 return _resolucion;19 }2021 public String toString () {22 String s = "[Autor: " + _autor + ", ";23 s += ("Res. " + Double.toString(_resolucion) + "]");2425 return s;26 }2728 }

Antes de pasar a mostrar una posible solución a este sencillo pro-blema, distinguiremos tres bloques de funcionalidad básica que ha deincluir la solución propuesta:

1. Añadir instancias de la clase foto a una lista.

2. Mostrar todos los elementos de una lista.

3. Proporcionar un sencillo menú para acceder a la funcionalidadde los dos puntos anteriores.

El siguiente listado de código representa una posible solución, cen-trada en proporcionar al lector un ejemplo de referencia y no tantoen los aspectos de diseño e implementación. Básicamente, el códigoestá gobernado por un bucle do-while que se ejecutará hasta que elusuario, controlándolo desde teclado, así lo desee.

Page 19: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

4.5. Aplicaciones de Referencia [65]

Listado 4.23: Posible solución al problema de manejo básico de listas.

1 import listas.*;2 import utilidades.*;34 class TestListas1 {56 public static void main (String[] args) throws

ListaVaciaException, IndiceFueraDeRangoException,CloneNotSupportedException {

78 ListaRecursiva<Foto> l = new ListaRecursiva<Foto>();9 boolean salir = false;1011 do {12 menu();13 int opcion = Teclado.leerEntero();1415 switch (opcion) {16 case 1:17 anyadirALista(l);18 break;19 case 2:20 System.out.println(l);21 break;22 case 3:23 salir = true;24 }2526 } while (!salir);2728 }2930 public static void menu () {3132 System.out.println("1. A adir foto a lista.");33 System.out.println("2. Mostrar lista.");34 System.out.println("3. Salir.");35 System.out.print("Introduzca la opci n deseada... ");3637 }3839 public static void anyadirALista (ListaRecursiva<Foto> l) {4041 System.out.print("Introduzca autor... ");42 String autor = Teclado.leerCadena();43 System.out.print("Introduzca resolucion... ");44 int resolucion = Teclado.leerEntero();4546 l.insertarFinal(new Foto(autor, resolucion));4748 }4950 }

La funcionalidad para mostrar el menú por pantalla se ha delegadoen el método menu (línea

�� ��35 ), mientras que la inserción de instanciasde la clase Foto se ha delegado en el método anyadirLista (línea

�� ��44 ).Este último método recibe como parámetro una referencia a una listade fotos e, internamente, le pide al usuario que introduzca el autor yla resolución de la foto para, posteriormente, insertar la instancia alfinal de la lista (línea

�� ��51 ). Debido a que la lista se pasa por referencia,ésta queda modificada al finalizar la ejecución del método.

Por otra parte, la funcionalidad para mostrar el contenido de la listase encuentra dentro del código principal (línea

�� ��25 ) y no en un métodoauxiliar. Note que no es necesario implementar dicha funcionalidad denuevo, ya que la implementación de listas discutida en la sección 4.4contemplaba la sobreescritura del método toString, el cual se utiliza

Page 20: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

[66] CAPÍTULO 4. LISTAS

para recorrer el contenido de la lista y obtener una representacióntextual del mismo.

Finalmente, el código necesario para leer los atributos de las ins-tancias de la clase Foto se delegan en una clase estática Teclado, quecontiene diversos métodos para leer diferentes tipos de datos, comopor ejemplo enteros o cadenas de texto.

4.5.2. Listas. Inversión

Extienda la funcionalidad de listas con un nuevo método que per-mita invertir el contenido de una lista pasada como parámetro.

Aunque existen diversas soluciones para este sencillo problema, acontinuación se expondrá una solución basada en el uso de la estruc-tura pila para invertir el contenido de una lista. El siguiente listadomuestra una posible implementación de dicho método.

Listado 4.24: Inversión del contenido de una lista.

1 package listas;23 import pilas.*;45 public class ListaOperaciones {67 public static <E> void invertir (ListaRecursiva<E> lista) {8 PilaDinamica<E> p = new PilaDinamica<E>();9

10 try {11 // Volcar el contenido en la pila.12 while (!lista.esVacia()) {13 p.apilar(lista.primero());14 lista.eliminarN(1);15 }1617 // Volcar la pila en la lista.18 while (!p.esVacia()) {19 lista.insertar(p.cima());20 p.desapilar();21 }22 }23 catch (ListaVaciaException exc) {}24 catch (DesbordamientoPilaException exc) {}25 catch (IndiceFueraDeRangoException exc) {}26 catch (PilaVaciaException exc) {}27 }2829 }

Básicamente, la solución planteada se basa en volcar el contenidode la lista a invertir en una pila (líneas

�� ��12-15 ). Para ello, se va apilandoel primer elemento de la lista en la pila y, a continuación, se eliminael primer elemento de la lista. Este proceso se realiza hasta que lalista queda completamente vacía y todo el contenido se encuentra enla pila.

Posteriormente, se procede a vaciar la pila de manera que la cimase va insertando al principio de la lista original y, a continuación, sedesapila (líneas

�� ��18-21 ). Este proceso se realiza hasta que la pila sequeda vacía y en la lista se almacena todo el contenido que había ori-ginalmente pero en el orden contrario, gracias al uso de la estructurade pila.

Page 21: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

4.6. Cuestionarios de Autoevaluación [67]

4.6. Cuestionarios de Autoevaluación

1. En una implementación de listas discutida en el presente capítu-lo, ¿qué operación requiere un recorrido de toda la estructura enel peor de los casos?

a) EsVacía.

b) Primero.

c) Borrar (x).

d) Ninguna de las anteriores.

(2). Si se desea utilizar las listas para realizar un gran número deoperaciones del tipo posición n, ¿qué implementación de listassería la más adecuada?

a) Listas con gestión estática de memoria (uso de arrays).

b) Listas con nodos de doble enlace.

c) Listas con nodos cabecera y fin.

d) Listas con nodo cabecera.

(3). Los elementos de una lista ordenada han de implementar, en Ja-va, la interfaz...

a) Enumeration.

b) Comparable.

c) Lista.

d) ListaRecursiva.

(4). Considerando la implementación de listas discutida en el presen-te capítulo, ¿sería posible instanciar una lista de pilas?

a) Sí, siempre.

b) Sí, si la pila está implementada con gestión dinámica de me-moria.

c) No, nunca.

d) Ninguna de las anteriores.

(5). Desde un punto de vista general, la implementación de listas conun enfoque recursivo es más eficiente que un enfoque iterativo.

a) Verdadero.

b) Falso, la eficiencia es independiente del enfoque escogido.

c) Falso, la implementación con un enfoque iterativo es máseficiente.

d) Ninguna de las anteriores.

(6). El uso de listas doblemente enlazadas...

a) Proporciona más flexibilidad que el enfoque recursivo.

b) Es más eficiente, al utilizar menos redundancia.

c) Proporciona menos flexibilidad que el enfoque recursivo.

d) Ninguna de las anteriores.

Page 22: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

[68] CAPÍTULO 4. LISTAS

(7). ¿Cuál es la complejidad en el peor de los casos de la operaciónelemento de listas según el enfoque recursivo?

a) O(1).

b) O(n).

c) O(n2).

d) Ninguna de las anteriores.

(8). Considere una implementación de listas con arrays, ¿cuál es lacomplejidad en el peor de los casos de la operación elemento delistas según el enfoque recursivo?

a) O(1).

b) O(n).

c) O(n/2).

d) Ninguna de las anteriores.

(9). ¿Para qué se utiliza la interfaz Comparable?

a) Para definir un criterio a la hora de ordenar elementos.

b) Para definir un criterio a la hora de comparar elementos.

c) Para definir un criterio a la hora de eliminar elementos.

d) Ninguna de las anteriores.

(10). ¿Cuándo utilizaría una cola o una pila en detrimento de una lis-ta?

a) En general, utilizaría listas para cualquier problema, al sermás flexibles que las colas y las pilas.

b) Cuando necesite utilizar un enfoque iterativo.

c) En casos en los que el criterio de acceso/modificación seamás restrictivo y sólo se permita acceder/modificar el primero último elemento, respectivamente.

d) Ninguna de las anteriores.

Page 23: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

4.7. Problemas y Soluciones [69]

4.7. Problemas y Soluciones

4.7.1. El juego de la diana

Enunciado

Una serie de personas juegan a los dardos en una diana con 10círculos concéntricos numerados del 1 al 10. Cada jugador va tirando,por riguroso orden, 3 dardos por tirada y se anota los puntos conse-guidos. Cuando un jugador alcanza los 100 puntos se retira del juego,que acabará cuando ya no queden jugadores (que hayan alcanzado los100 puntos).

Se pide solucionar los siguientes apartados:

A. Diseñe las estructuras de datos necesarias para solucionar elproblema.

B. Implemente la funcionalidad necesaria para simular partidas don-de, además, se desee conocer el orden en el que ha acabado cadajugador y la tirada, con su autor, de mayor puntuación.

Solución

En primer lugar, se propone al lector la identificación de las clasesque sean adecuadas para plantear el diseño de la solución propuesta.En este contexto, suele resultar bastante útil preguntarse por aquelloselementos que mantienen un estado y proporcionan una determinadafuncionalidad.

En principio, se podría plantear la siguiente lista de clases poten-ciales que se expone a continuación. Posteriormente, se irá discutien-do cuáles de ellas formarán parte de la solución final y ,en caso dedescartar alguna, cuáles no.

Jugador.

Dardo.

Diana.

Tirada.

Punto.

Juego.

Por ejemplo, la clase Diana debería contemplar un estado basadoen los distintos números que la conforman. En el caso particular delenunciado, dichos número irían de 1 a 10. Respecto a la funcionalidad,la parte más evidente sería la de generar una puntuación para undeterminado jugador.

Normalmente, suele ser una buena decisión de diseño el plante-amiento de soluciones generales. Por ejemplo, el estado de la claseDiana podría tener un rango de 1 a n, siendo parametrizable el valormáximo de puntuación que se puede obtener.

La clase Punto podría contener como estado el valor entero aso-ciado a instancias de dicha clase, pero no resulta evidente asociar

Page 24: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

[70] CAPÍTULO 4. LISTAS

funcionalidad relevante a la misma. Por lo tanto, dicha clase puedeser descartada.

El mismo razonamiento se puede aplicar para la clase Dardo, res-pecto a la funcionalidad de la misma. Se podría pensar en lanzar comofuncionalidad típica, pero parece más adecuado incluir dicha funcio-nalidad en la clase Diana, la cual será responsable de la gestión de lasdistintas tiradas de los jugadores.

Sin embargo, la clase Tirada sí que es relevante para la soluciónplanteada. Una tirada ha de registrar la puntuación asociada a la mis-ma, es decir, la obtenida por los tres dardos que la conforman, y hade gestionar la funcionalidad asociada a acumular los puntos de losdistintos dardos lanzados y, posteriormente, posibilitar la recupera-ción de la mejor tirada. Recuerde que la mejor tirada es aquella queregistra la mayor cantidad de puntos.

Por otra parte, la clase Jugador resulta bastante evidente comoparte de la solución, ya que debe almacenar como estado la evoluciónde su partida, es decir, la lista de tiradas, y algún identificador paradistinguir entre los jugadores que participan en una partida. Por otraparte, la clase jugador contiene la funcionalidad más relevante de lasolución, como por ejemplo lanzar los dardos en una tirada, la posi-bilidad de consultar la lista de tiradas anteriores, o el hecho de podercomprobar cuándo un jugador resulta ganador o cuál es la mejor tira-da de la partida.

Finalmente, la clase Juego es la responsable de llevar a cabo la si-mulación de partidas, y ha de almacenar como estado la propia diana,la lista de jugadores e incluso las reglas del juego, como por ejemploel número de dardos por tirada o el número de puntos a sumar paraganar una partida. Una vez más, es preferible plantear soluciones ge-nerales siempre que sea posible como práctica general. Respecto a lafuncionalidad, la clase juego ha de permitir la inclusión de jugadoresen una partida, gestionar el bucle de juego, mostrar la clasificación enun determinado momento y, tal y como pide explícitamente el enun-ciado, obtener la mejor tirada hasta el momento.

DianaJuego1 1

TiradaJugador

1

1..*

1

1

1 1..*

Figura 4.4: Diagrama de clases para el problema de la simulación del juego de la diana.

Page 25: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

4.7. Problemas y Soluciones [71]

La figura 4.4 muestra el diagrama de clases que refleja la soluciónplanteada para el problema propuesto.

Como se puede apreciar en la anterior figura, a raíz de los requi-sitos planteados en el enunciado, un juego tiene asociado uno o másjugadores y una única diana. A su vez, un jugador tiene asociado unaserie de tiradas, que representa su evolución en una determinada par-tida. Por otra parte, existe una relación de uso entre las clases tiraday diana, ya que una tirada ha de conocer la configuración de la dianapara registrar la puntuación obtenida en la misma.

El siguiente listado de código muestra la clase Diana, la cual no de-pende de otras clases en el diseño propuesto. Como se puede apreciaren dicho listado, las partes más relevantes de dicha clase son la inclu-sión de una variable de visibilidad privada para almacenar el valor depuntuación máxima que se puede obtener en una tirada1, y la funcio-nalidad del método puntos, que devuelve un valor aleatorio entre 0 yel máximo de la diana.

Listado 4.25: Clase Diana.

1 package diana;23 import java.util.Random;45 public class Diana {67 private int _maximo;89 public Diana () {10 _maximo = 10;11 }1213 public Diana (int n) {14 _maximo = n;15 }1617 public int puntos () {18 return new Random().nextInt(_maximo + 1);19 }2021 public String toString (){22 return "M ximo: " + _maximo;23 }2425 }

La clase Tirada se muestra en el siguiente listado de código. Taly como se puede apreciar en el mismo, el estado de dicha clase estácompuesto por un vector de enteros que permiten almacenar la pun-tuación de los distintos dardos que conforman una tirada. Normal-mente, una tirada está compuesta de 3 dardos (ver primer constructoren línea

�� ��7 ), pero la implementación de esta clase está complementadacon un segundo constructor (línea

�� ��11 ) que generaliza la definición detirada para n dardos.

A continuación, el método lanzar hace uso de un objeto de la claseDiana, pasado como parámetro, para obtener la puntuación de cadauno de los dardos que forman dicha tirada. En concreto, se utiliza elmétodo puntos de dicha clase para obtener el valor de la tirada t (verbucle for en la línea

�� ��16 ). Por otra parte, el método suma acumula lapuntuación obtenida de una tirada para poder devolverlo.

1La solución planteada se simplifica asumiendo que la puntuación más baja que sepuede obtener al lanzar un dardo es 1

Page 26: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

[72] CAPÍTULO 4. LISTAS

Listado 4.26: Clase Tirada.

1 package diana;23 public class Tirada {45 private int[] _puntos;67 public Tirada () {8 this(3);9 }

1011 public Tirada (int num_dardos) {12 _puntos = new int[num_dardos];13 }1415 public void lanzar (Diana d) {16 for (int t = 0; t < _puntos.length; t++)17 _puntos[t] = d.puntos();18 }1920 public int suma (){21 int p = 0;2223 for (int t = 0; t < _puntos.length; t++)24 p += _puntos[t];2526 return p;27 }2829 }

La clase Jugador es la clase diseñada para registrar la evoluciónde un jugador a lo largo de una partida. Para ello, cada jugador ha detener un identificador único y alguna estructura de datos que permi-ta el almacenamiento de las distintas tiradas de dicho jugador. Estaestructura de datos ha de permitir la insercción de nuevas tiradas yla posibilidad de consultar la mejor tirada del jugador en un momentodeterminado. En la solución planteada en la presente sección, la es-tructura elegida es la lista, tal y como se puede apreciar en el siguientelistado de código.

Nótese que el constructor inicializa todo el estado de la clase Juga-dor, en este caso el identificador o nombre y la lista de futuras tiradas(ver líneas

�� ��10-13 ).

Recuerde que los constructores de clase han de inicializar todoel estado de las instancias de una clase, con el objetivo de evitarposibles inconsistencias futuras.

Por otra parte, el método lanzar (línea�� ��15 ) actualiza el estado del

jugador, haciendo que el juego evolucione con una nueva tirada. Estanueva tirada, que se instancia cada vez que se realiza una llamadaal método lanzar de la clase Jugador, hace uso de su propia funcio-nalidad lanzar para obtener los puntos de las distintas tiradas de lasque conste, especificadas por el parámetro entero dardos. Cuando latirada ha sido realizada (línea

�� ��17 ), el jugador inserta dicha tirada ensu lista de tiradas (línea

�� ��18 ), esperando su turno hasta que el resto dejugadores terminen la ronda.

Page 27: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

4.7. Problemas y Soluciones [73]

Listado 4.27: Clase Jugador.

1 package diana;23 import listas.*;45 public class Jugador {67 private String _nombre;8 private ListaRecursiva <Tirada> _tiradas;910 public Jugador (String nombre) {11 _nombre = nombre;12 _tiradas = new Lista<Tirada>();13 }1415 public void lanzar (Diana d, int dardos) {16 Tirada t = new Tirada(dardos);17 t.lanzar(d);18 _tiradas.insertarFinal(t);19 }2021 public ListaRecursiva<Tirada> getTiradas() {22 return _tiradas;23 }2425 // ...2627 }

Por otra parte, el enunciado del problema pide explícitamente lafuncionalidad para poder determinar cuándo un jugador ha ganadouna partida, es decir, cuándo un jugador alcanza un número determi-nado de puntos. El lugar más adecuado para incluir la funcionalidadrelativa para consultar los puntos de un jugador en un momento de-terminado es la clase Jugador, ya que en ella se almacena el estado deun jugador, consistente en la lista de tiradas. El siguiente listado decódigo muestra una posible implementación de este método.

Listado 4.28: Clase Jugador. Método puntuación.

1 package diana;23 import listas.*;45 public class Jugador {67 // ...89 public int puntuacion () {1011 for (int puntos = 0, ListaRecursiva<Tirada> aux = _tiradas;12 !aux.esVacia();13 aux = aux.resto())1415 puntos += aux.primero().suma();1617 return puntos;18 }192021 }

Básicamente, el método puntuacion ha de recorrer la lista de tira-das de un jugador, sumar la puntuación de cada una de ellas en unacumulador y, finalmente, devolver el valor de éste. Una manera com-pacta de realizarlo consiste en usar un bucle for (líneas

�� ��11-13 ) querecorra la lista de tiradas, tal y como se discutió en la implementación

Page 28: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

[74] CAPÍTULO 4. LISTAS

de listas recursivas de la sección 4.4.2, utilizando una referencia auxi-liar que se vaya actualizando con el resto de la lista en cada iteración(mientras no sea vacía). Cada iteración del bucle permite acumular elvalor de la primera tirada de la lista y acumular su valor en la variablepuntos.

Finalmente, la clase Jugador también incluye la funcionalidad paraobtener la mejor tirada de un determinado jugador. Dicha funcionali-dad se incluye en esta clase por el mismo motivo que se argumentó enel caso del método puntuacion. Básicamente, el método mejorTiradaha de recorrer la lista de tiradas del jugador y devolver aquella que tu-vo una mayor puntuación, tal y como se aprecia en el siguiente listadode código.

Listado 4.29: Clase Jugador. Método mejorTirada.

1 package diana;23 import listas.*;45 public class Jugador {67 // ...89 public Tirada mejorTirada () {

1011 int mejor_puntuacion = 0;12 Tirada mejorTirada = null;13 ListaRecursiva<Tirada> aux = _tiradas;1415 while (!aux.esVacia()) {16 if(aux.primero().suma() > mejor_puntuacion) {17 mejor_puntuacion = aux.primero().suma();18 mejor_tirada = aux.primero();19 }20 aux = aux.resto();21 }2223 return mejor_tirada;2425 }2627 }

La parte más relevante del método mejorTirada se encuentra en laslíneas

�� ��16-19 , donde se comprueba con una sentencia if si la tirada idel jugador es mejor, es decir, tiene mayor puntuación, que la mejortirada obtenida hasta dicha iteración. En tal caso, se actualizan lasvariables auxiliares mejor_puntuacion y mejor_tirada.

La clase más compleja de la solución planteada es la clase Juego,responsable del bucle de juego y de mantener la clasificación de losdistintos jugadores. Por una parte, el bucle de juego ha de respetarel turno de todos y cada uno de los jugadores, así como comprobardespués de cada tirada si algún jugador resulta victorioso. Por otraparte, esta clase también ha de albergar la funcionalidad relativa a laclasificación de los usuarios en cada momento, así como la posibilidadde consultar información relativa al mejor jugador y a la mejor jugadaen términos generales.

En el estado de la clase Juego resulta evidente incluir una refe-rencia a un objeto de la clase Diana, así como el número de puntosa alcanzar para proclamar un ganador. También es necesario utilizaralgún tipo de estructura o algoritmo para respectar el turno de los ju-gadores y almacenar la clasificación en todo momento. Para modelar

Page 29: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

4.7. Problemas y Soluciones [75]

el turno de los jugadores se ha utilizado una estructura de tipo cola,la cual se justificará más adelante cuando se discuta el método jugar,mientras que para la clasificación se ha usado una estructura de tipolista, más flexible para la gestión de la misma.

En el siguiente listado de código se muestran las variables de clasey los métodos constructores de la clase Juego.

Listado 4.30: Clase Juego.

1 package diana;23 import listas.*;4 import colas.*;56 public class Juego {78 Diana _diana;9 ColaDinamica<Jugador> _jugadores;10 ListaRecursiva<Jugador> _clasificacion;11 int _puntos_fin;12 int _num_dardos;1314 public Juego (int max_punt, int puntos_fin, int num_dardos) {15 _diana = new Diana(max_punt);16 _jugadores = new ColaDinamica<Jugador>();17 _clasificacion = new ListaRecursiva<Jugador>();18 _puntos_fin = puntos_fin;19 _num_dardos = num_dardos;20 }2122 public Juego() {23 this (10, 100, 3);24 }2526 public Juego (int puntos_fin) {27 this (10, puntos_fin, 3);28 }2930 // ...3132 }

Como se puede apreciar en el anterior listado, el estado de la claseJuego también contempla los puntos de finalización de una partida(línea

�� ��11 ) y el número de dardos por tirada (línea�� ��12 ). Sin embargo, la

puntuación máxima que se puede alcanzar al lanzar un dardo formaparte del objeto de la clase Diana (línea

�� ��15 ).

Tanto el segundo constructor (líneas�� ��22-24 ) como el tercero (líneas�� ��26-28 ), recurren al primer constructor, utilizando la sentencia this,

para completar la inicialización de todos los parámetros de clase. Pordefecto, la máxima puntuación a obtener es de 10 puntos, con unapuntuación necesaria de 100 puntos para ganar, usando 3 dardos portirada.

A continuación se muestra una posible implementación del códigonecesario para ejecutar el bucle principal de la simulación del juegode la diana. La idea de la implementación es simple:

(1). Se recupera al primer jugador de la cola (línea�� ��14 ), almacenando

su referencia en la variable auxiliar j.

(2). Se saca al primer jugador de la cola (línea�� ��15 ).

(3). El jugador lanza a la diana (línea�� ��17 ).

Page 30: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

[76] CAPÍTULO 4. LISTAS

(4). Si el jugador no ha alcanzado la puntuación máxima (línea�� ��20 ),

entonces se vuelve a encolar en la cola de jugadores, aguardandosu próximo turno (línea

�� ��22 ). En caso contrario, es decir, si eljugador ha alcanzado la puntuación máxima, entonces se inserta,en la última posición, en la lista de jugadores que conforman laclasificación final (línea

�� ��25 ).

Listado 4.31: Clase Juego. Método jugar.

1 package diana;23 import listas.*;4 import colas.*;56 public class Juego {78 // ...9

10 public void jugar () {11 Jugador j = null;1213 while (!_jugadores.esVacia()) {14 j=_jugadores.primero();15 _jugadores.quitar();1617 j.lanzar(_diana, _num_dardos);1819 // ¿Ha llegado a la puntuación máxima?20 if (j.puntos() < _puntos_fin)21 // Encolar para seguir jugando.22 _jugadores.a adir(j);23 else24 // Termina y se actualiza la clasificación.25 _clasificacion.insertarFinal(j);26 }2728 }2930 }

Finalmente, para obtener la mejor tirada, junto con su tirador aso-ciado, se ha implementado el método mejorTirada, como se puedeapreciar a continuación.

Listado 4.32: Clase Juego. Método mejorTirada.

1 package diana;23 import listas.*;4 import colas.*;56 public class Juego {78 // ...9

10 public Tirada mejorTirada () {11 Jugador j = this.mejorTirador();1213 if(j != null)14 return j.mejorTirada();15 else16 return null;17 }1819 }

Page 31: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

4.7. Problemas y Soluciones [77]

Dicho método delega parte de su funcionalidad en el método me-jorTirador, contenido en la propia clase Juego. En primer lugar, se ob-tiene al mejor tirador (línea

�� ��11 ). Posteriormente, se devuelve la mejortirada del mejor tirador (línea

�� ��14 ). Recuerde que dicha funcionalidadya se implementó en la clase Jugador.

El método mejorTirador se expone en el siguiente listado. La idea essencilla. Básicamente se recorre la lista de jugadores de la clasificacióny en sendas variables auxiliares se almacenan el valor de la máximapuntuación lograda en una tirada y su jugador asociado (líneas

�� ��16-19 ).Cuando se ha recorrido la lista en su totalidad, es decir, cuando elbucle while termina, entonces ya es posible devolver la referencia aljugador con la mejor tirada del juego (línea

�� ��23 ).

Listado 4.33: Clase Juego. Método mejorTirada.

1 package diana;23 import listas.*;4 import colas.*;56 public class Juego {78 // ...910 public Jugador mejorTirador () {11 Jugador j = null;12 ListaRecursiva<Jugador> aux = clasificacion();13 int t = 0;1415 while (!aux.esVacia()) {16 if (aux.primero().mejorTirada().suma() > t) {17 j = aux.primero();18 t = aux.primero().mejorTirada().suma();19 }20 aux = aux.resto();21 }2223 return j;2425 }2627 }

Consideraciones finales

En esta sección no se discute el código necesario para inicializaruna simulación y añadir los distintos jugadores que conforman unapartida. Básicamente, sería necesario inicializar el estado de los juga-dores para poder identificarlos, añadirlos a la cola de jugadores pararespetar el juego por turnos e invocar al método jugar de la clase Ju-gador. A continuación, el menú debería de incluir opciones para con-sultar la mejor tirada y el mejor jugador.

Page 32: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

[78] CAPÍTULO 4. LISTAS

4.7.2. Sistema de publicación de noticias

Enunciado

Un grupo editorial de la región está interesado en adquirir un soft-ware de publicación de noticias. El sistema a desarrollar ha de permitirque los usuarios que deseen suscribirse puedan elegir el tipo de noti-cias en los que están interesados, como por ejemplo sociedad, cultura,internacional, deportes, etc. El sistema está estructurado en una seriede canales de noticias (uno por tipo de noticia), cuya tarea consiste enla distribución de noticias a los suscriptores.

Cada canal de noticias cuenta con un registro dinámico de losusuarios que están suscritos al mismo. El coste de la suscripción deun usuario vendrá determinado por la suma de los costes de suscrip-ción asociados a los canales a los que se suscribe un usuario. Cadausuario tiene un buzón de noticias para poder leerlas (en el mismoorden en que las recibe) y, en su caso, eliminarlas o archivarlas parasu posterior lectura.

Si un usuario encuentra particularmente interesante una noticia,entonces puede marcarla como muy interesante. El objetivo es que elsistema sea capaz de determinar el top 5 de noticias interesantes portipo de canal (sociedad, cultura, etc.), es decir, las más votadas por losusuarios. De este modo, el sistema ha de permitir que en cualquiermomento un suscriptor acceda a las 5 noticias más interesantes deun determinado tipo.

Se pide solucionar los siguientes apartados:

A. Diseñe las estructuras de datos necesarias para solucionar elproblema.

B. Implemente la funcionalidad necesaria para el cálculo del costede la suscripción, por usuario y a nivel de sistema.

C. Implemente el código necesario para actualizar y obtener el top5de noticias interesantes por tipo.

Solución

En primer lugar, se propone al lector la identificación de potencialesclases que sean útiles para plantear el diseño de la solución propuesta.En este contexto, suele resultar bastante útil preguntarse por aquelloselementos que mantienen un estado y proporcionan una determinadafuncionalidad.

Por ejemplo, resulta bastante evidente pensar que el suscriptor o elusuario representa una clase importante para solucionar el problemaplanteado. En este contexto, su estado estaría representado por ele-mentos como su identificación o los tipos de noticias a los que estásuscrito. Precisamente, una parte importante de la funcionalidad delsuscriptor es la posibilidad de modificar su suscripción al sistema denoticias.

A continuación se listan las clases que formarán parte de la solu-ción planteada:

Noticia, cuyo estado está relacionado a la identificación, tipo denoticia, contenido y número de veces que es interesante. La fun-cionalidad más relevante será la de marcarla como importante.

Page 33: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

4.7. Problemas y Soluciones [79]

Suscriptor, cuyo estado está relacionado con la identificación delsuscriptor, los tipos de noticias en los que está interesado y lasnoticias que recibe y archiva, principalmente. Respecto a la fun-cionalidad, un suscriptor ha de poder modificar su suscripción yrecibir noticias, entre otras cuestiones.

Canal de noticias, cuyo estado está relacionado con el tipo denoticias que gestiona, el coste de suscripción y las listas de sus-criptores y noticias que maneja. Entre su funcionalidad destacala publicación de noticias, la gestión de usuarios en un determi-nado canal y la posibilidad de obtener el top5 de noticias.

Sistema de publicación, que contendrá la lista de canales denoticias y actuará como interfaz entre los suscriptores y los ca-nales para que los primeros especifiquen los tipos de noticias enlos que están interesados, entre otras cosas.

Notése que el tipo de noticia no se modela como una clase ya queno tiene asociada ningún tipo de funcionalidad. De hecho, el tipo denoticia representa simplemente valores concretos, como por ejemplonacional, internacional o deportes.

A continuación, la figura 4.5 muestra el diagrama de clases querefleja la solución planteada para el problema propuesto.

Canal denoticias

Sistema depublicación 1 1..*

NoticiaUsuario

1

1..*1..*

1..* 1

1..*

* *

Figura 4.5: Diagrama de clases para el problema del sistema de publicación de noticias.

Como se puede apreciar en la anterior figura, y tal y como se des-prende del enunciado, el sistema de publicación de noticias está com-puesto por una serie de canales de noticias. Además, cada canal es elresponsable de gestionar la lista de suscriptores asociados al mismo(ver relación de asociación entre el suscriptor y el sistema y entre elsistema y el canal, respectivamente). En esta solución se ha planteadode manera que el sistema de publicación delega la operación de sus-cripción en los canales de noticias, de forma que no existe un registroexplícito de los suscriptores en el sistema. Este aspecto se volverá adiscutir más adelante.

Por otra parte, tanto el suscriptor como el canal de noticias estánrelacionados con las noticias. Sin embargo, tal y como se ha planteado

Page 34: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

[80] CAPÍTULO 4. LISTAS

el diseño, una noticia no conoce el canal por el que navega ni el usuarioque la consulta.

El siguiente listado de código muestra la clase Noticia, la cual no de-pende de otras clases en el diseño propuesto. Como se puede apreciaren la cabecera de la misma, esta clase implementa la interfaz Compa-rable, con el objetivo de establecer un criterio para comparar noticias.En el caso particular planteado por el enunciado, este criterio se basaen el número de veces que un usuario marca una noticia en concretocomo interesante.

Listado 4.34: Clase Noticia.

1 package sistemaNoticias;23 public class Noticia implements Comparable<Noticia> {45 private int _id;6 private TipoDeNoticia _tipo;7 private String _contenido;8 private int _vecesInteresante;9

10 public Noticia (int id, TipoDeNoticia tipo, String contenido) {11 _id = id;12 _tipo = tipo;13 _contenido = contenido;14 _vecesInteresante = 0;15 }1617 public int getId () { return _id; }18 public TipoDeNoticia getTipo() { return _tipo; }19 public void setContenido (String contenido) { _contenido =

contenido; }20 public String getContenido () { return _contenido; }21 public void esInteresante () { _vecesInteresante += 1; }22 public int getVecesInteresante () { return _vecesInteresante; }2324 public boolean equals (Object obj) {25 return ((obj instanceof Noticia) && _id == ((Noticia)obj).getId

()) || (this == obj);26 }2728 public String toString () {29 String aux = "";30 aux += ("ID: " + _id + "\t");31 aux += ("Tipo: " + _tipo + "\t");32 aux += ("Veces interesante: " + _vecesInteresante + "\n");33 aux += ("Contenido: " + _contenido + "\n");3435 return aux;36 }3738 public int compareTo (Noticia n) {39 if (_vecesInteresante > n.getVecesInteresante())40 return 1;41 else42 if (_vecesInteresante == n.getVecesInteresante())43 return 0;44 else45 return -1;46 }4748 }

La clase Noticia tiene cuatro varibles de clase:

El identificador _id, de tipo entero.

El tipo de noticia asociado _tipo, de tipo TipoDeNoticia. Debido a

Page 35: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

4.7. Problemas y Soluciones [81]

que pueden existir una serie de tipos de noticias, se ha optadopor utilizar una enumeración para definirlo. En Java, los tiposenumerados enum ofrecen una gran cantidad de funcionalidad yson muy prácticos y versátiles.

El propio contenido de la noticia _contenido, de tipo cadena.

El número de veces que una noticia resulta interesante _vecesIn-teresante, de tipo entero.

Recuerde utilizar nombres para las variables que aporten lamayor semántica posible.

Entre las líneas�� ��10-22 se exponen el método constructor y algunos

getters y setters relevantes para la implementación de la clase. Deellos, destaca el método esInteresante (línea

�� ��21 ), el cual sirve paraincrementar en una unidad la variable de clase _vecesInteresante.

El método equals (línea�� ��24 ) se ha implementado utilizando la com-

paración entre los identificadores de las noticias, ya que se parte de lapremisa de que cada noticia tendrá un identificador única. Así mismo,también se realizan algunas comprobaciones comunes en la mayoríade implementaciones de este método, como por ejemplo la comproba-ción explícita de que el objeto pasado como parámetros es una instan-cia de la clase Noticia.

Finalmente, el método compareTo de la interfaz Comparable per-mite la comparación de dos instancias de la clase en base al númerode veces que dichas noticias fueron marcadas como interesantes. Estemétodo ha de implementarse para que más adelante sea posible obte-ner el top5 de noticias interesante por tipo (como se verá más adelanteen la clase CanalDeNoticias).

Implemente el método compareTo en lugar de implementar elsuyo propio. De este modo, será posible utilizar toda la potenciaque ofrece el API de Java.

En la clase Suscriptor se puede apreciar el manejo de estructurasde datos como las listas o las colas, para gestionar de manera ade-cuada el estado de las instancias de dichas clases. Esta clase haceuso de las clases Noticia y Sistema. Por una parte, el suscriptor ha demantener algún tipo de estrucutura de datos para procesar y almace-nar las noticias. Para procesarlas, hace uso de una cola que garanticeque dichas noticias se procesan en el mismo orden en el que se reci-ben. Para almacenarlas, se ha optado por utilizar una lista. Por otraparte, el suscriptor ha de poder acceder al sistema con el objetivo deinteractuar y posibilitar la funcionalidad especificada por el enuncia-do, como por ejemplo el registro necesario para recibir noticias de undeterminado tipo.

Page 36: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

[82] CAPÍTULO 4. LISTAS

Listado 4.35: Clase Suscriptor.

1 import listas.*;2 import colas.*;34 public class Suscriptor {56 private int _dni;7 private ListaRecursiva<TipoDeNoticia> _tiposNoticia;8 private ColaDinamica<Noticia> _recibidas;9 private ListaRecursiva<Noticia> _archivadas;

1011 private SistemaPublicacion _sistema;1213 public Suscriptor (int dni, SistemaPublicacion sistema) {14 // ...15 }1617 public void anyadirTipoSuscripcion (TipoDeNoticia tipo) throws

CanalDeNoticiasNoExistenteException,SuscriptorExistenteException {

18 try {19 if (!_tiposNoticia.pertenece(tipo)) {20 _tiposNoticia.insertar(tipo);21 _sistema.suscribir(this, tipo);22 }23 }24 catch (CanalDeNoticiasNoExistenteException exc) {25 throw exc;26 }27 }2829 public void eliminarTipoSuscripcion (TipoDeNoticia tipo) {30 _tiposNoticia.eliminar(tipo);31 }3233 public void recibirNoticia (Noticia n) throws

ColaLlenaException {34 _recibidas.anyadir(n);35 // Insertar en archivadas tras proceso...36 }3738 public String toString () {39 String s = "Usuario: " + _dni + "\nTipos de noticias: ";40 // Recorrer la lista de tipos...41 return s;42 }4344 }

La clase Suscriptor tiene cinco variables de clase:

El DNI del suscriptor _dni, que además es su identificador única,de tipo entero.

Una lista con los tipos de noticias a los que está suscrito _tipos-Noticia, de tipo ListaRecursiva<Noticia>.

Una cola de noticias recibidas _recibidas, de tipo ColaDinami-ca<Noticia>.

Una lista de noticias archivadas _archivadas, de tipo ListaRecur-siva<Noticia>.

Una referencia al sistema de publicación de noticias _sistema, detipo SistemaPublicacion.

Una de las decisiones de diseño más relevantes de la solución pro-puesta se refleja, de manera indirecta, en el método anyadirTipoSus-cripcion (línea

�� ��17 ). Internamente, el suscriptor actualizará su lista de

Page 37: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

4.7. Problemas y Soluciones [83]

tipos de noticias a los que está suscrito (línea�� ��20 ). Sin embargo, de ma-

nera externa, el suscriptor solicitará su suscripción al tipo de noticiapasado como parámetro (línea

Acceso concurrente

En la solución planteada nose ha tenido en cuenta el po-sible acceso concurrente almétodo recibirNoticia. Ideal-mente, sería necesario con-trolarlo para evitar inconsis-tencias en el estado del sus-criptor.

El otro método relevante de esta clase es recibirNoticia, el cual im-plementa la lógica necesaria para procesar y almacenar noticias. Poruna parte, la noticia recibida como parámetro se almacena en la colade noticias para procesarlas en orden. Por otra parte, en este métodose incluiría el criterio necesario para almacenarla en la lista de noti-cias.

La clase CanalDeNoticias es la encargada de gestionar parte de lafuncionalidad clave demandada por el enunciado, destacando la sus-cripción de usuarios y la publicación del top5 de noticias. Debido aque dicha suscripción está tipificada, es decir, los usuarios eligen enqué tipo de noticias están interesados, en la solución discutida se haplanteado delegar esta funcionalidad en la clase CanalDeNoticias, demanera que cada uno de los canales tenga la lista de usuarios suscri-tos al mismo.

Listado 4.36: Clase Canal de Noticias.

1 import listas.*;2 import colas.*;34 public class CanalDeNoticias {56 private TipoDeNoticia _tipo;7 private int _costeSuscripcion;8 private ListaRecursiva<Suscriptor> _suscriptores;9 private ListaRecursiva<Noticia> _noticias;1011 public CanalDeNoticias (TipoDeNoticia tipo, int

costeSuscripcion) {12 // Inicializar estado...13 }1415 public TipoDeNoticia getTipo () { return _tipo; }1617 public int getCosteSuscripcion () { return _costeSuscripcion; }1819 public void suscribir (Suscriptor s) throws

SuscriptorExistenteException {20 if (_suscriptores.pertenece(s))21 throw new SuscriptorExistenteException();22 else23 _suscriptores.insertar(s);24 }2526 public void eliminarSuscripcion (Suscriptor s) {27 _suscriptores.eliminar(s);28 }2930 public ListaRecursiva<Suscriptor> getSuscriptores () {31 return _suscriptores;32 }3334 }

Page 38: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

[84] CAPÍTULO 4. LISTAS

Como se puede apreciar en el anterior listado de código, la claseCanalDeNoticias tiene cuatro variables de clase:

El tipo de noticias que gestiona el canal _tipo, de tipo TipoDeNoti-cia.

El coste de suscripción a dicho canal _costeSuscripción, de tipoentero.

La lista de suscriptores _suscriptores, de tipo Suscriptor, asocia-dos a un determinado canal.

La lista de noticias _noticias, de tipo Noticia, que gestiona y pu-blica el canal.

En este punto, es importante destacar, aunque el enunciado lo es-pecifica, que el coste de suscripción a un canal de noticias es inde-pendiente del número de noticias que publica. Así, un usuario sólonecesita suscribirse una única vez y, por lo tanto, para el coste sólouna vez, para recibir las noticias de un determinado tipo.

El método más relevante expuesto en el anterior listado es el mé-todo suscribir (línea

�� ��19 ), responsable de asociar un suscriptor a uncanal de un determinado tipo. Este método comprueba que el suscrip-tor pasado como parámetro no está ya suscrito al canal (línea

�� ��20 ). Enese caso, simplemente lo añade a la lista de suscriptores (línea

�� ��23 ).En caso contrario, lanza una excepción del tipo SuscriptorExistenteEx-ception.

En el siguiente listado de código se completa la implementación dela clase CanalDeNoticias con la funcionalidad relativa a la publicaciónde noticias y a la generación del top5 de noticias interesantes por tipo.

Listado 4.37: Clase Canal de Noticias. Publicación y top5.

1 public class CanalDeNoticias {23 // ...45 public void publicar (Noticia n) throws

NoticiaYaPublicadaException {67 if (_noticias.pertenece(n))8 throw new NoticiaYaPublicadaException();9 else

10 _noticias.insertar(n);1112 if (_tipo.equals(n.getTipo()))13 for (int i = 1; i <= _suscriptores.tamanyo(); i++)14 try {15 _suscriptores.elemento(i).recibirNoticia(n);16 }17 catch (IndiceFueraDeRangoException exc) {}18 catch (ColaLlenaException exc) {}19 }2021 public ListaRecursiva<Noticia> getTop5 () {22 ListaOperaciones.ordenar(_noticias);23 ListaOperaciones.invertir(_noticias);2425 return ListaOperaciones.sublista(_noticias, 5);26 }2728 }

Page 39: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

4.7. Problemas y Soluciones [85]

El método publicar (línea�� ��5 ) es responsable de enviar una noticia

a todos los suscriptores asociados al canal. Para ello, y siempre quela noticia no haya sido previamente publicada (líneas

�� ��7-10 ), recorrerála lista de suscriptores (línea

�� ��13 ) para invocar el método recibirNoticiade la clase Suscriptor (línea

�� ��15 ). En este método se puede apreciarcómo se usa la funcionalidad de la clase lista para insertar elementos(método insertar línea

�� ��10 ), obtener la longitud de la misma (métodotamanyo línea

�� ��13 ) y acceder a cada uno de sus elementos (métodoelementoN línea

�� ��15 ) para realizar una determinada acción sobre ellos.

Por otra parte, el método getTop5 permite obtener las cinco noticiasmás interesantes de un determinado tipo. Recuerde que cada canal es-tá asociado a un único tipo de noticias. Por lo tanto, el lugar natura dedicho método se encuentra en la clase CanalDeNoticias. Básicamen-te, en este método se ordena la lista de noticias de mayor a menorinterés (líneas

�� ��22-23 ), para obtener posteriormente las 5 primeras ydevolverlas como una sublista (línea

�� ��25 ). Note cómo este método de-lega la funcionalidad requerida para ordenar y obtener la sublista enla clase ListaOperaciones, abstrayéndose de la implementación de lamisma. En este contexto, se supone, por ejemplo, que si no hay 5 no-ticias interesantes asociadas a un canal, el método sublista delvoverásimplemente las n primeras, 0 ≤ n < 5.

A continuación se muestra la última de las clases desarrolladas,la clase SistemaPublicacion. Esta clase representa la interfaz entre lossuscriptores y el sistema software, es decir, cuando un usuario quieresuscribirse a un determinado tipo de noticias, entonces recurrirá aesta clase que, a su vez, delegará en el canal de eventos adecuado.

Listado 4.38: Clase Sistema de Publicación.

1 import listas.*;23 public class SistemaPublicacion {45 ListaRecursiva<CanalDeNoticias> _canales;67 public SistemaPublicacion () {8 _canales = new ListaRecursiva<CanalDeNoticias>();9 }1011 public void anyadirCanal (TipoDeNoticia tipo, int

costeSuscripcion) {12 _canales.insertar(new CanalDeNoticias(tipo, costeSuscripcion));13 }1415 public ListaRecursiva<CanalDeNoticias> getCanales () {16 return _canales;17 }1819 public void suscribir (Suscriptor s, TipoDeNoticia t) throws

SuscriptorExistenteException,CanalDeNoticiasNoExistenteException {

20 try {21 this.getCanalPorTipo(t).suscribir(s);22 }23 catch (SuscriptorExistenteException exc) {24 throw exc;25 }26 catch (CanalDeNoticiasNoExistenteException exc) {27 throw exc;28 }29 }3031 }

Page 40: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

[86] CAPÍTULO 4. LISTAS

El estado de la clase SistemaPublicacion queda definido exclusiva-mente por una lista de canales de noticias _canales, de tipo Lista-Recursiva<CanalDeNoticias> (línea

�� ��5 ). En la solución propuesta se haconsiderado que cada canal de noticias gestiona su propia lista de sus-criptores, sin que exista la necesidad de mantener un registro global anivel de sistema. Esta aproximación simplifica el diseño y es suficientepara cumplir con los requisitos planteados por el enunciado. Sin em-bargo, en una solución más real y menos didáctica se habría optadopor mantener un registro global de suscriptores a nivel de sistema conpropósitos administrativos, mientras que a nivel funcional cada ca-nal de noticias tendría su propia lista de suscriptores con propósitosfuncionales.

Finalmente, el siguiente listado de código muestra la funcionalidadasociada al cálculo de costes, tanto a nivel de suscriptor como a nivelglobal. Esta funcionalidad ha de estar situada en la clase Sistema, yaque desde la misma es posible acceder desde un punto de vista globala los canales a los que un determinado usuario se encuentra suscrito.

El método getCosteSuscripcion, que acepta un suscriptor s comoparámetro de entrada, comprueba a qué canales de eventos pertenecedicho suscriptor para calcular el coste de suscripción de s. Para ello,se recorre la lista de canales (línea

�� ��10 ) y se comprueba en cada itera-ción del bucle si el suscriptor s pertenece al canal i (línea

�� ��12 ). En talcaso, se acumula el coste de suscripción al canal i en el coste total delsuscriptor (línea

�� ��13 ).

Listado 4.39: Clase Sistema de Publicación. Cálculo de costes.

1 import listas.*;23 public class SistemaPublicacion {45 // ...67 public int getCosteSuscripcion (Suscriptor s) {8 int coste = 0;9

10 for (int i = 1; i <= _canales.tamanyo(); i++)11 try {12 if (_canales.elemento(i).getSuscriptores().pertenece(s))13 coste += _canales.elemento(i).getCosteSuscripcion();14 }15 catch (IndiceFueraDeRangoException exc) {}1617 return coste;18 }1920 public int getCosteTotalSuscripcion () {21 int coste = 0;2223 for (int i = 1; i <= _canales.tamanyo(); i++)24 try {25 coste += (_canales.elemento(i).getSuscriptores().tamanyo()

* _canales.elemento(i).getCosteSuscripcion());26 }27 catch (IndiceFueraDeRangoException exc) {}2829 return coste;30 }3132 }

Page 41: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

4.7. Problemas y Soluciones [87]

Por otra parte, el método getCosteTotalSuscripcion calcula los bene-ficios a nivel de sistema, teniendo en cuenta todos los usuarios suscri-tos a todos los canales. Para ello, simplemente es necesario obtener lalongitud de la lista de usuarios de cada canal, multiplicar su longitudpor el coste de suscripción del canal y acumular la relación coste/be-neficio parcial para obtener el resultado final (línea

�� ��25 ).

Consideraciones finales

La solución discutida para el sistema de publicación de noticiasrepresenta una de las diversas opciones posibles para abordar la pro-blemática planteada. Probablemente, el diseño de clases propuesto nodeja margen para plantear demasiadas variaciones. Sin embargo, síque es posible manejar otras opciones relativas a la funcionalidad de-mandada por el enunciado.

Por ejemplo, el cálculo de costes se puede delegar perfectamen-te en la clase Suscriptor en lugar de plantearlo en la clase Sistema-Publicacion. Básicamente, cada vez que un suscriptor se suscribe aun determinado canal de noticias, sería posible actualizar su estadocon el nuevo coste, asociado al nuevo canal. Esta decisión implicaríaincluir una nueva variable en la clase Suscriptor. Esta aproximacióntiene la ventaja de que para obtener el coste de suscripción de undeterminado usuario, simplemente sería necesario invocar el métodogetCosteSuscripcion sobre el mismo. Por otra parte, para calcular elcoste de suscripción total a nivel de sistema, y suponiendo un registroglobal a nivel de sistema, simplemente habría que recorrer esa lista desuscriptores y acumular el coste de suscripción de los mismos en unvariable.

Otra cuestión a discutir reside en si utilizar una lista ordenada ono para almacenar las noticias de un determinado tipo, es decir, lasnoticias asociadas a un canal de noticias en particular, considerandoespecialmente la funcionalidad asociada a obtener el top5 (que requie-re ordenar las noticias por el número de veces que son interesantes).La solución planteada considera el uso de una lista no ordenada, he-cho que implica ordenar para obtener el top5. Esta decisión se justificadebido a que la obtención del top5 es una acción que los suscriptoressolicitarían puntualmente, mientras que el grueso de la funcionalidadde listas se centra en el acceso y la modificación de las mismas. En es-te caso particular, se puede suponer que el uso de una lista ordenadatendría un impacto negativo en el rendimiento general, aunque seríabeneficioso a la hora de obtener el top5.

Page 42: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

[88] CAPÍTULO 4. LISTAS

4.7.3. Sistema de afinidad entre usuarios

Enunciado

Un grupo de amigos informáticos, emprendedores y cinéfilos, estánplanteándose el diseño de un sistema que permita a los usuarios sus-critos al mismo la recomendación de películas entre dichos usuarios,en base a la afinidad o gustos compartidos que tengan entre sí. Ca-da película tiene asociada un título, año, género y actores de repartoprincipales.

Un usuario, además de tener asociado un dni y un nombre, man-tiene una serie de preferencias a nivel individual. Una preferencia con-siste en vincular un determinado género de películas a un valor nu-mérico, entre 1 y 10. Por ejemplo, un usuario puede asignar un valorde 5 al género Drama y un valor de 9 al género Terror.

El grado de afinidad entre dos usuarios se calcula atendiendo a unumbral numérico común a todos los usuarios. Así, si Luis y Juan tie-nen asignado un 8 y un 9 al género de Terror, respectivamente, y elvalor umbral común a todos los usuarios es 7, entonces el grado deafinidad de Luis hacia Juan sumará un punto. Si además compartenla preferencia Comedia con un valor de 10, superior al umbral 7, en-tonces sumarán otro punto a su grado de afinidad. Si por el contrariocomparten Drama, con valores de 5 y 8, respectivamente, entonces esegénero no se tendrá en cuenta para incrementar su grado de afinidad(al ser 5 menor que 7).

Un usuario mantiene una serie de recomendaciones hechas porotros usuarios. Básicamente, una recomendación consiste en una pe-lícula recomendada por otro usuario, junto con el grado de afinidadentre el usuario que recomienda y el usuario al que se le recomiendauna película. Así, cuando a un usuario le recomiendan una película,éste puede actualizar las recomendaciones considerando su afinidadcon el usuario que recomienda, estableciendo un criterio para poderver películas cuando disponga de tiempo. Un usuario verá primerouna película recomendada por alguien que tiene una mayor afinidadcon él, de manera independiente al género de la película.

Considerando este supuesto se pide plantear la solución a los si-guientes apartados:

A. Diseñe las estructuras de datos necesarias para solucionar elproblema.

B. Implemente el código necesario para calcular la afinidad entredos usuarios. ¿En qué clase lo incluiría?

C. Implemente el código necesario para comparar recomendaciones.¿En qué clase lo incluiría?

Solución

Al igual que en los problemas anteriores, se propone al lector laidentificación de las clases potenciales que conforman la solución alproblema propuesto, en base a la definición de un estado asociado auna determinada funcionalidad.

El propio enunciado sugiere, de manera directa, la existencia deentidades importantes cuyo modelado resultará esencial para obte-ner una solución. Por ejemplo, Película se puede modelar como una

Page 43: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

4.7. Problemas y Soluciones [89]

clase, ya que servirá para almacenar el estado asociado al stock delsistema y también estará de algún modo vinculado al concepto de Re-comendación. El concepto de usuario también puede hacer evidente lanecesidad de una clase específica para dicha entidad.

A continuación se enumeran las clases que formarán parte de lasolución propuesta para este problema:

Película, cuyo estado queda definido por el título, el año de pro-ducción, el género asociado a la película y el reparto, es decir, losactores que trabajan en dicha película. Como se discutirá másadelante, el reparto de ha modelado utilizando una lista.

Usuario, cuyo estado viene definido de manera explícita en elsegundo párrafo del enunciado y que consiste en el DNI, el nom-bre y una serie preferencias que determinan su predilección porciertos géneros de películas. Las preferencias se han modeladoutilizando una tabla hash donde la clave es el género y el valorconsiste en un entero que sirve para especificar la predilecciónpor un género en particular. Un aspecto importante de esta claseserá la posibilidad de calcular el grado de afinidad entre usuarios.

Clave

Mapping

Valor

Figura 4.6: El proceso de ob-tener el valor a partir de unaclave en una tabla hash sedenomina comúnmente map-ping.

Recomendación, cuyo estado es explícito en el enunciado y es-tá compuesto por el usuario que recomienda, la propia películarecomendada y el grado de afinidad entre los dos usuarios (el re-comendado y el que recomienda). En esta clase se ha definidoun criterio para comparar de manera explícita varias recomen-daciones, de forma que sirva como base para ordenar la lista derecomendaciones asociadas a un usuario.

Sistema, que contendrá la lista de usuarios registrados en el mis-mo y la lista de películas que conforman su catálogo.

El concepto de género se ha modelado utilizando un tipo enume-rado, ya que sólo sirve para especificar un valor entre una serie devalores constantes. En otras palabras, este concepto no tiene asocia-da una funcionalidad específica y, por lo tanto, se ha descartado comoposible clase del diseño planteado. Así mismo, el uso de un tipo enu-merado para el propio género es un planteamiento eficiente ya que seutilizará como clave en la tabla de preferencias de un usuario.

En la solución planteada, el concepto de actor se ha modeladomediante una cadena de texto, ya que no es relevante para el diseñode dicha solución y el enunciado no especifica un estado complejo másallá de una identificación textual.

A continuación, la figura 4.7 muestra el diagrama de clases querefleja la solución planteada para el problema propuesto.

Como se puede apreciar en la figura anterior, el sistema mantieneuna lista de usuarios suscritos y una lista de películas que conformanel stock del sistema. Por otra parte, tal y como se discute en el enun-ciado, el usuario mantiene una lista de recomendaciones, por lo queexiste una relación directa entre usuario y recomendación. Del mismomodo, y debido a que una recomendación siempre está asociada a unapelícula, entonces existe una relación entre ambas clases.

Resulta importante destacar que las recomendaciones son perso-nalizadas, debido a que están asociadas al usuario que realiza la re-comendación y al nivel de afinidad entre el usuario que recomiendala película y el usuario al que se le recomienda la película. Esta clase

Page 44: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

[90] CAPÍTULO 4. LISTAS

PelículaSistema1 1..*

RecomendaciónUsuario

1

1..*

1

0..*

1 0..*

Figura 4.7: Diagrama de clases para el problema del sistema de afinidad de usuarios.

resulta muy interesante para facilitar la mantenibilidad del sistema yaque permite relacionar película y usuario.

A continuación se muestra el listado de código de la clase Película,la cual no depende de otras clases en el diseño propuesto. Como puntodestacable, esta clase hace uso de la implementación de listas paragestionar los actores que conforman el reparto de la misma.

Listado 4.40: Clase Película.

1 package afinidad;23 import listas.*;45 public class Pelicula {67 private String _titulo;8 private int _anyo;9 private Genero _genero;

10 private ListaRecursiva<String> _actores;1112 public Pelicula (String titulo, int anyo, Genero genero) {13 _titulo = titulo;14 _anyo = anyo;15 _genero = genero;16 _actores = new ListaRecursiva<String>();17 }1819 public String getTitulo () { return _titulo; }20 public int getAnyo () { return _anyo; }21 public Genero getGenero () { return _genero; }2223 public ListaRecursiva<String> getActores () {24 return _actores;25 }2627 public void anyadirActor (String actor) {28 _actores.insertarFinal(actor);29 }3031 }

Page 45: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

4.7. Problemas y Soluciones [91]

Para modelar el género asociado a una película se ha optado pordefinir una enumeración, tal y como se muestra en el siguiente listadode código.

Listado 4.41: Enumeración Género.

1 package afinidad;23 public enum Genero {ACCION, DRAMA, COMEDIA, TERROR, THRILLER};

En caso de que sea necesario añadir un nuevo tipo, simplementees necesario extender la definición de dicha enumeración. Una posibleopción para evitar la recompilación de código hubiera sido obtener lainformación de los distintos géneros a partir de un archivo de texto a lahora de arrancar el sistema. Sin embargo, esta alternativa implicaríael procesamiento de archivos mediante algún parser.

Una de las clases esenciales del diseño planteado es la clase Usua-rio, encargada de mantener el estado básico indicado en el enunciado(DNI y nombre) junto con las preferencias y las recomendaciones. Elsiguiente listado de código muestra las variables miembro de dichaclase y la implementación del constructor. Note cómo se hace uso dela clase Hashtable de java.util para gestionar las preferencias de unusuario. En realidad, dichas preferencias son una secuencia de pares<Género, Entero>.

Listado 4.42: Clase Usuario. Método constructor.

1 package afinidad;23 import listas.*;4 import java.util.Hashtable;5 import java.util.Enumeration;67 public class Usuario {89 protected int _dni;10 protected String _nombre;11 protected Hashtable<Genero, Integer> _preferencias;12 protected ListaOrdenada<Recomendacion> _recomendaciones;1314 protected static int _umbralAfinidad = 8;1516 public Usuario (int dni, String nombre) {17 _dni = dni;18 _nombre = nombre;19 _preferencias = new Hashtable<Genero, Integer>();20 _recomendaciones = new ListaOrdenada<Recomendacion>();21 }2223 // Se omite el resto del código.2425 }

Esta clase incluye el valor umbral de afinidad a partir del cual seconsidera que dos usuarios, siempre y cuando compartan un género,sienten predilección por un determinado género. Dicho valor se hadefinido como 8 en una escala de 1 a 10.

Es importante destacar que las recomendaciones asociadas a unusuario se mantienen en una lista ordenada, ya que se parte de la su-posición de que habrá un bajo número de inserciones y eliminaciones,situación en la que una lista hubiera sido más eficiente. En este caso,simplemente habría que ordenar la lista u obtener la recomendación

Page 46: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

[92] CAPÍTULO 4. LISTAS

con la mayor afinidad en dicho contenedor. Posteriormente se discuti-rán otras opciones de diseño para manejar las recomendaciones.

Con el objetivo de facilitar la interacción con las preferencias aso-ciadas a un usuario, se ha optado por la implementación de algunosmétodos que envuelven la interfaz básica de la clase Hashtable paraañadir una nueva preferencia y obtener la afinidad de un usuario apartir de un determinado género. En el siguiente listado también seincluye un método trivial para añadir una nueva recomendación a lalista de recomendaciones.

Listado 4.43: Clase Usuario. Métodos de acceso.

1 public class Usuario {23 // Se omite parte del código fuente.45 public void anyadirPreferencia (Genero genero, int afinidad) {6 _preferencias.put(genero, afinidad);7 }89 public void anyadirRecomendacion (Recomendacion p) {

10 _recomendaciones.insertar(p);11 }1213 public int getAfinidadGenero (Genero genero) {14 if (_preferencias.containsKey(genero))15 return _preferencias.get(genero);16 else // Mínima afinidad posible.17 return 1;18 }1920 }

En el método getAfinidadGenero() se asume la mínima afinidad paraun género no contemplado en los gustos de un usuario. En este caso,se considera un valor númerico de 1. Este método se utiliza en la claseUsuario con el objetivo de obtener la afinidad entre dos usuarios. Laimplementación de este método se expone a continuación.

Listado 4.44: Clase Usuario. Método afinidadUsuario().

1 public class Usuario {23 // Se omite parte del código fuente.45 public int afinidadUsuario (Usuario u) {6 int afinidad = 0;7 Enumeration generos = _preferencias.keys();89 // Para cada género del usuario...

10 while (generos.hasMoreElements()) {11 Genero it = (Genero)generos.nextElement();1213 // Coinciden en el género?14 // Afinidad >= Umbral?15 if ((u.getAfinidadGenero(it) >= _umbralAfinidad)16 &&17 (_preferencias.get(it) >= _umbralAfinidad))18 afinidad++;19 }2021 return afinidad;22 }2324 }

Page 47: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

4.7. Problemas y Soluciones [93]

Como se puede apreciar, el anterior método permite obtener el gra-do de afinidad entre dos usuarios: el representado en la propia defini-ción de la clase y el del usuario pasado por parámetro. Para calcularel grado de afinidad entre usuarios, simplemente hay que comprobarlas preferencias que comparten, es decir, los géneros sobre los que tie-nen definidos de manera individual alguna preferencia. Si compartenun género, entonces se comprueba si los valores de afinidad de losdos usuarios es mayor que el umbral definido a nivel global. En estecaso, se incremente el valor de afinidad entre usuarios. Si no es así,simplemente no se incrementa.

En el caso de que dos usuarios no compartan un género, entoncesno se incrementará el nivel de afinidad ya que el método getAfinidad-Genero devolverá el valor mínimo 1 para dicho género.

El recorrido de la tabla hash se realiza utilizando el método next-Element(), responsable de obtener un elemento de dicha estructura yavanzar el iterador hasta el siguiente elemento (si existiera). Este pro-cedimiento se realizará mientras queden preferencias por recorrer, esdecir, mientras el método hashMoreElements() devuelva un valor lógicode verdadero.

En esta clase se implementan los métodos equals() y toString(). Am-bos se muestran en el siguiente listado de código.

Listado 4.45: Clase Usuario. Métodos equals() y toString().

1 public class Usuario {23 // Se omite parte del código fuente.45 public boolean equals (Object obj) {6 return ((obj instanceof Usuario)7 &&8 (_dni == ((Usuario)obj)._dni) || (this == obj));9 }1011 public String toString () {12 String aux = "Usuario ";13 aux += (_nombre + "\n");14 aux += "\tPreferencias: ";1516 Enumeration generos = _preferencias.keys();17 while (generos.hasMoreElements()) {18 Genero it = (Genero)generos.nextElement();19 aux += it; aux += ":";20 aux += _preferencias.get(it); aux += " ";21 }2223 aux += "\n\tRecomendaciones: ";2425 aux += _recomendaciones;2627 return aux;28 }2930 }

Según el enunciado, una recomendación consiste en una películarecomendada por otro usuario, junto con el grado de afinidad entreel usuario que recomienda y el usuario al que se le recomienda unapelícula. Dicho estado se refleja en la definición de la clase Recomen-dación, cuyo código fuente se muestra a continuación.

Page 48: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

[94] CAPÍTULO 4. LISTAS

Listado 4.46: Clase Recomendación.

1 package afinidad;23 public class Recomendacion implements Comparable<Recomendacion> {45 private Pelicula _pelicula;6 private Usuario _usuarioRecomienda;7 private int _afinidad;89 public Recomendacion (Pelicula pelicula, Usuario usuario, int

afinidad) {10 _pelicula = pelicula;11 _usuarioRecomienda = usuario;12 _afinidad = afinidad;13 }1415 // El criterio para comparar recomendaciones16 // reside en la afinidad con el usuario17 // que recomienda la película.18 public int compareTo (Recomendacion rec) {19 if (_afinidad > rec._afinidad)20 return -11;21 else22 if (_afinidad == rec._afinidad)23 return 0;24 else25 return 1;26 }2728 public String toString () {29 String aux = "";3031 aux += "["; aux += _pelicula.getTitulo();32 aux += (" (" + _afinidad + ")]");3334 return aux;35 }3637 }

La parte más relevante del listado de código anterior está en elmétodo compareTo(), el cual define el criterio de comparación entre dosrecomendaciones. Dicho criterio reside en la afinidad con el usuarioque recomienda una película. De este modo se plantea un mecanismosencillo para mantener ordenada la lista de recomendaciones asociadaa un determinado usuario, satisfaciendo así el requisito impuesto porel enunciado en el que se establece que un usuario visualizará primerola película asociada a una recomendación cuyo grado de afinidad conel usuario que la recomienda sea mayor.

Finalmente, la clase Sistema simplemente mantiene la lista deusuarios y el stock de películas. Además, esta clase proporciona lafuncionalidad necesaria para gestionar dichas listas.

Page 49: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

4.7. Problemas y Soluciones [95]

Listado 4.47: Clase Sistema.

1 package afinidad;23 import listas.*;45 public class Sistema {67 ListaRecursiva<Usuario> _usuarios;8 ListaRecursiva<Pelicula> _catalogo;910 public Sistema () {11 _usuarios = new ListaRecursiva<Usuario>();12 _catalogo = new ListaRecursiva<Pelicula>();13 }1415 public void suscribir (Usuario usuario) {16 if (!_usuarios.pertenece(usuario))17 _usuarios.insertarFinal(usuario);18 }1920 public void eliminarSuscripcion (Usuario usuario) {21 _usuarios.eliminar(usuario);22 }2324 public ListaRecursiva<Usuario> getUsuarios () {25 return _usuarios;26 }2728 public ListaRecursiva<Pelicula> getCatalogo () {29 return _catalogo;30 }3132 public void anyadirPelicula (Pelicula nueva) {33 if (!_catalogo.pertenece(nueva))34 _catalogo.insertarFinal(nueva);35 }3637 public void eliminarPelicula (Pelicula p) {38 _catalogo.eliminar(p);39 }4041 }

Consideraciones finales

Una posible alternativa al diseño planteado hubiera sido modelarlas recomendaciones de un usuario mediante una cola con prioridad,en lugar de utilizar una lista ordenada. En función de la implementa-ción de estas estructuras de datos y de evaluar la tasa de insercionesy eliminaciones en las mismas, sería más eficiente utilizar una alter-nativa u otra.

Para manejar las preferencias de un usuario se podría haber op-tado por un array estático. Sin embargo, esta alternativa de diseñopresenta diversas desventajas frente a una de naturaleza dinámica,como es el caso de la tabla hash. Por otra parte, la definición explíci-ta de una estructura para los géneros incrementa el nivel semánticode la solución planteada. En el caso de utilizar un array, habría quemantener algún tipo de tabla para asociar el identificador numéricousado para indexar el array y la representación textual del género encuestión.

Page 50: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

[96] CAPÍTULO 4. LISTAS

4.8. Problemas Propuestos

Problema 1

Implemente la operación ultima_posicion, que devuelve la últimaposición en la que aparece un cierto elemento en una lista. ¿Dóndeincluiría su implementación? ¿Por qué?

Problema 2

Implemente la operación contar, que devuelve el número de vecesque un determinado elemento pasado como parámetro aparece en unalista. ¿Dónde incluiría su implementación? ¿Por qué?

Problema 3

Implemente la operación es_mayor, que devuelve un valor lógico enfunción de si el número de elementos de una lista es mayor que el deotra. ¿Dónde incluiría su implementación? ¿Por qué?

Problema 4

Implemente la operación equivalentes, que devuelve un valor lógicoen función de si dos listas tienen los mismos elementos, aunque seaen distinto orden. ¿Dónde incluiría su implementación? ¿Por qué?

Problema 5

Implemente la operación es_sublista, que devuelve un valor lógicoen función de si una lista está contenida en otra. ¿Dónde incluiría suimplementación? ¿Por qué?

Problema 6

Utilice listas para representar números en sistema binario. Imple-mente un método recursivo que sume uno a un número binario.

Problema 7

Una comunidad tiene una serie de provincias, cada una de ellascompuestas por un conjunto de pueblos que poseen a su vez unaspersonas empadronadas en ellos. El sistema a desarrollar debe sercapaz de ofrecer la siguiente información:

A. Mostrar todos los pueblos de mas de x habitantes de una comu-nidad o provincia.

B. Listar todos los habitantes mayores de 18 años de una comuni-dad, provincia o pueblo.

C. Contar el numero de personas de una comunidad, provincia opue-blo.

A partir de los requisitos previamente comentados, solucione lossiguientes apartados.

(1). Realice un gráfico explicativo del diseño general de la soluciónpropuesta.

(2). Diseñe e implemente las estructuras de datos necesarias pararesolver este problema.

(3). Diseñe e implemente los algoritmos que resuelven los apartadosa), b).

Page 51: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

4.8. Problemas Propuestos [97]

Problema 8

Se desea implementar un sistema software para organizar biblio-tecas musicales. Para ello, se distingue de cada disco los siguienteselementos: el tipo de música al que pertenece, el autor, el título deldisco y año de publicación, y los títulos de las canciones y su dura-ción. El sistema debe ser capaz de ofrecer la siguiente información:

A. Toda la información de todos los discos, organizada primero portipo de música, dentro de cada tipo de música por autor y, porcada autor, sus discos ordenados por año de publicación.

B. Para completar la grabación de un disco falta un tiempo t. Loca-lice la canción de un autor determinado, cuya duración se ajusteal máximo a ese tiempo t, sin pasarse.

A partir de los requisitos previamente comentados, solucione lossiguientes apartados.

(1). Realice un gráfico explicativo del diseño general de la soluciónpropuesta.

(2). Diseñe e implemente las estructuras de datos necesarias pararesolver este problema.

(3). Diseñe e implemente los algoritmos que resuelven los apartadosa), b).

Problema 9

Se desea realizar un programa de atención al usuario en la Feriadel Libro. Como sabrá, la feria la integran una serie de casetas, cadauna de las cuales ofrece la posibilidad de comprar una cierta cantidadde libros.

Por otra parte, a cada visitante se le entrega un catálogo con losnombres de los autores que firmarán libros y a qué hora. El programadebe ser capaz de ofrecer al visitante la siguiente información:

A. Qué libros hay en una determinada caseta.

B. En qué casetas se puede comprar un libro determinado.

C. A qué horas firma ejemplares de su último libro publicado unautor y en qué caseta.

D. Qué autores firman libros.

E. Los 10 libros más vendidos al final de la feria.

A partir de los requisitos previamente comentados, solucione lossiguientes apartados.

(1). Realice un gráfico explicativo del diseño general de la soluciónpropuesta.

(2). Diseñe e implemente las estructuras de datos necesarias pararesolver este problema.

(3). Diseñe e implemente los algoritmos que resuelven los apartadosa)-e).

Page 52: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

[98] CAPÍTULO 4. LISTAS

Problema 10

Se desea crear un sistema software necesario para representar lainformación almacenada en la biblioteca de una facultad, correspon-diente a los libros y a los usuarios de la misma.

De cada libro, se desea tener acceso a su título, autor, editorial,año de publicación, número de ejemplares, signatura (única y dife-rente para cada ejemplar de cada libro) y si está disponible o no. Encaso de que esté prestado, hay que saber la fecha de préstamo, la dedevolución y los datos del usuario que lo tiene. Si está disponible, semostrará la identificación de la estantería en la que se puede encon-trar.

Por otro lado, los usuarios de la biblioteca pueden ser alumnos yprofesores de la facultad, de los que se almacenará su nombre, ape-llidos, dirección, DNI, el máximo número de libros que pueden tenerprestados (distinto para alumnos que para profesores) y la lista delibros prestados.

Diseñe e implemente un método para que, dada una persona, mues-tre los datos de los libros que tiene prestados.

A partir de los requisitos previamente comentados, solucione lossiguientes apartados.

(1). Realice un gráfico explicativo del diseño general de la soluciónpropuesta.

(2). Diseñe e implemente las estructuras de datos necesarias pararesolver este problema.

(3). Diseñe e implemente los algoritmos que proporcionen la funcio-nalidad necesaria para el sistema software planteado.

Problema 11

Se desea crear un sistema software necesario para representar lainformación almacenada en la biblioteca de una facultad, correspon-diente a los libros y a los usuarios de la misma.

De cada libro, se desea tener acceso a su título, autor, editorial,año de publicación, número de ejemplares, signatura (única y dife-rente para cada ejemplar de cada libro) y si está disponible o no. Encaso de que esté prestado, hay que saber la fecha de préstamo, la dedevolución y los datos del usuario que lo tiene. Si está disponible, semostrará la identificación de la estantería en la que se puede encon-trar.

Por otro lado, los usuarios de la biblioteca pueden ser alumnos yprofesores de la facultad, de los que se almacenará su nombre, ape-llidos, dirección, DNI, el máximo número de libros que pueden tenerprestados (distinto para alumnos que para profesores) y la lista delibros prestados.

Diseñe e implemente un método para que, dada una persona, mues-tre los datos de los libros que tiene prestados.

A partir de los requisitos previamente comentados, solucione lossiguientes apartados.

(1). Realice un gráfico explicativo del diseño general de la soluciónpropuesta.

Page 53: Capítulo Listas - Escuela Superior de Informática … · caso de C++ y la biblioteca STL ... Las listas doblemente enlazadas o las listas circu-lares son algunos casos. 4.4. Implementación

4.8. Problemas Propuestos [99]

(2). Diseñe e implemente las estructuras de datos necesarias pararesolver este problema.

(3). Diseñe e implemente los algoritmos que proporcionen la funcio-nalidad necesaria para el sistema software planteado.