Está en la página 1de 20

FUNDAMENTOS DE PROGRAMACIÓN Versión: 0.0.

Tema 9. Colecciones II

Autores: José C. Riquelme y Carlos A. García Vallejo


Revisiones: Toñi Reina Quintero
Tiempo estimado: 4 horas

1. El tipo Map .................................................................................................................................................. 1


1.1 Definición ............................................................................................................................................ 1
1.2 Métodos del tipo Map ........................................................................................................................ 3
1.3 El tipo Map.Entry ................................................................................................................................ 4
1.4 Inicialización de un objeto de tipo Map ............................................................................................. 5
2. El tipo SortedMap ....................................................................................................................................... 7
2.1 Definición ............................................................................................................................................ 7
2.2 Métodos .............................................................................................................................................. 8
3 La librería Guava ......................................................................................................................................... 9
4 Factorías para los tipos List, Set, SortedSet, Map y SortedMap ............................................................. 10
4.1 Factoría de Guava para el tipo List ................................................................................................... 10
4.2 Factoría de Guava para los tipos Set y SortedSet ............................................................................ 11
4.3 Factoría Guava para los tipos Map y SortedMap ............................................................................ 12
5. El tipo Multiset .......................................................................................................................................... 13
6. El tipo Multimap ....................................................................................................................................... 16
7. Ejercicios propuestos ................................................................................................................................ 19

1. El tipo Map

1.1 Definición
El tipo de dato Map, incluido en el paquete java.util, permite modelar el concepto de aplicación: una
relación entre los elementos de dos conjuntos de modo que a cada elemento del conjunto inicial le
corresponde uno y solo un elemento del conjunto final. Los elementos del conjunto inicial se denominan
claves (keys) y los del conjunto final valores (values).

En la figura 1 puede verse un ejemplo de Map. En este caso las claves son cadenas de caracteres, cada una
de las cuales representa un valor numérico en diversas representaciones textuales, y los valores son enteros.
Cada cadena tiene asociada el valor numérico correspondiente.
2 Fundamentos de Programación

“uno”

“1” 1
“I”

“1.0”
“2”
2
“II”

“1+1”

Figura 1. Modelo de un Map

Este mismo ejemplo se podría representar como una tabla con dos columnas:

Clave Valor
“uno” 1
“1” 1
“I” 1
“1.0” 1
“2” 2
“II” 2
“1 + 1” 2

Vemos que la tabla refleja la misma información que el diagrama anterior.

Como entre las claves no puede haber elementos duplicados (una clave no puede estar asociada con más de
un valor), las claves forman un conjunto (Set). Sin embargo sí que puede haber valores duplicados, por los
que estos están en una Collection.

Por tanto, un Map está definido por un conjunto de claves, una colección de valores y un conjunto de pares
clave-valor (también llamadas entradas), que son realmente los elementos de los que está compuesto.

Dentro de la jerarquía de tipos de Java, Map no extiende a Collection ni a Iterable. Por tanto, no se puede
aplicar un for extendido sobre sus elementos, es decir, sobre sus pares, salvo que accedamos explícitamente
a ellos. Los Map se utilizan en muchas situaciones. Por ejemplo, los listines telefónicos (clave: nombre del
contacto, valor: número de teléfono), listas de clase (clave: nombre del alumno, valor: calificaciones), ventas
de un producto (clave: código de producto, valor: total de euros de recaudación), índices de palabras por
páginas en un libro (clave: palabra, valor: lista de números de página donde aparece), la red de un Metro
(clave: nombre de la estación, valor: conjunto de líneas que pasan por esa estación, o al revés, clave: número
de línea, valor: lista de estaciones de esa línea), etc. Tiene también muchas otras aplicaciones en
9. Colecciones II 3

informática: representar diccionarios o propiedades, almacenar en memoria tablas de bases de datos,


cachés, etc.

Centrándonos en Java, la interfaz Map tiene dos tipos genéricos, que suelen denominarse K (de Key) y V (de
Value): Map<K, V>. El Map del ejemplo anterior sería Map<String, Integer>.

La clase que implementa la interfaz Map es HashMap (aunque también se puede utilizar TreeMap, que se
verá más adelante). La clase HashMap obliga a definir de forma correcta el método hashCode del tipo de las
claves para evitar comportamientos incorrectos, como claves repetidas. Asimismo, al igual que pasa en el
tipo Set, los objetos mutables que se introducen en un Map son “vistas” o referencias a objetos y, por tanto,
si estos cambian, el Map puede dejar de ser coherente, esto es, podría tener claves repetidas.

Un Map cuyas claves sean String y cuyos valores sean Integer se definiría e inicializaría de la siguiente forma:

Map<String, Integer> m = new HashMap<String, Integer>();

1.2 Métodos del tipo Map


Los métodos del tipo Map se relacionan a continuación. La información completa sobre este tipo se puede
encontrar en la API de Java1.

void clear()
Elimina todos los elementos (pares o entradas) del Map.

boolean containsKey(Object key)


Devuelve true si el Map contiene la clave especificada.

boolean containsValue(Object value)


