Close
Register
Close Window

OpenDSA Stand-alone Modules

Chapter 0 modules

Show Source |    | About   «  0.311. Variable Scoping, Input, and Output   ::   Contents   ::   0.313. Static, Main, and Exceptions  »

Maps and Sets

1. The Map and Set Interfaces

So far, we have use the List interface as the basic form of container in Java. However, two additional interfaces that define containers with different properties are Map and Set. The Map and Set interfaces are similar to List in that there are multiple classes in the java.util collections framework that implement them. The main difference is that they organize values differently, which means that you add and access values differently.

2. The Map Interface

The Map<K, V> interface is modeled after looking up definitions for words in a dictionary. In computer science, you will hear people refer to these kinds of “look up” structures using names like “map”, “dictionary”, “hash”, or even “associative array”. You can think of a map as a collection of pairs of elements that are associated with each other. A pair consists of a key that corresponds to a value you can look up, and a value corresponding to the result you will find when you look up its key. If you think of a dictionary of words, for example, each entry in the dictionary consists of a “word” and its “definition”. We would call the “word” a “key”, and its definition would be a “value”, and the dictionary itself is a collection of pairs of keys and values (words and definitions). You will sometimes hear the elements in a map referred to as a key-value pair because it contains pairs of connected values.

Pairs can be added to maps and can be removed from maps. Maps cannot have distinct pairs with the same keys; if you attempt to add a pair to a map that already contains a pair with the same key, the second pair will replace the first.

The Map<K, V> interface defines the map operations. It takes two separate generic type parameters: K is the type parameter specifying the key type, and V is the type parameter specifying the value type. For example K could be Integer and a V could be String. Or K and V could both be Boolean. Or K could be Jeroo and V could be List<Flower>. There are no limits on possible combinations!

The most important Map operations are:

public V put(K key, V val);         // store a given key,value pair
public V get(K key);                // get the value associated with given key
public V remove(K key);             // remove key,value pair for given key
public boolean containsKey(K key);  // determine whether key exists in Map
public Set<K> keySet();             // return the set of keys

2.1. Classes that Implement Map

HashMap and TreeMap are two classes in the java.util collections framework that implement the Map interface. They both provide fast operations to look up a key in a map. They also provide quick insertion of a pair into the map or removal of a pair from a map. For large volumes of data, both are much, much faster at lookup tasks than storing items in a List or an array. For new programmers, we usually use HashMap as the default choice when we create new maps, similar to choosing ArrayList as the default choice for new List objects. The HashMap class works well in most cases.

One situation when you would prefer to use TreeMap is when you would like to iterate over all the keys in the map in sorted order. For example, in a dictionary, you might expect words to be stored in alphabetical order, and in a phone book, you might expect names to be stored in alphabetical order. If keys have a natural ordering, the TreeMap class will use this order when iterating over keys, although his can slightly impact the overall performance of the map by a small amount. The HashMap does not keep keys in any predictable order.

2.2. Using a Map

Let’s think about a simple example for using a map data structure. Suppose that a programmer is developing an application for a large company for maintaining a no–call list. The programmer wishes to store pairs of names and phone numbers. We could represent both using strings, so we could use a Map<String, String> to store these pairs. The resulting map will act sort of like a phone book, associating names (keys) with phone numbers (values) in pairs.

public void testMap()
{
    Map<String, String> noCallMap = new HashMap<String, String>();
}

3. Syntax Practice: Making Maps

4. Adding and Accessing Pairs in a Map

Now, lets add some values to our noCallMap. To add something to a Map, we’ll call the put() method:

public void testMap()
{
    Map<String, String> noCallMap = new HashMap<String, String>();

    noCallMap.put("Roger M", "090−997−2918");
    noCallMap.put("Jane Q", "999-777-1234");
}

put() takes in two parameters: first a key, and then an associated value. The two calls to put() above create two key-value pairs, each with a name and a phone number.

To access those pairs, we use the get() method:

public void testMap()
{
    Map<String, String> noCallMap = new HashMap<String, String>();

    noCallMap.put("Roger M", "090−997−2918");
    noCallMap.put("Jane Q", "999-777-1234");

    System.out.print("Jane Q's number is: " + noCallMap.get("Jane Q"));
}

When we run the code above, the following message would be printed out:

"Jane Q's number is: 999-777-1234"

5. Syntax Practice: Adding to Maps

6. Checking for and Removing Pairs in a Map

