COMP1406/1006 - Design and Implementation of Computer Applications |
Winter 2006
|
7 More Collections: Sets and HashMaps |
Here are the individual topics found in this set of notes (click on one to go there):![]()
![]()
7.1 Collections Re-Visited |
Recall that an interface merely specifies a list of method signatures ... not actual code. Recall as well that the Collection interface defined many messages. Below is a list of a few of these grouped as querying methods and modifying methods.![]()
7.2 The Set Classes |
The Set classes are much like Lists, except that they do not allow duplicates.
![]()
![]()
![]()
HashSet
|
![]() |
TreeSet
|
![]() |
Hashing is such a high speed scheme for converting
sparse (i.e., quite spread apart) keys into unique array subscripts. Hashing
essentially generates for each item, a number (called the hashcode)
which is used as the key (or index). The generated
number has no significant meaning other than to allow efficient storage and
retrieval of data.
It is used as a way of getting quickly to the "vicinity" of the object you are looking for. For example, this is exactly what post offices do when they sort mail. They use the postal code to determine "roughly" where in the city your mail should go. People living in the same area have the same postal code, so it is easier for the post office to locate, gather and deliver your mail.
So HashSets (as well as Hashtables and HashMaps which we will see later) store their objects according to a hash function which:![]()
Let us now look at an example that helps us understand the differences between these two sets.
Consider this code that adds some BankAccounts to an ArrayList:
String[] names = {"Al", "Zack", "Sally", "Al", "Mel", "Zack", "Zack", "Sally"};Here is the output, as expected:
ArrayList<String> aCollection = new ArrayList<String>();// Fill up the collection
for(int i=0; i<names.length; i++)
aCollection.add(names[i]);// Now print out the elements
for(String s: aCollection)
System.out.println(s);
Al Zack Sally Al Mel Zack Zack Sally |
Consider what happens when we replace the line:
with:
When we run the code, we obtain the following output:
Mel Al Sally Zack |
What happened to the duplicates ? They were removed.
We only see the unique names coming out on the console window. But
hold on! This is not the order that we added the names into the
collection!! Well ... recall that the elements are not kept in
any kind of order, and in fact, JAVA has its own pre-determined order based
on hashcode values of the elements in which we added.
Now what about a TreeSet ? Shouldn't that keep them
in order ?
Consider what happens when we replace:
with:
Al Mel Sally Zack |
Notice that the items are now sorted alphabetically. If we
wanted to sort in a different order, we would need to make our own objects,
instead of using Strings so that we could implement the Comparable interface.
What if we want to store Customer objects in the sets instead of
simply strings:
public class Customer {
private String name;
public Customer(String n) { name = n; }
public String getName() { return name; }
public String toString() { return
"Customer: " + name; }
}
Here is some code that makes a HashSet
of Customer objects and prints
them:
String[] names = {"Al", "Zack", "Sally", "Al", "Mel", "Zack", "Zack", "Sally"};Here is the output:
HashSet<Customer> aCollection = new HashSet<Customer>();
for(int i=0; i<names.length; i++) {
Customer c = new Customer(names[i]);
aCollection.add(c);
}
for(Customer c: aCollection)
System.out.println(c);
Customer: Sally Customer: Zack Customer: Zack Customer: Mel Customer: Al Customer: Sally Customer: Al Customer: Zack |
public boolean equals(Object obj) {In addition, since objects are "hashed" in order to find their position in the HashSet, we must also implement a hashCode() method. The hashCode() method should return an integer that attempts to represent the object uniquely. Usually, it simply returns the combined hashcodes of its instance variables:
if (!(obj instanceof Customer))
return false;
return getName().equals(((Customer)obj).getName());
}
public int hashCode() {Now when we run the code, we see that the duplicates are gone:
return name.hashCode();
}
Customer: Mel Customer: Al Customer: Sally Customer: Zack |
We could change HashSet to TreeSet to get the items in sorted order. However, we would then need to make sure that our Customer objects are Comparable. We could add the following to our Customer class:
public class Customer implements Comparable
{
...
public int compareTo(Object obj) {
if (obj instanceof Customer)
return getName().compareTo(((Customer)obj).getName());
else
throw new ClassCastException();
}
}
Then when we run the code, we see that the duplicates are gone and that
the items are in sorted order:
Customer: Al Customer: Mel Customer: Sally Customer: Zac |
So remember ... we can use a HashSet or TreeSet to eliminate
duplicates from a collection.
7.3 The Map Interface and Its Classes |
Each map entry has:
|
![]() |
Querying methods (returns some kind of value):
Here is the hierarchy showing most of the Map classes:
Notice that there are 4 main Map implementations:
Hashtable
|
|
HashMap
|
|
TreeMap
|
|
WeakHashMap
|
7.4 HashMaps |
HashMaps are not fixed-size, so they can grow as needed (just like
with ArrayLists). Items are
added to the HashMap by specifying
the key AND the value. The key is used as a unique identifier that
distinguishes its associated value from any other value. Both the keys
and values can be arbitrary objects (but no null key for Hashtables). We will look
at HashMaps here, but the Hashtable class works the same way, but
is synchronized (slower than HashMap).
HashMap table = new HashMap(); |
HashMap<String,ArrayList>
table = new HashMap<String,ArrayList>(); |
Method | Description and Example |
Object
put(Object key, Object value)
|
Add the given value to the HashMap with the given key. If there
is no value there for that key then null is returned, otherwise the
original value in the HashMap is
returned.
HashMap<String,String> aPhoneBook = new HashMap<String,String>(); |
Object
get(Object key)
|
Return the value associated with the given key.
If there is no value, null is returned.
HashMap<String,String> aPhoneBook = new HashMap<String,String>(); |
Object
remove(Object key)
|
Remove the key/value pair from the HashMap and return the value that was
removed. If it is not there, return null.
HashMap<String,String> aPhoneBook = new HashMap<String,String>(); |
boolean isEmpty()
|
Return whether or not there are any values in the
HashMap.
HashMap<String,String> aPhoneBook = new HashMap<String,String>(); |
boolean containsKey(Object key)
|
Return whether or not the given key is in the HashMap.
HashMap<String,String> aPhoneBook = new HashMap<String,String>(); |
boolean containsValue(Object value)
|
Return whether or not the given value is in the HashMap.
HashMap<String,String> aPhoneBook = new HashMap<String,String>(); |
void clear()
|
Empty the HashMap.
HashMap<String,String> aPhoneBook = new HashMap<String,String>(); |
Collection values()
|
Return a Collection
of the values in the HashMap.
HashMap<String,String> aPhoneBook = new HashMap<String,String>(); |
Set
keySet()
|
Return a Set of
the keys in the HashMap.
HashMap<String,String> aPhoneBook = new HashMap<String,String>(); |
Set
entrySet() |
Return a Set of
the key/value pairs (i.e., as Map.Entry
objects) in the HashMap.
HashMap<String,String>
aPhoneBook = new HashMap<String,String>();
aPhoneBook.put("Arthur", "555-8813"); aPhoneBook.put("Mark", "555-2238"); aPhoneBook.put("Norm", "555-3789"); aPhoneBook.entrySet(); // returns Set [Norm=824-3789, Mark=744-2238, Arthur=741-8813] |
What is the difference between a HashMap
and a TreeMap ?
As with HashSets and TreeSets, TreeMaps maintain the keys in sorted order,
whereas HashMaps do not maintain
the keys in sorted order.
Consider this code:
String[] names = {"Al", "Zack", "Sally", "Al", "Mel", "Zack", "Zack", "Sally"};Here we see that a HashMap is formed with keys being the names of the Customer and values being the Customers themselves. This is the output:
HashMap<String,Customer> aMap = new HashMap<String,Customer>();// Fill up the collection
for (int i=0; i<names.length; i++)
aMap.put(names[i], new Customer(names[i]));System.out.println("Here are the keys:");
for (String key: aMap.keySet())
System.out.println(" " + key);System.out.println("Here are the values:");
for (Customer val: aMap.values())
System.out.println(" " + val);
System.out.println("Here are the key/value pairs:");
for (Map.Entry pair: aMap.entrySet())
System.out.println(" " + pair);
Here are the keys: Mel Al Sally Zack Here are the values: Customer: Mel Customer: Al Customer: Sally Customer: Zack Here are the key/value pairs: Mel=Customer: Mel Al=Customer: Al Sally=Customer: Sally Zack=Customer: Zack |
Notice that there are only 4 keys, even though we added many items ... this is because one key overwrites another when we call the put() method more than once with the same key.
If we replace the HashMap with a TreeMap in the above code,
the code still works, we just get the items in sorted order according to
the keys:
Here are the keys: Al Mel Sally Zack Here are the values: Customer: Al Customer: Mel Customer: Sally Customer: Zack Here are the key/value pairs: Al=Customer: Al Mel=Customer: Mel Sally=Customer: Sally Zack=Customer: Zack |
Using WeakHashMap, you won't notice a difference. We
will not talk about this class in this course.
7.5 The MovieStore Example |
Consider an application which represents a movie store
that maintains movies to be rented out. Assume that we have a
collection of movies. When renting, we would like to be able
to find movies quickly. For example, we may want to:
|
![]() |
Obviously, we could simply store all moves in one big ArrayList. But how long would we waste finding our movies ? Imagine a video store in which the movies are not sorted in any particular order ... just randomly placed on the shelves !! We would have to go through them one by one !!! | ![]() |
We will use HashMaps to store our movies efficiently so that we can quickly get access to the movies that we want.
Let us start out with the representation of a Movie object.
Each movie will maintain a title, list of actors
and a type (category). Obviously, in a real system,
we would need to keep much more information such as ID, rental history, new
releases vs. oldies, etc... Here is the diagram representing the
Movie object:
Let us now define this Movie class.![]()
public String getTitle() { return title; }
public
String getType() { return type; }
public
ArrayList<String> getActors() { return actors; }
public
void setTitle(String aTitle) { title = aTitle; }
public
void setType(String aType) { type = aType; }
public Movie() { this("???", "???"); }
public
Movie(String aTitle, String aType) {
title = aTitle;
type = aType;
actors = new ArrayList<String>();
}
public
String toString() { return("Movie: " + "\"" + title + "\"");
}
public void addActor(String anActor) { actors.add(anActor);
}
}
aMovie = Movie.example1();
anotherMovie = Movie.example2();
System.out.println(aMovie);
System.out.println("is a " + aMovie.getType() +
" with actors " + aMovie.getActors());
System.out.println(anotherMovie);
System.out.println("is a " + anotherMovie.getType()
+
" with actors " + anotherMovie.getActors());
}
}
Movie: "The Matrix" is a SciFic with actors [Keanu Reeves, Laurence Fishburne, Carrie-Anne Moss] Movie: "Blazing Saddles" is a Comedy with actors [Cleavon Little, Gene Wilder] |
Now we need to consider the making a MovieStore object. Recall, that we want to store movies efficiently using HashMaps.
For the MovieStore, we will maintain three HashMaps. One will be the movieList where the keys are titles and the values are the movie objects with that title. The second will be the actorList which will keep actor/actress names as keys and the values will be ArrayLists of all movies that the actor/actress stars in. The last one will be the typeList in which the keys will be the "types" (or categories) of movies and the values will be ArrayLists of all movies belonging to that type.
Notice that one of the movies is "blue" in the picture. Why ?![]()
Isn't this wasteful ? Keep in mind that we are not duplicating all the movie's data ... we are only duplicating the pointer to the movie. So in fact, each time we duplicate a movie in our HashMaps, we are simply duplicating its reference (or pointer) which takes 4 bytes.
So, yes, we are taking slightly more space, but at the benefit of allowing quick access to the data. You will learn more about efficiency when you do your second-year course on data structures.
The basic MovieStore definition is as follows:
//These are the get methods, not set methods are needed
public
HashMap<String,Movie> getMovieList() { return movieList; }
public
HashMap<String,ArrayList<Movie>> getActorList() { return
actorList; }
public
HashMap<String,ArrayList<Movie>> getTypeList() { return
typeList; }
//This is the constructor
public
MovieStore() {
movieList = new HashMap<String,Movie>();
actorList = new HashMap<String,ArrayList<Movie>>();
typeList = new HashMap<String,ArrayList<Movie>>();
}
//This method returns a String representation of the
Movie
public
String toString() {
return ("MovieStore with " + movieList.size()
+ " movies.");
}
}
Now, how do we add a movie to the store ? Well ... how do the instance variables change ?
//If there is no category yet matching this
movie's type, make a new category
if
(!typeList.containsKey(aMovie.getType()))
typeList.put(aMovie.getType(), new ArrayList<Movie>());
//Add the movie to the proper category.
typeList.get(aMovie.getType()).add(aMovie);
//Now add all of the actors
for
(String anActor: aMovie.getActors()) {
//If there is no actor yet matching
this actor, make a new actor key
if (!actorList.containsKey(anActor))
actorList.put(anActor, new ArrayList<Movie>());
//Add the movie for
this actor
actorList.get(anActor).add(aMovie);
}
}
//Remove from the type list vector. If
last one, remove the type.
typeList.get(aMovie.getType()).remove(aMovie);
if
(typeList.get(aMovie.getType()).isEmpty())
typeList.remove(aMovie.getType());
//Now Remove from the actors list. If
actor has no more, remove him.
for(String
anActor: aMovie.getActors()) {
actorList.get(anActor).remove(aMovie);
if (actorList.get(anActor).isEmpty())
actorList.remove(anActor);
}
}
public class MovieStoreTester {
System.out.println("Here are the movies in: "
+ aStore);
aStore.listMovies();
System.out.println();
//Try some removing now
System.out.println("Removing The Matrix");
aStore.removeMovieWithTitle("The Matrix");
System.out.println("Trying to remove Mark's Movie");
aStore.removeMovieWithTitle("Mark's Movie");
//Do some listing of movies
System.out.println("\nHere are the Comedy movies in: " + aStore);
aStore.listMoviesOfType("Comedy");
System.out.println("\nHere are the Science Fiction movies in: " + aStore);
aStore.listMoviesOfType("SciFic");
System.out.println("\nHere are the movies with Ben Stiller:");
aStore.listMoviesWithActor("Ben Stiller");
System.out.println("\nHere are the movies with Keanu Reeves:");
aStore.listMoviesWithActor("Keanu Reeves");
}
Here are the movies in: MovieStore with 10 movies. Envy Blazing Saddles The Matrix The Matrix Reloaded Meet the Fockers Meet the Parents Runaway Jury The Matrix Revolutions The Adventure of Sherlock Holmes' Smarter Brother The Aviator Removing The Matrix Trying to remove Mark's Movie No movie with that title Here are the Comedy movies in: MovieStore with 9 movies. Movie: "Blazing Saddles" Movie: "The Adventure of Sherlock Holmes' Smarter Brother" Movie: "Meet the Fockers" Movie: "Meet the Parents" Movie: "Envy" Here are the Science Fiction movies in: MovieStore with 9 movies. Movie: "The Matrix Reloaded" Movie: "The Matrix Revolutions" Here are the movies with Ben Stiller: Movie: "Meet the Fockers" Movie: "Meet the Parents" Movie: "Envy" Here are the movies with Keanu Reeves: Movie: "The Matrix Reloaded" Movie: "The Matrix Revolutions" |