Devuelve true si una o más claves del Map tienen asociadas el valor especificado.

V get(Object key)
Devuelve el valor asociado con la clave especificada o null si esa clave no está
asociada con ningún valor (la clave no está en el conjunto de claves).

boolean isEmpty()
Devuelve true si el Map no contiene ningún par.

Set<K> keySet()
Devuelve un Set que es una vista de las claves que contiene el Map.

V put(K key, V value)


Asocia el valor con la clave especificada. Devuelve el valor previamente asociado con la
clave si esta ya estaba en el Map o null, en caso contrario.

1
http://docs.oracle.com/javase/7/docs/api/java/util/Map.html
4 Fundamentos de Programación

V remove(Object key)
Elimina el par que tiene como clave el parámetro especificado. Devuelve el valor
previamente asociado con la clave o null, si la clave no existía.

int size()
Devuelve el número de pares del Map.

Collection<V> values()
Devuelve una Collection que es una vista de los valores del Map.

Set<Map.Entry<K,V>> entrySet()
Devuelve una vista del conjunto de todos los pares del Map (el tipo Map.Entry se verá
más adelante).

void putAll(Map<? extends K, ? extends V> m)


Añade al Map todos los pares contenidos en m. Tiene el mismo efecto que hacer put de
todos los elementos de m.

Tanto los conjuntos devueltos por keySet y entrySet como la colección devuelta por va,lues son vistas del
Map original, por lo que las modificaciones que se realicen sobre estos repercuten en los pares almacenados
en el Map original (con las posibles repercusiones negativas) y viceversa. Hay que destacar que esos tres
objetos son iterables. El orden en el que el iterador recorre los elementos de los conjuntos (keySet y
entrySet) o la colección (values) es impredecible.

La clase HashMap tiene dos constructores: el constructor sin argumentos, que construye un Map vacío, y el
constructor con un argumento de tipo Map (constructor copia), que construye un Map con los mismos pares
que el Map que se le pasa como argumento (equivale a crear un Map vacío y aplicar putAll). Existe una clase
LinkedHashMap, que se comporta igual que HashMap excepto en que los iteradores sobre las claves, los
valores o los pares los devuelven en el orden en que se insertaron.

1.3 El tipo Map.Entry


Los pares de elementos (también llamados entradas) de los que está compuesto un Map<K, V> son de un
tipo que viene implementado por la interfaz Map.Entry<K, V>2. Esta interfaz, aparte de la operación equals
que permite comparar pares para comprobar su igualdad (y el correspondiente hashCode), tiene tres
operaciones:

K getKey()
Devuelve la clave del par.

V getValue()
Devuelve el valor del par.

V setValue(V value)
Modifica el valor del par, dándole como nuevo valor value. Devuelve el valor (segunda componente)
previo del par.

2
Más información en: http://docs.oracle.com/javase/7/docs/api/java/util/Map.Entry.html
9. Colecciones II 5

Por ejemplo, si tenemos una entrada (par) p que asocie “II” con 2, p.getKey() devuelve la cadena “II” y
p.getValue() devuelve el entero 2; si se escribe p.setValue(3), devuelve el entero 2 y modifica el valor
asociado con la clave dándole el valor 3. El toString de las entradas es de la forma clave=valor, de modo que
la representación como cadena del par resultante sería II=3.

1.4 Inicialización de un objeto de tipo Map


La algoritmia para inicializar un objeto de tipo Map siempre sigue la misma estructura, que se puede ver en
el esquema siguiente:

a. Creación del objeto (HashMap)


b. Para cada clave
c. Si la clave ya está en el Map (containsKey)
d. Obtener el valor asociado a esa clave (get)
e. Actualizar el valor u obtener nuevovalor a partir de valor
f. Si es necesario, añadir al Map el par clave, nuevovalor (put)
g. Si la clave no está en el Map
h. Inicializar valor
i. Añadir al Map el par clave, valor (put)

En el paso a. se construye el objeto tipo Map invocando al constructor de la clase HashMap. El paso b.
normalmente incluye un recorrido sobre el conjunto de claves. Para cada clave se calculará el valor
correspondiente, normalmente mediante algún tipo de cálculo u operación de acceso a una colección de
datos a partir de la clave. Una vez tenemos el par clave-valor a insertar en el Map, nos preguntamos en el
paso c. mediante el método containsKey si la clave ya estaba anteriormente en el Map.

Si la clave ya estaba, debemos obtener el valor asociado a esa clave en el Map mediante el método get.
Dependiendo de si la actualización consiste en sustituir un nuevo valor por el anterior o actualizar el ya
existente, se ejecutan los pasos e. y/o f. Por ejemplo, si el Map asocia a cada palabra una lista con los
números de las páginas de un libro donde aparece esa palabra, la actualización será añadir a la vista de la
lista que conforma el valor un nuevo elemento y, por tanto, no hace falta invocar al método put (ya que List
es un tipo mutable, y podemos hacerle modificaciones). Sin embargo, si el Map es una asociación entre el
código de un producto y sus ventas, para actualizar las ventas, al invocar al método get obtendríamos el
valor de las ventas pasadas, y a este dato se le deberían sumar las nuevas ventas. Como Double es un tipo
inmutable, la operación suma devolverá un objeto nuevo y, por tanto, deberíamos actualizar, mediante el
método put, la asociación de ese nuevo objeto de tipo Double con las ventas realizadas, y el código del
producto como clave.

