Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Beginning Algorithms (2006)

.pdf
Скачиваний:
255
Добавлен:
17.08.2013
Размер:
9.67 Mб
Скачать

Chapter 12

}

private final class ValueIterator implements Iterator { private Node _current;

public void first() {

_current = _root != null ? _root.minimum() : null;

}

public void last() {

_current = _root != null ? _root.maximum() : null;

}

public boolean isDone() { return _current == null;

}

public void next() { if (!isDone()) {

_current = _current.successor();

}

}

public void previous() { if (!isDone()) {

_current = _current.predecessor();

}

}

public Object current() throws IteratorOutOfBoundsException { if (isDone()) {

throw new IteratorOutOfBoundsException();

}

return _current.getValue();

}

}

}

How It Works

The TreeSetTest class extends AbstractSetTestCase to re-use all the tests you created earlier, with createSet() returning an instance of the TreeSet class.

The code for TreeSet follows very closely the code you developed for BinarySearchTree in Chapter 10, so the discussion of the code is confined to only the differences between it and the original

BinarySearchTree implementation.

The first difference, of course, is that TreeSet implements the Set interface. This means that the original insert() method is renamed to add(). Along with the name change, however, is a slight change in behavior. Whereas the original insert() method allowed duplicate values, the set semantics do not, and the code in add() has to be modified accordingly. The while loop in the original insert() method looked like this:

314

Sets

while (node != null) { parent = node;

cmp = _comparator.compare(value, node.getValue()); node = cmp <= 0 ? node.getSmaller() : node.getLarger();

}

Notice that when a duplicate value was inserted, it would always be added as a left child of any similar value. The add() method, however, cannot allow duplicates:

while (node != null) { parent = node;

cmp = _comparator.compare(value, node.getValue()); if (cmp == 0) {

return false;

}

node = cmp < 0 ? node.getSmaller() : node.getLarger();

}

Here, if an existing value is found (cmp == 0), the method returns false immediately to indicate that no change has been made; otherwise, it proceeds as per the original.

The next change is that the search() method has been made private, and in its place is the contains() method as required by the Set interface. The contains() method then returns true only if search() actually finds a matching node.

Apart from the addition of the clear(), isEmpty(), size(), and iterator() methods — again mandated by the Set interface — the only other change of note is that the Node class has been made an inner class, and there is an extra ValueIterator inner class that iterates forwards or backwards over the nodes, in order, by calling successor() and predecessor(), respectively.

There you have it: a set implementation that, as you know from Chapter 10, has an average performance of O(log N), and maintains the stored values in sorted order.

Summar y

This chapter demonstrated the following:

A set is a collection that contains only distinct values.

In general, a set makes no guarantee of the iteration order.

List-based sets are useful for relatively small data sets, as operations run in O(N).

Hash-table-based set operations run in O(1) with random iteration order.

Binary-search-tree-based sets provide O(log N) performance as well as predictable iteration order.

315

Chapter 12

Exercises

1.Write a method that takes two sets and determines whether they are equal.

2.Write a method that takes two sets and produces a third set containing the union of the first two.

3.Write a method that takes two sets and produces a third set containing the intersection of the first two.

4.Write a method that takes two sets and produces a third set containing the difference between the first two.

5.Update the delete() method in HashSet to free the bucket if it’s empty.

6.Create a set implementation that uses a sorted list.

7.Create a set implementation that is always empty and throws UnsupportedOperationException whenever an attempt is made to modify it.

316

13

Maps

Maps — also known as dictionaries, lookup tables, and associative arrays — are especially useful for building indexes.

This chapter discusses the following topics:

The basic operations of a map

A map implementation designed for small amounts of data, the list map

Another implementation that efficiently manages large amounts of unordered data, the hash map

A third type of map that has predictable iteration order, the tree map

Understanding Maps

A map holds an association between a key and a value. Each key within a map is unique and enables you to quickly set and retrieve an associated value. This can be useful for creating lookup tables in which a code is entered and a description is obtained or for building indexes that enable you to locate information — for example, a person’s record based on some pertinent details. Figure 13-1 shows a map in which the people’s names represent the keys, and the values are database record numbers.