As you saw with get(), when accessing values in a map, you usually use the key to specify which pair you wish to work on. In fact, sometimes one might say “index into a map” using a key. The alternate name of “associative array” comes from the fact that a map uses keys as unique identifiers for the pairs it contains, and you can think of the key as being similar to the “position” of a pair in a map, just like numeric positions are used to refer to positions in a List.

So when checking to see if a pair is stored in a map, or to remove the pair from the map, it is natural to use the key as the identifier. Maps provide a remove() method where you specify a key, and the pair with that key will be removed from the map. Maps also provide a contains() method that takes a key value and returns a boolean result indicating whether a pair with the corresponding key is present in the map. For both of these operations, since keys must be unique in a map, we really only need a key.

public void testMap()
{
    Map<String, String> noCallMap = new HashMap<String, String>();

    noCallMap.put("Roger M", "090−997−2918");
    noCallMap.put("Jane Q", "999-777-1234");

    noCallMap.remove("Jane Q");
    System.out.print(noCallMap.contains("Jane Q"));
}

Here, we add “Jane Q” and her phone number to the Map, remove it, then the value false would be printed out as there is no longer a key called “Jane Q” in our Map.

7. A Visual Summary of Using Map and HashMap

8. Syntax Practice: Map Contains and Remove

9. Looping Over Map Contents

As mentioned above, keys are unique, and maps provide a method to get the full set of all keys they contain. This method is called keySet() and it returns a Set of key values–the Set interface is discuseed next.

Because the keySet() method returns a collection of all the keys in the map, it is commonly used in looping over the entire map:

public void testMap()
{
    Map<String, String> noCallMap = new HashMap<String, String>();

    noCallMap.put("Roger M", "090−997−2918");
    noCallMap.put("Jane Q", "999-777-1234");

    for (String name : noCallMap.keySet())
    {
        System.out.println("name: " + name
            + ", phone: " + noCallMap.get(name));
    }
}

This method would print out the entire contents of the map by using a for-each loop over the set of all keys in the map. This approach to writing a for-each loop over a map is a great place for beginners to start.

More advanced programmers may also use a for-each loop, but might wish to loop over all the pairs in the map, instead of just the keys. This is a bit more complicated, due to the type used to represent pairs in a map. The Map interface provides a nested class called Map.Entry that represents one entry or pair in the map. The Map interface also provides a method called entrySet() that is similar to keySet(), but provides a collection of all the entries (pairs) in the map. You can use entrySet() to write a more advanced loop that looks like this:

public void testMap()
{
    Map<String, String> noCallMap = new HashMap<String, String>();

    noCallMap.put("Roger M", "090−997−2918");
    noCallMap.put("Jane Q", "999-777-1234");

    for (Map.Entry<String, String> pair : noCallMap.entrySet())
    {
        System.out.println("name: " + pair.getKey(),
            + ", phone: " + pair.getValue());
    }
}

Writing a loop using keySet() is usually simpler. However, it requires calling get() to retrieve the value associated with each key. Writing a loop using entrySet() is a little more complex, but because it provides access to both the key and the value at the same time without having to look up anything in the map, it is much more efficient when both the key and value are needed inside the loop.

10. Check Your Understanding: Maps

11. The Set Interface

The Set interface is modeled after the set theory principles taught in mathematics. In mathematics, sets are a collection of elements–oftentimes with some amount of common properties. A set is a collection that represents a mathematical set. There are three important properties of a set:

  • The same element value may only occur once in a set.

  • The order in which the elements of a set appear (when iterating through the elements) is typically different than the order in which the elements were added. Two sets that have the same elements listed in different orders are considered to be the same set.

In computer science and in Java, data structures that model sets are designed for large collections of data. Such data structures have a method that determines if an object is in a given set with an efficient algorithm. For large data sets, using such a method is much faster than iterating through a list.

11.1. Classes that Implement Set

TreeSet and HashSet are two classes in the collections framework that implement the Set interface. They both provide fast operations to check whether an element is in a set. They also provide quick insertion of an element into the set or removal of an element from a set. For large sets—those having at least several thousand elements—where there are large numbers of insertions, deletions, and tests for whether elements are in a set, Lists would be much slower. Just like for maps, TreeSet guarantees that it will iterate over its values in their natural order, while HashSet does not maintain any ordering.

The Set<E> interface, which is a subclass of Collection<E> (just like List<E>), describes the operations that all sets provide. Here are are the three most important set operations:

boolean add(E element);         // add an element to the set
boolean contains(Object o);     // does the set contain given object?
boolean remove(Object o);       // remove given object from the set