Si la clave no estaba en el Map, se calcula el valor inicial en el paso h, para añadir el par clave-valor mediante
el método put en el paso i.

Ejemplo 1
Supongamos que se quiere calcular la frecuencia absoluta de aparición o número de veces que aparecen los
caracteres en un String. Para ello se define un Map cuyas claves son de tipo Character y cuyos valores son de
6 Fundamentos de Programación

tipo Integer. El método recibirá un String y devolverá un objeto de tipo Map<Character, Integer>. El código
del método sería:

public static Map<Character, Integer> contadorCarac(String frase) {


Map<Character,Integer> contador = new HashMap<Character,Integer>();

frase = frase.toUpperCase(); // todos los caracteres en mayúsculas

for (int i = 0; i < frase.length(); i++) {


Character caracter = frase.charAt(i);
if (contador.containsKey(caracter)) {
Integer valor = contador.get(caracter);
valor++;
contador.put(caracter, valor);
} else {
contador.put(caracter, 1);
}
}
return contador;
}

Nótese como el método sigue fielmente el esquema antes indicado. Se recorren los caracteres del String de
entrada mediante un for clásico. Para cada carácter se pregunta si ya está en el Map, de forma que si está, se
obtiene el valor correspondiente al número de veces que ha aparecido anteriormente, se incrementa en uno
y se vuelve a actualizar la asociación entre el carácter y el nuevo valor (un nuevo objeto, puesto que Integer
es un tipo inmutable). Al ser Integer un tipo inmutable, la llamada al método put es obligatoria. En el caso de
que el carácter no estuviera en el Map, se inicializa la frecuencia a 1.

Ejemplo 2
Supongamos que tenemos una lista de String que denominamos palabras y se quiere obtener un índice de
las posiciones que ocupan las cadenas de la lista palabras mediante una lista de enteros. El argumento de
entrada será por tanto de tipo List<String> y el argumento de salida un Map<String, List<Integer>>. Por
ejemplo, para una lista que contuviera las palabras de la cadena “la palabra que más aparece en este texto
es la palabra palabra”, la salida sería

{más=[3], texto=[7], aparece=[4], palabra=[1, 10, 11], la=[0, 9], en=[5], que=[2],
este=[6], es=[8]}

Indicando que ‘la’ está en las posiciones 0 y 9, ‘palabra’ en la 1, 10 y 11, etc. El método tendría el siguiente
código:

public static Map<String, List<Integer>> indicePal(List<String> palabras) {


Map<String,List<Integer>> indice = new HashMap<String,List<Integer>>();
int pos = 0;

for (String palabra: palabras) {


if (indice.containsKey(palabra)) {
indice.get(palabra).add(pos);
} else {
List<Integer> lis = new ArrayList<Integer>();
lis.add(pos);
9. Colecciones II 7

indice.put(palabra, lis);
}
pos++;
}
return indice;
}

Nótese que la principal diferencia con el ejercicio anterior es que no es necesario invocar al método put en el
caso de que la palabra ya esté en el índice, puesto que List es un tipo mutable. Observe que la sentencia

indice.get(palabra).add(pos);

es equivalente a

List<Integer> lis = indice.get(palabra);


lis.add(pos);

Igualmente, debe observar que incluso en este caso, tampoco es necesario invocar a put ya que lis es una
vista de la lista correspondiente y por tanto al añadir un elemento a lis, ya lo estamos haciendo a la lista
guardada en el conjunto imagen del Map.

Este problema se puede restringir a que aparezcan sólo las páginas en las que aparecen las palabras
importantes de un texto: las que se denominan palabras clave. (Por ejemplo, en este tema nos interesaría
saber en qué páginas aparecen las palabras “Map”, “HashMap”, “TreeMap”, “Guava”, etc., pero no en cuáles
aparece “en”, “la”, “las”, etc.). Supongamos que tenemos las palabras claves en un Set de String, ¿cómo
modificaría el código del método anterior para que en el Map sólo estuvieran las palabras clave?

2. El tipo SortedMap

2.1 Definición
El tipo SortedMap es un subtipo de Map en el que el conjunto de las claves está ordenado. La clase que
implementa SortedMap es TreeMap. Es necesario que el tipo de las claves tenga un orden natural (es decir,
que implemente Comparable) o que se proporcione un orden alternativo mediante un Comparator. Por
tanto, una inicialización de SortedMap como la siguiente:

SortedMap<String, List<Integer>> indice = new TreeMap<String, List<Integer>>();

Crea un SortedMap donde las claves son de tipo String y están ordenadas por el orden natural. Para crear un
SortedMap por un orden alternativo debemos invocar al constructor pasándole como argumento un
comparador:

Comparator<Libro> cmpLibroNumPag = new ComparadorLibroNumPaginas();