One thing to remember about maps is that while the keys in a map are guaranteed to be unique, no such promise is made about the values. This can be useful, however. Imagine an index that maps telephone numbers to database records so that you can easily find someone using his or her phone number. It’s conceivable that a person might have more than one telephone number — home, business, cellular, and so on. In this case, multiple keys might map to the same record number. In Figure 13-2, you can see that Leonardo da Vinci can be contacted at two numbers: 555-123-4560 and 555-991-4511.

Chapter 13

Leonardo

da Vinci

Raphael

Record #5

Record #2

Michelangelo

Renoir

Record #1 Monet

Record #4

Record #3

Figure 13-1: An index that maps people’s names to their corresponding database record number.

Maps are also known as dictionaries, and it’s not too hard to understand why. (In fact, the original map class in the JDK was called Dictionary.) A language dictionary associates a word with a definition (or, in the case of a foreign language translation dictionary, another word). In these cases, the word is the key and the definition is the value.

Lastly, another name for a map is associative array. In fact, it’s possible to think of arrays — or lists, for that matter — as being quite similar to a map. Recall that arrays store values associated with a specific position, the index, and a map stores values associated with a specific key. Therefore, if you think of the index as being like a key, then in a sense, an array is like a specialized map.

Table 13-1 summarizes the operations provided by a map.

318

Maps

555-123-4560

 

 

 

 

 

 

 

 

 

 

 

 

 

555-918-2172

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Leonardo

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

da Vinci

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Raphael

 

 

555-836-1917

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

555-991-4511

 

 

 

 

 

 

 

 

555-334-7686

 

 

Monet

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Leonardo

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Renoir

 

 

 

 

 

da Vinci

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Figure 13-2: Keys are unique; values are not.

Table 13-1: Map Operations

Operation

Description

get

Obtains the value (if any) associated with a given key.

set

Sets the associated map with a given key. Returns the previous value (if any).

delete

Removes a value associated with a given key. Returns the value (if any).

contains

Determines whether a specified key exists in the map.

iterator

Obtains an Iterator over all key/value pairs in the map.

size

Obtains the number of key/value pairs in the map.

isEmpty

Determines whether the map is empty or not. Returns true if the set is

 

empty (size() == 0); otherwise, returns false.

clear

Deletes all key/value pairs from the map. The size of the map is reset to

 

zero.

319

Chapter 13

Maps enable you to set the value for a particular key, obtain the value (if any) associated with a given key, and remove the key/value pair all together. A map also enables you to iterate over the key/value pairs — also known as entries — and, just like sets, maps generally make no guarantee regarding ordering.

So that you can easily plug in different map implementations depending on the needs of your application, and, just as important, so that you can easily test each type of map, in the next Try It Out section, you create an interface that defines all the required methods.

Try It Out

Creating a Generic Map Interface

Create the Map interface as follows:

package com.wrox.algorithms.maps;

import com.wrox.algorithms.iteration.Iterable;

public interface Map extends Iterable { public Object get(Object key);

public Object set(Object key, Object value); public Object delete(Object key);

public boolean contains(Object key);

public void clear(); public int size(); public boolean isEmpty();

public static interface Entry { public Object getKey(); public Object getValue();

}

}

How It Works

The Map interface has all the operations listed in Table 13-1 translated into Java methods, and extends the Iterable interface so as to inherit the iterator() method and also be usable anywhere an Iterable is required. Notice the inner interface, Entry. This specifies a common interface for the key/value pairs contained within a map. Instances of Map.Entry will be returned from map iterators.

As well as the Map.Entry interface, in the next Try It Out section you also create a default Map.Entry implementation that will be used by all but one of the map classes later.

Try It Out

Creating a Default Entry Implementation

Create the DefaultEntry class as follows:

package com.wrox.algorithms.maps;

public class DefaultEntry implements Map.Entry { private final Object _key;

private Object _value;

320

Maps

public DefaultEntry(Object key, Object value) { assert key != null : “key can’t be null”; _key = key;

setValue(value);

}

public Object getKey() { return _key;

}

public Object setValue(Object value) { Object oldValue = _value;

_value = value; return oldValue;

}

public Object getValue() {

return _value;

}

public boolean equals(Object object) { if (this == object) {

return true;

}

if (object == null || getClass() != object.getClass()) { return false;

}

DefaultEntry other = (DefaultEntry) object;

return _key.equals(other._key) && _value.equals(other._value);

}

}