11.2. Using a Set

Let’s think about a simple example for using a set data structure. Let’s return to our no-call list example. Suppose that a programmer is developing an application for a large company for maintaining a no–call list. The programmer has decided to use the TreeSet data structure to store a series of PhoneRecord objects.

The PhoneRecord class looks like this:

public class PhoneRecord
{
    public String name;
    public String phoneNumber;

    public PhoneRecord(String initName, String initNumber)
    {
        this.name = initName;
        this.phoneNumber = initNumber;
    }
}

A TreeSet seems to be an appropriate structure for this problem, since the main use of the data will be to test whether records are in the set.

The programmer would first need to create a Set variable to contain our PhoneRecord objects:

public void testSet()
{
    Set<PhoneRecord> noCall = new TreeSet<PhoneRecord>();
}

12. Syntax Practice: Making A Set

13. Adding Values to a Set

Now, lets add some records to our Set:

public void testSet()
{
    Set<PhoneRecord> noCall = new TreeSet<PhoneRecord>();

    // making PhoneRecord and adding to set
    PhoneRecord roger = new PhoneRecord("Roger M", "090−997−2918");
    noCall.add(roger);
}

In the code above, we make a PhoneRecord object called roger and then add it to our set. We could also add an object directly to the set without using a separate variable:

noCall.add(new PhoneRecord("Stacy K", "090−997−9188"));

Importantly, adding the same object to a set multiple times won’t cause any errors in your code. Only the first call will actually add the object to the set, however.

public void testSet()
{
    Set<PhoneRecord> noCall = new TreeSet<PhoneRecord>();

    PhoneRecord roger = new PhoneRecord("Roger M", "090−997−2918");
    noCall.add(roger);

    // Running a second time won't do anything
    // but also won't cause errors:
    noCall.add(roger);
}

Just as with lists, you must make sure the item added is the same type as the type in your angle brackets(<>). For example we could not simply add the number 1 to the set noCall.

14. Syntax Practice: Adding to a Set

15. Checking Values in a Set

The second important method for a set is contains(). This method takes a value and returns true if the value is in the set or false if not.

public void testSet()
{
    Set<PhoneRecord> noCall = new TreeSet<PhoneRecord>();

    PhoneRecord roger = new PhoneRecord("Roger M", "090−997−2918");
    noCall.add(roger);

    boolean inside = noCall.contains(roger);
    System.out.println("It is " + inside + " that Roger is in the set");
}

If we ran the code above, the following message would be output:

"It is true that Roger is in the set"

However, if we created another PhoneRecord object but did not add it to the set…

public void testSet()
{
   Set<PhoneRecord> noCall = new TreeSet<PhoneRecord>();

   PhoneRecord jane = new PhoneRecord("Jane Q", "999-777-1234");

   boolean inside = noCall.contains(jane);
   System.out.println("It is " + inside + " that Jane is in the set");
}

This method would output the following message:

"It is false that Jane is in the set

16. Syntax Practice: Set Contains

17. Removing Values from a Set

The final important method on a set is remove(), which removes something from a set.

public void testSet()
{
    Set<PhoneRecord> noCall = new TreeSet<PhoneRecord>();

    PhoneRecord roger = new PhoneRecord("Roger M", "090−997−2918");
    noCall.add(roger);

    boolean inside = noCall.contains(roger);
    System.out.println("It is " + inside + " that Roger is in the set");

    noCall.remove(roger);
    inside = noCall.contains(roger);
    System.out.println("It is " + inside + " that Roger is in the set");
}

We can see above that we added the PhoneRecord called roger to noCall. We then print out:

"It is true that Roger is in the set"

We then remove roger` from the set and then print out:

"It is false that Roger is in the set"

18. Syntax Practice: Set Remove

19. Looping Over Sets

Iterating over a set is easiest if you use a for-each loop, and is virtually identical to using a for-each loop over a list.

public void testMap()
{
    Set<PhoneRecord> noCall = new TreeSet<PhoneRecord>();

    // insert records into the set

    for (PhoneRecord record : noCall)
    {
        System.out.println("name: " + record.getName()
            + ", phone: " + record.getPhoneNumber());
    }
}

This method would print out the entire contents of the set by using a for-each loop over all of the elements in the set.

20. Check Your Understanding: Sets

21. Programming Practice: Maps

   «  0.311. Variable Scoping, Input, and Output   ::   Contents   ::   0.313. Static, Main, and Exceptions  »

nsf
Close Window