SortedMap<Libro, List<Integer>> mp = new TreeMap<Libro, List<Integer>>(cmpLibroNumPag);
8 Fundamentos de Programación

Las reflexiones que se hicieron para el tipo SortedSet respecto del orden definido por un Comparator siguen
siendo válidas para SortedMap. Esto es, dos claves c1 y c2 son iguales para un SortedMap si
compare(c1,c2) == 0. Por tanto, si el Comparator no rompe los empates por el orden natural, en el
SortedMap anterior sólo habría un Libro clave por cada número de páginas.

2.2 Métodos
SortedMap hereda de Map. Por tanto, todos los métodos de Map son válidos también para un objeto
SortedMap. Además, proporciona algunos métodos para manejar el orden en el conjunto de las claves como
podemos ver a continuación3:

K firstKey()
Devuelve la primera clave del Map, según el orden que tenga inducido.

K lastKey()
Devuelve la última clave del Map.

SortedMap<K,V> headMap(K toKey)


Devuelve una vista de Map con los pares cuyas claves son estrictamente inferiores a
toKey.
SortedMap<K,V> subMap(K fromKey, K toKey)
Devuelve una vista de la porción del Map cuyas claves están en el rango fromKey,
incluida, a toKey, excluida.

SortedMap<K,V> tailMap(K fromKey)


Devuelve una vista del Map con los pares que son posteriores o iguales a fromKey.
Comparator<? comparator()
super K>
Devuelve el comparador utilizado para ordenar las claves, o null si se usa el orden
natural.

Los métodos keySet, entrySet y values tienen el mismo perfil que en Map, esto es, devuelven Set, Set y
Collection, respectivamente. Los iteradores sobre ellos recorren los elementos en el orden en el que están
ordenadas las claves.

La clase TreeMap tiene cuatro constructores:


 Sin argumentos: construye un conjunto ordenado vacío que utilizará el orden natural de las claves.
 Con un argumento de tipo Comparator sobre las claves: construye un conjunto ordenado vacío que
utiliza para ordenar las claves el orden inducido por el comparador.
 Con un argumento de tipo Map: onstruye un SortedMap con los mismos pares que el Map que
recibe como argumento, pero donde las claves estarán ordenadas según el orden natural de eéstas.
 Con un argumento de tipo SortedMap: construye un SortedMap con los mismos elementos que el
que recibe como argumento y ordenado según el mismo criterio (sea el natural o uno inducido). Es el
constructor copia.

3
http://docs.oracle.com/javase/7/docs/api/java/util/SortedMap.html
9. Colecciones II 9

3 La librería Guava

Guava es una librería de programación de código abierto diseñada por ingenieros de Google que amplía la
funcionalidad de la API de Java en muchos aspectos. Incluye nuevos tipos de datos, factorías y clases de
utilidad para los tipos existentes y para los nuevos, y una amplia funcionalidad para manejar objetos
iterables. Se usará extensamente de aquí en adelante. En este tema nos vamos a centrar en los aspectos
relacionados con las Colecciones vistas hasta el momento y a introducir algunas nuevas.

La página principal desde la que puede llegarse a la documentación, código fuente, etc. es
https://code.google.com/p/guava-libraries/. En ella podemos encontrar un enlace para la descarga de la
librería (guava-14.0.jar). La versión actual es la 14.0, liberada el 25 de febrero de 2013. El procedimiento
para la instalación es el siguiente:

1. Descargamos la librería.
2. Podemos crear una copia de la librería en cada proyecto que desarrollemos o bien guardar una sola
copia y referenciarla desde todos los proyectos. Vamos a continuar con la descripción de esta
modalidad, que nos parece más operativa.
3. Movemos el archivo a una carpeta donde la tengamos bien localizada; por ejemplo, podemos guardarla
en C:/Archivos de programas/eclipse
4. En cada proyecto donde queramos utilizar Guava, pulsamos con el botón derecho del ratón sobre el
proyecto. En el menú contextual que se abre hacemos clic en “Build Path” y en el menú que se despliega
en “Add External Archives”. Navegamos por el sistema de archivos hasta encontrar la librería, la
marcamos, le damos a abrir y listo. A partir de este momento disponemos de toda la funcionalidad de la
librería.
10 Fundamentos de Programación

4 Factorías para los tipos List, Set, SortedSet, Map y SortedMap

Como hemos dicho, Guava proporciona clases factoría para la creación de estos tipos de datos. Estas clases,
además de los métodos propios de las factorías, tienen una serie de métodos de utilidad. Es decir, son a la
vez factorías y clases de utilidad.

4.1 Factoría de Guava para el tipo List


La clase para crear objetos de tipo list es Lists 4. Los métodos creacionales tienen los mismos nombres que
las clases de la API de Java que implementan las listas (ArrayList y LinkedList) pero precedidas de new, es
decir newArrayList y newLinkedList. Son métodos estáticos de la clase (es una factoría), por lo que se invocan
precedidos por el nombre de la clase