How It Works

The DefaultEntry class holds the key and value pair and makes each available via the getKey() and getValue() methods, respectively. There is also an equals() method to enable you to quickly determine whether two entries are equivalent. This will be used from within the tests that follow.

Notice that the key cannot be changed after construction — it is marked as final — as there is no need for it to change once assigned. However, the value can be modified. Also notice that while you must always provide a key, a value may be null. In practice, null keys are rarely, if ever, useful. However, null values occur all the time, especially with database applications. Figure 13-3 shows a typical situation in which a database record is represented as a map.

Notice that the values for Cell Phone and Drivers License are both null, indicating that both have been assigned, but that the values are unknown.

Lastly, recall that the map interface specifies that not only may a value for a given key be updated, but also that any previously assigned value will be returned. This behavior is reflected in the fact that the setValue() method returns the existing value.

321

Chapter 13

 

 

 

Date of Birth

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Cell Phone

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

1st January

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

1967

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

null

 

 

 

Name

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Drivers

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

David

 

 

 

 

Address

 

 

 

 

License

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

null

 

 

 

 

 

 

 

 

 

Somewhere

 

 

 

 

 

 

 

 

 

 

 

 

 

in London

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Figure 13-3: Keys in a map are mandatory, but values may be null.

You now have everything you need to start writing some generic tests: an interface with which any map implementation must conform, and a default Map.Entry implementation.

Testing Map Implementations

So that the tests may be re-used for any type of map, in the next Try It Out section you create an abstract test class containing all the test cases. This can then be extended with a test class specific to any map implementation that you create in the future.

Try It Out

Creating a Generic Suite of Map Tests

Create the AbstractMapTestCase class as follows:

package com.wrox.algorithms.maps;

import com.wrox.algorithms.iteration.Iterator;

import com.wrox.algorithms.iteration.ReverseIterator;

import com.wrox.algorithms.iteration.IteratorOutOfBoundsException; import com.wrox.algorithms.lists.LinkedList;

322

Maps

import com.wrox.algorithms.lists.List; import junit.framework.TestCase;

public abstract class AbstractMapTestCase extends TestCase {

private static final Map.Entry A = new DefaultEntry(“akey”, “avalue”); private static final Map.Entry B = new DefaultEntry(“bkey”, “bvalue”); private static final Map.Entry C = new DefaultEntry(“ckey”, “cvalue”); private static final Map.Entry D = new DefaultEntry(“dkey”, “dvalue”); private static final Map.Entry E = new DefaultEntry(“ekey”, “evalue”); private static final Map.Entry F = new DefaultEntry(“fkey”, “fvalue”);

private Map _map;

protected void setUp() throws Exception { super.setUp();

_map = createMap();

_map.set(C.getKey(), C.getValue()); _map.set(A.getKey(), A.getValue()); _map.set(B.getKey(), B.getValue()); _map.set(D.getKey(), D.getValue());

}

protected abstract Map createMap();

public void testContainsExisting() { assertTrue(_map.contains(A.getKey())); assertTrue(_map.contains(B.getKey())); assertTrue(_map.contains(C.getKey())); assertTrue(_map.contains(D.getKey()));

}

public void testContainsNonExisting() { assertFalse(_map.contains(E.getKey())); assertFalse(_map.contains(F.getKey()));

}

public void testGetExisting() { assertEquals(A.getValue(), _map.get(A.getKey())); assertEquals(B.getValue(), _map.get(B.getKey())); assertEquals(C.getValue(), _map.get(C.getKey())); assertEquals(D.getValue(), _map.get(D.getKey()));

}

public void testGetNonExisting() { assertNull(_map.get(E.getKey())); assertNull(_map.get(F.getKey()));

}

public void testSetNewKey() { assertEquals(4, _map.size());

assertNull(_map.set(E.getKey(), E.getValue()));

323