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

Beginning Algorithms (2006)

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

Chapter 13

Then create the hash map implementation:

package com.wrox.algorithms.maps;

import com.wrox.algorithms.hashing.HashtableIterator; import com.wrox.algorithms.iteration.ArrayIterator; import com.wrox.algorithms.iteration.Iterator;

public class HashMap implements Map {

public static final int DEFAULT_CAPACITY = 17;

public static final float DEFAULT_LOAD_FACTOR = 0.75f;

private final int _initialCapacity; private final float _loadFactor; private ListMap[] _buckets; private int _size;

public HashMap() {

this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR);

}

public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR);

}

public HashMap(int initialCapacity, float loadFactor) {

assert initialCapacity > 0 : “initialCapacity can’t be < 1”; assert loadFactor > 0 : “loadFactor can’t be <= 0”;

_initialCapacity = initialCapacity; _loadFactor = loadFactor;

clear();

}

public Object get(Object key) {

ListMap bucket = _buckets[bucketIndexFor(key)]; return bucket != null ? bucket.get(key) : null;

}

public Object set(Object key, Object value) { ListMap bucket = bucketFor(key);

int sizeBefore = bucket.size();

Object oldValue = bucket.set(key, value); if (bucket.size() > sizeBefore) {

++_size; maintainLoad();

}

return oldValue;

}

public Object delete(Object key) {

ListMap bucket = _buckets[bucketIndexFor(key)];

334

Maps

if (bucket == null) { return null;

}

int sizeBefore = bucket.size(); Object value = bucket.delete(key); if (bucket.size() < sizeBefore) {

--_size;

}

return value;

}

public boolean contains(Object key) {

ListMap bucket = _buckets[bucketIndexFor(key)]; return bucket != null && bucket.contains(key);

}

public Iterator iterator() {

return new HashtableIterator(new ArrayIterator(_buckets));

}

public void clear() {

_buckets = new ListMap[_initialCapacity]; _size = 0;

}

public int size() { return _size;

}

public boolean isEmpty() { return size() == 0;

}

private int bucketIndexFor(Object key) { assert key != null : “key can’t be null”;

return Math.abs(key.hashCode() % _buckets.length);

}

private ListMap bucketFor(Object key) { int bucketIndex = bucketIndexFor(key); ListMap bucket = _buckets[bucketIndex]; if (bucket == null) {

bucket = new ListMap(); _buckets[bucketIndex] = bucket;

}

return bucket;

}

private void maintainLoad() { if (loadFactorExceeded()) {

resize();

335

Chapter 13

}

}

private boolean loadFactorExceeded() {

return size() > _buckets.length * _loadFactor;

}

private void resize() {

HashMap copy = new HashMap(_buckets.length * 2, _loadFactor);

for (int i = 0; i < _buckets.length; ++i) { if (_buckets[i] != null) {

copy.addAll(_buckets[i].iterator());

}

}

_buckets = copy._buckets;

}

private void addAll(Iterator entries) {

assert entries != null : “entries can’t be null”;

for (entries.first(); !entries.isDone(); entries.next()) { Map.Entry entry = (Map.Entry) entries.current(); set(entry.getKey(), entry.getValue());

}

}

}

How It Works

The HashMapTest class extends AbstractMapTestCase in order to re-use all the tests you created earlier. Other than that, all that you need to do is implement createMap() to return an instance of the

HashMap class.

For the most part, the HashMap class is a copy of the code for BucketingHashtable introduced in Chapter 11. For this reason, the discussion concentrates only on the differences between the HashMap and the original BucketingHashtable code.

In addition to implementing the Map interface, probably the first thing you’ll notice — besides a few constants and convenience constructors — is that you’ve used a ListMap for the buckets instead of a List as in the original BucketingHashtable code. You can think of the hash map as distributing (hopefully, fairly evenly) the key/value pairs between list maps. You know from Chapter 11 that the buckets will be kept relatively small, so the list-based maps will perform just fine. Therefore, by using a map instead of a list for your buckets, you simplify the code — once the appropriate bucket has been found, all the work of adding the key/value pair is delegated to it. This can be seen in the code for get(), set(), delete(), and contains(), where most of the work is carried out by the bucket, leaving the hash map code to perform the housekeeping duties, such as resizing, and so on.

The other obvious difference between HashMap and BucketingHashtable is that the buckets store entries. Therefore, when resizing, addAll() iterates through each of the key/value pairs, rather than just the values, as was the case in the original.

336

Maps

Lastly, because a Map is also an Iterable — and therefore so is ListMap — you can re-use the HashtableIterator from Chapter 11 to iterate over the entries.

Assuming a good hash function for the keys, you can expect to achieve fairly close to O(1) performance for the HashMap.

A Tree Map

As previously mentioned, maps don’t generally guarantee any particular ordering of the keys: A ListMap, for example, will present the entries in order of insertion, whereas entries from a HashMap iterator will appear somewhat randomly. Sometimes, however, you may want a predictable ordering of the keys. In this case, a map implementation based on binary search trees is ideal.

Before proceeding with the implementation of tree-based maps, we recommend that you revisit binary search trees (see Chapter 10) to refresh your understanding of the core concepts and code, as once again, the discussion will be limited to only the differences between the TreeMap code presented here and the original BinarySearchTree code.

Try It Out

Testing and Implementing a Tree Map

Starting by creating the TreeMapTest class as follows:

package com.wrox.algorithms.maps;

public class TreeMapTest extends AbstractMapTestCase { protected Map createMap() {

return new TreeMap();

}

}

Follow with the tree map implementation:

package com.wrox.algorithms.maps;

import com.wrox.algorithms.iteration.Iterator;

import com.wrox.algorithms.iteration.IteratorOutOfBoundsException; import com.wrox.algorithms.sorting.Comparator;