Lists ofrece más formas de crear listas que las que tienen los constructores de ArrayList y LinkedList, que
recordemos que son:
 El constructor sin argumentos, que crea una lista vacía.
 El constructor con un argumento de tipo Collection, que crea una lista con los mismos elementos
que la colección que se le pasa (constructor copia).

Los métodos creacionales adicionales de Lists son:

 Para las LinkedList:


static <E> LinkedList<E> newLinkedList()

Creates an empty LinkedList instance.

static <E> LinkedList<E> newLinkedList(Iterable<? extends E> elements)

Creates a LinkedList instance containing the given elements.

 Para las ArrayList (hay algunos otros con aspectos técnicos, pero vamos a enumerar los más útiles):
static <E> ArrayList<E> newArrayList()

Creates a mutable, empty ArrayList instance.

static <E> ArrayList<E> newArrayList(E... elements)

Creates a mutable ArrayList instance containing the given elements.

static <E> ArrayList<E> newArrayList(Iterable<? extends E> elements)

Creates a mutable ArrayList instance containing the given elements.

static <E> ArrayList<E> newArrayList(Iterator<? extends E> elements)

Creates a mutable ArrayList instance containing the given elements.

4
http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/Lists.html
9. Colecciones II 11

El primer método equivale al constructor vacío, el segundo permite crear una lista a partir de un número
variable de objetos separados por comas, el tercero equivale al constructor copia (recordemos que
Collection es subtipo de Iterable) y el último permite crear una lista a partir de un Iterator. Usando el
segundo método se puede crear una lista, haciendo por ejemplo:

List<String> ls = Lists.newArrayList("A", "B", "C");

Obsérvese que en el método no se indica el tipo (ni en ningún otro método creacional): éste se toma del tipo
del que esté declarada la variable; en este caso List<String>.

La clase Lists tiene algunos métodos de utilidad adicionales, como reverse, que dada una lista devuelve una
vista de la lista original con los elementos invertidos de orden, o charactersOf, que dada una cadena de
caracteres (String) devuelve una lista de caracteres con los caracteres de los que está compuesta la cadena.

4.2 Factoría de Guava para los tipos Set y SortedSet


La clase en este caso es Sets5. El método estático newHashSet crea conjuntos de manera análoga al
constructor utilizado hasta el momento, pero tiene las mismas cuatro variantes que newArrayList en Lists, y
con el mismo sentido; newLinkedHashSet es análogo a los constructores correspondientes de la clase
LinkedHashSet; newTreeSet crea conjuntos ordenados con las mismas variantes que los constructores
correspondientes de TreeSet:

static <E> HashSet<E> newHashSet()

Creates a mutable, empty HashSet instance.

static <E> HashSet<E> newHashSet(E... elements)

Creates a mutable HashSet instance containing the given elements in


unspecified order.

static <E> HashSet<E> newHashSet(Iterable<? extends E> elements)

Creates a mutable HashSet instance containing the given elements in


unspecified order.

static <E> HashSet<E> newHashSet(Iterator<? extends E> elements)

Creates a mutable HashSet instance containing the given elements in


unspecified order.

static <E> LinkedHashSet<E> newLinkedHashSet()

Creates a mutable, empty LinkedHashSet instance.

static <E> LinkedHashSet<E> newLinkedHashSet(Iterable<? extends


E> elements)

Creates a mutable LinkedHashSet instance containing the given

5
http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/Sets.html
12 Fundamentos de Programación

elements in order.

static <E newTreeSet()


extends Comparable>
Creates a mutable, empty TreeSet instance sorted by the natural sort
TreeSet<E>
ordering of its elements.

static <E> TreeSet<E> newTreeSet(Comparator<? super E> comparator)

Creates a mutable, empty TreeSet instance with the given comparator.

static <E newTreeSet(Iterable<? extends E> elements)


extends Comparable>
Creates a mutable TreeSet instance containing the given elements
TreeSet<E>
sorted by their natural ordering.

La clase Sets tiene otros métodos que permiten, por ejemplo, calcular el producto cartesiano de varios
conjuntos o listas, calcular la unión, la intersección, la diferencia o la diferencia simétrica de dos conjuntos o
el conjunto de las partes de un conjunto.

4.3 Factoría Guava para los tipos Map y SortedMap


Como cabía esperar, la factoría para crear estos tipos es Maps6. Sus métodos creacionales son equivalentes a
los de las clases HashMap, LinkedHashMap y TreeMap:

static <K,V> HashMap<K,V> newHashMap()

Creates a mutable, empty HashMap instance.

static <K,V> HashMap<K,V> newHashMap(Map<? extends K,? extends V> map)

Creates a mutable HashMap instance with the same mappings as the


specified map.

static <K,V> HashMap<K,V> newHashMapWithExpectedSize(int expectedSize)

Creates a HashMap instance, with a high enough "initial capacity" that


it should hold expectedSize elements without growth.

static newIdentityHashMap()
<K,V> IdentityHashMap<K,V>
Creates an IdentityHashMap instance.

static newLinkedHashMap()
<K,V> LinkedHashMap<K,V>
Creates a mutable, empty, insertion-
ordered LinkedHashMap instance.

static newLinkedHashMap(Map<? extends K,? extends


<K,V> LinkedHashMap<K,V> V> map)

Creates a mutable, insertion-ordered LinkedHashMap instance with

6
http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/Maps.html
9. Colecciones II 13

the same mappings as the specified map.

static <K newTreeMap()


extends Comparable,V>
Creates a mutable, empty TreeMap instance using the natural ordering
TreeMap<K,V>
of its elements.

static <C,K extends C,V> newTreeMap(Comparator<C> comparator)


TreeMap<K,V>
Creates a mutable, empty TreeMap instance using the given
comparator.

static <K,V> TreeMap<K,V> newTreeMap(SortedMap<K,? extends V> map)

Creates a mutable TreeMap instance with the same mappings as the


specified map and using the same ordering as the specified map.

5. El tipo Multiset

El tipo Multiset7 (atención a la ese minúscula), en español multiconjunto, es un conjunto en el que cada
elemento tiene asociada una multiplicidad que indica cuántas veces aparece el elemento. Sirve, por ejemplo,
para contar con facilidad frecuencias. Sólo aparece en Guava: no hay ninguna interfaz equivalente en la API
de Java.

El tipo tiene un tratamiento distinto a los anteriores en cuanto a su creación: se crean objetos de este tipo
mediante los métodos create de la clase factoría HashMultiset8. Tiene dos métodos creacionales: uno sin
argumentos, que crea un Multiset vacío; y otro con un argumento de tipo Iterable, que crea un Multiset con
los mismos elementos que el iterable:

static <E> HashMultiset<E> create()

Creates a new, empty HashMultiset.

static <E> HashMultiset<E> create(Iterable<? extends E> elements)

Creates a new HashMultiset containing the specified elements.

Por ejemplo, el siguiente trozo de código

List<String> listaCaracteres = Lists.newArrayList("N", "A", "P", "A", "N", "A");


Multiset<String> ms = HashMultiset.create(listaCaracteres);
mostrar(ms);

produce la salida:

[P, A x 3, N x 2]

7
http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/Multiset.html
8
http://docs.guava-libraries.googlecode.com/git-
history/release/javadoc/com/google/common/collect/HashMultiset.html
14 Fundamentos de Programación

esto es, “P” aparece una vez, “A” tres y “N” dos (al igual que en los conjuntos, el orden de aparición es
impredecible: depende del hashCode de los elementos, entre otros factores).

Existe una clase LinkedHashMultiset9 donde los elementos aparecen en el orden en el que se insertaron
cuando se itera sobre ellos.

Los métodos de Multiset son:

boolean add(E element)

Adds a single occurrence of the specified element to this multiset.

int add(E element, int occurrences)

Adds a number of occurrences of an element to this multiset.

boolean contains(Object element)

Determines whether this multiset contains the specified element.

boolean containsAll(Collection<?> elements)

Returns true if this multiset contains at least one occurrence of each element in
the specified collection.

int count(Object element)

Returns the number of occurrences of an element in this multiset (the count of the
element).

Set<E> elementSet()

Returns the set of distinct elements contained in this multiset.

Set<Multiset.Entry<E>> entrySet()

Returns a view of the contents of this multiset, grouped


into Multiset.Entry instances, each providing an element of the multiset
and the count of that element.

Iterator<E> iterator()

boolean remove(Object element)

Removes a single occurrence of the specified element from this multiset, if present.

int remove(Object element, int occurrences)

Removes a number of occurrences of the specified element from this multiset.

boolean removeAll(Collection<?> c)

boolean retainAll(Collection<?> c)

9
http://docs.guava-libraries.googlecode.com/git-
history/release/javadoc/com/google/common/collect/LinkedHashMultiset.html
9. Colecciones II 15

int setCount(E element, int count)

Adds or removes the necessary occurrences of an element such that the element
attains the desired count.

boolean setCount(E element, int oldCount, int newCount)

Conditionally sets the count of an element to a new value, as described


in setCount(Object, int), provided that the element has the expected
current count.

Como todos los tipos, naturalmente también tiene los métodos equals, hashCode y toString, aunque no los
mencionemos.

La clase Multisets10 es una clase de utilidad que permite hacer operaciones sofisticadas sobre los
multiconjuntos. Destacamos los siguientes métodos:

static boolean containsOccurrences(Multiset<?> superMultiset,


Multiset<?> subMultiset)

Returns true if subMultiset.count(o) <=


superMultiset.count(o) for all o.

static copyHighestCountFirst(Multiset<E> multiset)


<E> ImmutableMultiset<E>
Returns a copy of multiset as an ImmutableMultiset whose
iteration order is highest count first, with ties broken by the iteration order of
the original multiset.

static <E> Multiset<E> difference(Multiset<E> multiset1, Multiset<?> m


ultiset2)

Returns an unmodifiable view of the difference of two multisets.

static <E> Multiset<E> intersection(Multiset<E> multiset1, Multiset<?>


multiset2)

Returns an unmodifiable view of the intersection of two multisets.

static boolean removeOccurrences(Multiset<?> multisetToModify,


Multiset<?> occurrencesToRemove)