import com.wrox.algorithms.sorting.NaturalComparator;

public class TreeMap implements Map { private final Comparator _comparator;

private Node _root; private int _size;

public TreeMap() { this(NaturalComparator.INSTANCE);

}

public TreeMap(Comparator comparator) {

assert comparator != null : “comparator can’t be null”;

337

Chapter 13

_comparator = comparator;

}

public boolean contains(Object key) { return search(key) != null;

}

public Object get(Object key) { Node node = search(key);

return node != null ? node.getValue() : null;

}

public Object set(Object key, Object value) { Node parent = null;

Node node = _root; int cmp = 0;

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

cmp = _comparator.compare(key, node.getKey()); if (cmp == 0) {

return node.setValue(value);

}

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

}

Node inserted = new Node(parent, key, value);

if (parent == null) { _root = inserted;

}else if (cmp < 0) { parent.setSmaller(inserted);

}else {

parent.setLarger(inserted);

}

++_size; return null;

}

public Object delete(Object key) { Node node = search(key);

if (node == null) { return null;

}

Node deleted = node.getSmaller() != null && node.getLarger() != null ? node.successor() : node;

assert deleted != null : “deleted can’t be null”;

Node replacement = deleted.getSmaller() != null ? deleted.getSmaller() : deleted.getLarger();

if (replacement != null) { replacement.setParent(deleted.getParent());

338

Maps

}

if (deleted == _root) { _root = replacement;

}else if (deleted.isSmaller()) { deleted.getParent().setSmaller(replacement);

}else {

deleted.getParent().setLarger(replacement);

}

if (deleted != node) {

Object deletedValue = node.getValue(); node.setKey(deleted.getKey()); node.setValue(deleted.getValue()); deleted.setValue(deletedValue);

}

--_size;

return deleted.getValue();

}

public Iterator iterator() { return new EntryIterator();

}

public void clear() { _root = null; _size = 0;

}

public int size() { return _size;

}

public boolean isEmpty() { return _root == null;

}

private Node search(Object value) {

assert value != null : “value can’t be null”;

Node node = _root;

while (node != null) {

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

break;

}

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

}

return node;

}

private static final class Node implements Map.Entry {

339

Chapter 13

private Object _key; private Object _value; private Node _parent; private Node _smaller; private Node _larger;

public Node(Node parent, Object key, Object value) { setKey(key);

setValue(value);

setParent(parent);

}

public Object getKey() { return _key;

}

public void setKey(Object key) {

assert key != null : “key can’t be null”; _key = key;

}

public Object getValue() { return _value;

}

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

_value = value; return oldValue;

}

public Node getParent() { return _parent;

}

public void setParent(Node parent) { _parent = parent;

}

public Node getSmaller() { return _smaller;

}

public void setSmaller(Node node) {

assert node != getLarger() : “smaller can’t be the same as larger”; _smaller = node;

}

public Node getLarger() { return _larger;

}

public void setLarger(Node node) {

340

Maps

assert node != getSmaller() : “larger can’t be the same as smaller”; _larger = node;

}

public boolean isSmaller() {

return getParent() != null && this == getParent().getSmaller();

}

public boolean isLarger() {

return getParent() != null && this == getParent().getLarger();

}

public Node minimum() { Node node = this;

while (node.getSmaller() != null) { node = node.getSmaller();

}

return node;

}

public Node maximum() { Node node = this;

while (node.getLarger() != null) { node = node.getLarger();

}

return node;

}

public Node successor() {

if (getLarger() != null) { return getLarger().minimum();

}

Node node = this;

while (node.isLarger()) { node = node.getParent();

}

return node.getParent();

}

public Node predecessor() {

if (getSmaller() != null) { return getSmaller().maximum();

}

Node node = this;

while (node.isSmaller()) { node = node.getParent();

341

Chapter 13

}

return node.getParent();

}

}

private final class EntryIterator 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;

}

}

}

How It Works

The TreeMapTest class extends AbstractMapTestCase to re-use all of the tests you created earlier with createMap(), returning an instance of the TreeMap class.

The code for TreeMap follows very closely the code you developed for BinarySearchTree in Chapter 10. Besides the fact that this class implements the Map interface, the most obvious difference you’ll see if you browse the code is that almost everywhere the original code referenced a value, TreeMap uses the key. To this end, the comparator is used to compare keys, not values, and thus the tree is ordered by key.

You’ll also notice that Node has been made an inner class; and, instead of having each node hold an entry, you’ve instead made Node implement Map.Entry directly. The original node implementation

342

Maps

already held a value, so all you needed to do was add a key and modify setValue() slightly to return the original value, just as you did with the DefaultEntry earlier.

Next, the original insert() method has been renamed to set(). Whereas the original insert() method worked off values and allowed duplicates, the map uses keys, all of which must be unique. Additionally, set() returns any value previously associated with the key.

The while loop in the original insert() method looked like this:

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 set() method now looks like this:

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

cmp = _comparator.compare(key, node.getKey()); if (cmp == 0) {

return node.setValue(value);

}

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

}

Here, if an existing key is found (cmp == 0), the method updates the value and returns the old value immediately; otherwise, the code proceeds as per the original.

The next change is that the search() method has been made private, and instead there is the contains() method as required by the Map 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 Map interface — the only other change of note is the EntryIterator inner class, which iterates forwards or backwards over the nodes in order — and therefore the entries — by calling successor() and predecessor(), respectively.

That’s it: a map implementation that, as you know from Chapter 10, has an average performance of O(log N) as well as the added bonus of maintaining the entries in order, sorted by key.

Summar y

This chapter demonstrated the following:

Maps store values associated with a key.

Each key within a map is unique and enables you to quickly locate its associated value.

343