For each occurrence of an element e in occurrencesToRemove,


removes one occurrence of e in multisetToModify.

static boolean retainOccurrences(Multiset<?> multisetToModify,


Multiset<?> multisetToRetain)

Modifies multisetToModify so that its count for an element e is at


most multisetToRetain.count(e).

10
http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/Multisets.html
16 Fundamentos de Programación

static <E> Multiset<E> sum(Multiset<? extends E> multiset1, Multiset<?


extends E> multiset2)

Returns an unmodifiable view of the sum of two multisets.

static <E> Multiset<E> union(Multiset<? extends


E> multiset1, Multiset<? extends E> multiset2)

Returns an unmodifiable view of the union of two multisets.

Existe también el tipo SortedMultiset11, en el que los elementos se ordenan según el orden natural o el
inducido por un Comparator. La clase TreeMultiset12 tiene tres métodos create (equivalentes a los
constructores de TreeSet) que crean multiconjuntos ordenados: uno sin argumentos, que crea un Multiset
vacío en el que los elementos se ordenan por el orden natural de los elementos; uno con un argumento de
tipo Iterable, que contendrá los mismos elementos que el argumento pero con los elementos ordenados por
su orden natural; y otro con un argumento de tipo Comparator, que crea un Multiset vacío en el que los
elementos se ordenan según el criterio aportado por el comparador.

Por ejemplo, la ejecución del siguiente código:

String frase = "El estribillo de una chirigota de " +


"Cádiz es: como como como como, estoy como estoy";
String fraseMinusculas = frase.toLowerCase();
List<String> lista = Cadenas.separaElementos(fraseMinusculas, "\\W | ");
Multiset<String> ms = HashMultiset.create(lista);
mostrar(ms);
SortedMultiset<String> msOrdenado = TreeMultiset.create(ms);
mostrar(msOrdenado);
Multiset<String> ordenFrecuencias = Multisets.copyHighestCountFirst(ms);
mostrar(ordenFrecuencias);

produce la salida:

[de x 2, una, estribillo, como x 5, estoy x 2, el, cádiz, es, chirigota]


[chirigota, como x 5, cádiz, de x 2, el, es, estoy x 2, estribillo, una]
[como x 5, de x 2, estoy x 2, una, estribillo, el, cádiz, es, chirigota]

6. El tipo Multimap

El tipo Multimap13 (cuidado con la segunda eme minúscula) es como un Map pero en el que si se asigna más
de un valor a la misma clave (se hacen varios put con el mismo valor de clave), se mantienen todos los
valores asignados, en lugar de quedarse sólo con el último como ocurre en el Map. Podemos imaginarlo
como que asocia a cada clave una colección de valores en lugar de un sólo valor. Dependiendo de la
11
http://docs.guava-libraries.googlecode.com/git-
history/release/javadoc/com/google/common/collect/SortedMultiset.html
12
http://docs.guava-libraries.googlecode.com/git-
history/release/javadoc/com/google/common/collect/TreeMultiset.html
13
http://docs.guava-libraries.googlecode.com/git-
history/release/javadoc/com/google/common/collect/Multimap.html
9. Colecciones II 17

implementación, esa colección admitirá duplicados (si lo que asocia con la clave es una lista de valores) o no
(si lo que asocia es un conjunto). Las clases implementadoras son ArrayListMultimap14 o
LinkedListMultimap15 para las que admiten duplicados y HashMultimap16 para la que no.

Veamos esto con un ejemplo: si se ejecuta el código

Multimap<String, String> mm = LinkedListMultimap.create();


mm.put("3", "3");
mm.put("3", "tres");
mm.put("3", "III");
mm.put("3", "tres");
mm.put("1", "I");
mm.put("1", "uno");
mm.put("2", "dos");
mm.put("2", "1+1");
mm.put("2", "2");
mm.put("2", "1+1");
mostrar("LinkedListMultimap admite valores duplicados:");
mostrar(mm);
mostrar("HashMultimap no admite valores duplicados:");
Multimap<String, String> mm2 = HashMultimap.create(mm);
mostrar(mm2);

se obtiene la salida

LinkedListMultimap admite valores duplicados:


{3=[3, tres, III, tres], 1=[I, uno], 2=[dos, 1+1, 2, 1+1]}
HashMultimap no admite valores duplicados:
{3=[3, III, tres], 2=[2, 1+1, dos], 1=[uno, I]}

Hay que tener en consideración que los valores asociados a las claves son vistas. Si se añade al código
anterior

mm2.get("2").add("10 - 8");
mm2.get("2").remove("2");
mm2.get("3").clear();
mostrar("Modificaciones directas sobre el último:");
mostrar(mm2);

se obtiene

{2=[10 - 8, 1+1, dos], 1=[uno, I]}

Los métodos más importantes de Multimap son:

14
http://docs.guava-libraries.googlecode.com/git-
history/release/javadoc/com/google/common/collect/ArrayListMultimap.html
15
http://docs.guava-libraries.googlecode.com/git-
history/release/javadoc/com/google/common/collect/LinkedListMultimap.html
16
http://docs.guava-libraries.googlecode.com/git-
history/release/javadoc/com/google/common/collect/HashMultimap.html
18 Fundamentos de Programación

Map<K,Collection<V>> asMap()

Returns a map view that associates each key with the corresponding values in the
multimap.

void clear()

Removes all key-value pairs from the multimap.

boolean containsEntry(Object key, Object value)

Returns true if the multimap contains the specified key-value pair.

boolean containsKey(Object key)

Returns true if the multimap contains any values for the specified key.

boolean containsValue(Object value)

Returns true if the multimap contains the specified value for any key.

Collection<Map.Entry entries()
<K,V>>
Returns a collection of all key-value pairs.

Collection<V> get(K key)

Returns a collection view containing the values associated with key in this
multimap, if any.

boolean isEmpty()

Returns true if the multimap contains no key-value pairs.

Multiset<K> keys()

Returns a collection, which may contain duplicates, of all keys.

Set<K> keySet()

Returns the set of all keys, each appearing once in the returned set.

boolean put(K key, V value)

Stores a key-value pair in the multimap.

boolean putAll(K key, Iterable<? extends V> values)

Stores a collection of values with the same key.

boolean putAll(Multimap<? extends K,? extends V> multimap)

Copies all of another multimap's key-value pairs into this multimap.

boolean remove(Object key, Object value)

Removes a single key-value pair from the multimap.


9. Colecciones II 19

Collection<V> removeAll(Object key)

Removes all values associated with a given key.

Collection<V> replaceValues(K key, Iterable<? extends V> values)

Stores a collection of values with the same key, replacing any existing values for that
key.

int size()

Returns the number of key-value pairs in the multimap.

Collection<V> values()

Returns a collection of all values in the multimap.

Existe una clase de utilidad Multimaps, pero no tiene utilidad para nosotros por el momento.

7. Ejercicios propuestos

1. Escriba el método

public static Map<String, Integer> pasajerosPorDestino(List<Vuelo> l)

de la clase Vuelos que dada una lista de vuelos devuelve una función que hace corresponder a cada
ciudad de destino la suma del número de pasajeros de todos los vuelos que tengan ese destino. Nota: no
usar Guava.

2. Escriba el método

public static SortedMap<String, Integer> cuentaPalabras(String frase)

que recibe una cadena de caracteres que representa una frase y devuelve una función ordenada que
hace corresponder a cada palabra de la frase (una vez convertida a minúsculas) la frecuencia de
aparición de esa palabra en la frase. Las claves (las palabras del texto) siguen el orden natural de las
palabras. Utilícese la cadena "\\W | " como delimitador en Cadenas.separaElementos.

Por ejemplo, si la cadena de entrada es

"Este es el estribillo de una chirigota de Cádiz: Como como como como, estoy como
estoy"

la función devuelta debe ser:

{chirigota=1, como=5, cádiz=1, de=2, el=1, es=1, este=1, estoy=2, estribillo=1, una=1}

Obsérvese que las palabras están en orden alfabético (las vocales con tilde van detrás de las vocales sin
tilde en el orden alfabético de las cadenas en Java). Nota: no usar Guava.
20 Fundamentos de Programación

3. Suponiendo que tenemos el método anterior, escriba el método

public static SortedMap<Integer, List<String>> invierteMap(SortedMap<String,


Integer> m)

que recibe la función ordenada generada en el ejercicio anterior y, a partir de la información que
contiene, genera otra que devuelve y que hace corresponder a cada frecuencia de palabras que aparecía
en la función ordenada recibida, una lista con todas las palabras que tienen esa frecuencia. Las claves de
la función ordenada devuelta (enteros) tienen que estar ordenadas de mayor a menor, de modo que
aparezcan en primer lugar las palabras más frecuentes. Utilice un Comparator si lo considera necesario.
En la frase del ejemplo anterior, la función ordenada devuelta debe ser:

{5=[como], 2=[de, estoy], 1=[chirigota, cádiz, el, es, este, estribillo, una]}

Observe que las palabras con la misma frecuencia siguen en las listas correspondientes en orden
alfabético, puesto que se obtuvieron de la función anterior, donde estaban ordenadas. Nota: no usar
Guava.

4. Reescriba el método del ejercicio 2 usando Guava y haciendo que devuelva un SortedMultiset<Integer>:

public static SortedMultiset<String > cuentaPalabras2(String frase)

5. Reescriba el método del ejercicio 4 usando Guava y haciendo que reciba el SortedMultiset anterior y que
devuelva un Multimap<Integer, String>:

public static Multimap<Integer, String> invierteMultiset(


SortedMultiset<String> ms)

En este ejercicio no se exige que los elementos aparezcan en orden de mayor a menor frecuencia.

6. Escriba en la clase Vuelos un método que dada una lista de vuelos devuelve un Multimap que hace
corresponder cada origen con los códigos de los vuelos de la lista que tienen ese origen. El perfil del
método es:

public static Multimap<String, String> codigosSegunOrigen(List<Vuelo> l)

También podría gustarte