package com.jwetherell.algorithms.data_structures; import java.util.ArrayList; import java.util.List; import com.jwetherell.algorithms.data_structures.interfaces.IMap; /** * Hash Map using either chaining or probing. hash map is a data structure that * uses a hash function to map identifying values, known as keys, to their * associated values. * * http://en.wikipedia.org/wiki/Hash_table * * @author Justin Wetherell */ @SuppressWarnings("unchecked") public class HashMap implements IMap { public static enum Type { CHAINING, PROBING } private HashMap delegateMap = null; private static class ChainingHashMap extends HashMap { private float loadFactor = 10.0f; private int minimumSize = 1024; private int initialListSize = 10; private List>[] array = null; private int size = 0; /** * Create a hash map with K as the hashing key. * * @param size * initial size. */ public ChainingHashMap(int size) { initializeMap(size); } /** * Create a hash map with the default hashing key. */ public ChainingHashMap() { initializeMap(minimumSize); } /** * {@inheritDoc} */ @Override public V put(K key, V value) { return put(new Pair(key, value)); } public V put(Pair newPair) { int index = indexOf(newPair.key.hashCode()); List> list = array[index]; V prev = null; boolean exist = false; // Do not add duplicates for (Pair p : list) { if (p.key.equals(newPair.key)) { prev = p.value; p.value = newPair.value; exist = true; break; } } if (!exist) list.add(newPair); size++; // If size is greater than threshold int maxSize = (int)(loadFactor*array.length); if (size >= maxSize) increase(); return prev; } /** * {@inheritDoc} */ @Override public V get(K key) { int index = indexOf(key.hashCode()); List> list = array[index]; for (Pair p : list) { if (p.key.equals(key)) return p.value; } return null; } /** * {@inheritDoc} */ @Override public boolean contains(K key) { return (get(key)!=null); } /** * {@inheritDoc} */ @Override public V remove(K key) { int index = indexOf(key.hashCode()); List> list = array[index]; for (Pair pair : list) { if (pair.key.equals(key)) { list.remove(pair); size--; V value = pair.value; pair.key = null; pair.value = null; int loadFactored = (int)(size/loadFactor); int smallerSize = getSmallerSize(array.length); if (loadFactored < smallerSize && smallerSize > minimumSize) reduce(); return value; } } return null; } /** * {@inheritDoc} */ @Override public void clear() { for (int i=0; i>[] temp = this.array; // Calculate new size and assign int length = getLargerSize(array.length); //System.out.println("increase from "+array.length+" to "+length); initializeMap(length); // Re-hash old data for (List> list : temp) { for (Pair p :list) { this.put(p); } } } private void reduce() { // Save old data List>[] temp = this.array; // Calculate new size and check minimum int length = getSmallerSize(array.length); //System.out.println("reduce from "+array.length+" to "+length); initializeMap(length); // Re-hash old data for (List> list : temp) { for (Pair p :list) { this.put(p); } } } /** * Increases the input ten-fold. e.g. 16->160 */ private static final int getLargerSize(int input) { return input*10; } /** * Returns one fourth of the input. e.g. 16->4 */ private static final int getSmallerSize(int input) { return input/4; } /** * Initialize the hash array. */ private void initializeMap(int length) { this.array = new ArrayList[length]; for (int i = 0; i < array.length; i++) this.array[i] = new ArrayList>(initialListSize); this.size = 0; } /** * Converts the key into an integer. * * @param h * hash to get index of. * @param length * length of array * * @return Integer which represents the key. */ private int indexOf(int h) { return h & (array.length-1); } /** * {@inheritDoc} */ @Override public java.util.Map toMap() { return (new JavaCompatibleHashMap(this)); } /** * {@inheritDoc} */ @Override public boolean validate() { java.util.Set keys = new java.util.HashSet(); for (List> list : array) { for (Pair pair : list) { K k = pair.key; V v = pair.value; if (k==null || v==null) return false; if (keys.contains(k)) return false; keys.add(k); } } return (keys.size()==size()); } /** * {@inheritDoc} */ @Override public String toString() { StringBuilder builder = new StringBuilder(); for (int key = 0; key < array.length; key++) { List> list = array[key]; for (int item = 0; item < list.size(); item++) { Pair p = list.get(item); V value = p.value; if (value != null) builder.append(key).append("=").append(value).append(", "); } } return builder.toString(); } private static class JavaCompatibleHashMap extends java.util.AbstractMap { private ChainingHashMap map = null; protected JavaCompatibleHashMap(ChainingHashMap map) { this.map = map; } /** * {@inheritDoc} */ @Override public V put(K key, V value) { return map.put(key, value); } /** * {@inheritDoc} */ @Override public V remove(Object key) { return map.remove((K)key); } /** * {@inheritDoc} */ @Override public boolean containsKey(Object key) { return map.contains((K)key); } /** * {@inheritDoc} */ @Override public void clear() { map.clear(); } /** * {@inheritDoc} */ @Override public int size() { return map.size(); } /** * {@inheritDoc} */ @Override public java.util.Set> entrySet() { java.util.Set> set = new java.util.HashSet>() { private static final long serialVersionUID = 1L; /** * {@inheritDoc} */ @Override public java.util.Iterator> iterator() { return (new JavaCompatibleIteratorWrapper(map,super.iterator())); } }; for (List> list : map.array) { for (Pair p : list) { java.util.Map.Entry entry = new JavaCompatibleMapEntry(p.key, p.value); set.add(entry); } } return set; } } } private static class ProbingHashMap extends HashMap { private int hashingKey = -1; private float loadFactor = 0.6f; private int minimumSize = 1024; private Pair[] array = null; private int size = 0; /** * Create a hash map with K as the hash. * * @param size * to use for the hash. */ public ProbingHashMap(int size) { initializeMap(size); } /** * Create a hash map with the default hashing key. */ public ProbingHashMap() { initializeMap(minimumSize); } /** * {@inheritDoc} */ @Override public V put(K key, V value) { return put(new Pair(key,value)); } private V put(Pair newPair) { V prev = null; int index = indexOf(newPair.key); // Check initial position Pair pair = array[index]; if (pair == null) { array[index] = newPair; size++; // If size is greater than threshold int maxSize = (int)(loadFactor*array.length); if (size >= maxSize) increase(); return prev; } if (pair.key.equals(newPair.key)) { prev = pair.value; pair.value = newPair.value; return prev; } // Probing until we get back to the starting index int start = getNextIndex(index); while (start != index) { pair = array[start]; if (pair == null) { array[start] = newPair; size++; // If size is greater than threshold int maxSize = (int)(loadFactor*array.length); if (size >= maxSize) increase(); return prev; } if (pair.key.equals(newPair.key)) { prev = pair.value; pair.value = newPair.value; return prev; } start = getNextIndex(start); } // We should never get here. return null; } /** * {@inheritDoc} */ @Override public V get(K key) { int index = indexOf(key); Pair pair = array[index]; // Check initial position if (pair == null) return null; if (pair.key.equals(key)) return pair.value; // Probing until we get back to the starting index int start = getNextIndex(index); while (start != index) { pair = array[start]; if (pair == null) return null; if (pair.key.equals(key)) return pair.value; start = getNextIndex(start); } // If we get here, probing failed. return null; } /** * {@inheritDoc} */ @Override public boolean contains(K key) { return (get(key)!=null); } /** * {@inheritDoc} */ @Override public V remove(K key) { int index = indexOf(key); Pair prev = null; // Check initial position Pair pair = array[index]; if (pair != null && pair.key.equals(key)) { prev = array[index]; array[index] = null; size--; int loadFactored = (int)(size/loadFactor); int smallerSize = getSmallerSize(array.length); if (loadFactored < smallerSize && smallerSize > minimumSize) reduce(); return prev.value; } // Probing until we get back to the starting index int start = getNextIndex(index); while (start != index) { pair = array[start]; if (pair != null && pair.key.equals(key)) { prev = array[start]; array[start] = null; size--; int loadFactored = (int)(size/loadFactor); int smallerSize = getSmallerSize(array.length); if (loadFactored < smallerSize && smallerSize > minimumSize) reduce(); return prev.value; } start = getNextIndex(start); } // If we get here, probing failed. return null; } /** * {@inheritDoc} */ @Override public void clear() { for (int i=0; i[] temp = this.array; // Calculate new size and assign int length = getLargerSize(array.length); //System.out.println("increase from "+array.length+" to "+length); initializeMap(length); // Re-hash old data for (Pair p : temp) { if (p != null) this.put(p); } } private void reduce() { // Save old data Pair[] temp = this.array; // Calculate new size and check minimum int length = getSmallerSize(array.length); //System.out.println("reduce from "+array.length+" to "+length); initializeMap(length); // Re-hash old data for (Pair p : temp) { if (p != null) this.put(p); } } /** * Doubles the input. e.g. 16->32 */ private static final int getLargerSize(int input) { return input<<1; } /** * Returns one fourth of the input. e.g. 16->8->4 */ private static final int getSmallerSize(int input) { return input>>1>>1; } /** * Returns the next index in the probing sequence, at this point it's linear */ private int getNextIndex(int input) { int i = input+1; if (i >= array.length) i = 0; return i; } /** * The hashing function. Converts the key into an integer. * * @param key * to create a hash for. * @return Integer which represents the key. */ private int indexOf(K key) { int k = key.hashCode() % hashingKey; if (k>=array.length) k = k - ((k/array.length) * array.length); return k; } /** * {@inheritDoc} */ @Override public java.util.Map toMap() { return (new JavaCompatibleHashMap(this)); } /** * {@inheritDoc} */ @Override public boolean validate() { java.util.Set keys = new java.util.HashSet(); for (Pair pair : array) { if (pair == null) continue; K k = pair.key; V v = pair.value; if (k==null || v==null) return false; if (keys.contains(k)) return false; keys.add(k); } return (keys.size()==size()); } /** * {@inheritDoc} */ @Override public String toString() { StringBuilder builder = new StringBuilder(); for (int key = 0; key < array.length; key++) { Pair p = array[key]; if (p == null) continue; V value = p.value; if (value != null) builder.append(key).append("=").append(value).append(", "); } return builder.toString(); } private static class JavaCompatibleHashMap extends java.util.AbstractMap { private ProbingHashMap map = null; protected JavaCompatibleHashMap(ProbingHashMap map) { this.map = map; } /** * {@inheritDoc} */ @Override public V put(K key, V value) { return map.put(key, value); } /** * {@inheritDoc} */ @Override public V remove(Object key) { return map.remove((K)key); } /** * {@inheritDoc} */ @Override public boolean containsKey(Object key) { return map.contains((K)key); } /** * {@inheritDoc} */ @Override public void clear() { map.clear(); } /** * {@inheritDoc} */ @Override public int size() { return map.size(); } /** * {@inheritDoc} */ @Override public java.util.Set> entrySet() { java.util.Set> set = new java.util.HashSet>() { private static final long serialVersionUID = 1L; /** * {@inheritDoc} */ @Override public java.util.Iterator> iterator() { return (new JavaCompatibleIteratorWrapper(map,super.iterator())); } }; for (Pair p : map.array) { if (p==null) continue; java.util.Map.Entry entry = new JavaCompatibleMapEntry(p.key, p.value); set.add(entry); } return set; } } } /** * Create a hash map with K as the hashing key. * * @param type * type of hashing to use. * @param size * initialize size. */ public HashMap(Type type, int size) { if (type == Type.CHAINING) { delegateMap = new ChainingHashMap(size); } else if (type == Type.PROBING) { delegateMap = new ProbingHashMap(size); } } /** * Create a hash map with the default hashing key. * * @param type * type of hashing to use. */ public HashMap(Type type) { if (type == Type.CHAINING) { delegateMap = new ChainingHashMap(); } else if (type == Type.PROBING) { delegateMap = new ProbingHashMap(); } } private HashMap() { } /** * {@inheritDoc} */ @Override public V put(K key, V value) { return delegateMap.put(key, value); } /** * {@inheritDoc} */ @Override public V get(K key) { return delegateMap.get(key); } /** * {@inheritDoc} */ @Override public boolean contains(K key) { return (get(key)!=null); } /** * {@inheritDoc} */ @Override public V remove(K key) { return delegateMap.remove(key); } /** * {@inheritDoc} */ @Override public void clear() { delegateMap.clear(); } /** * {@inheritDoc} */ @Override public int size() { return delegateMap.size(); } /** * {@inheritDoc} */ @Override public java.util.Map toMap() { return delegateMap.toMap(); } /** * {@inheritDoc} */ @Override public boolean validate() { return delegateMap.validate(); } /** * {@inheritDoc} */ @Override public String toString() { return delegateMap.toString(); } private static final class Pair { private K key = null; private V value = null; public Pair(K key, V value) { this.key = key; this.value = value; } /** * {@inheritDoc} */ @Override public int hashCode() { return 31 * (this.key.hashCode()); } /** * {@inheritDoc} */ @Override public boolean equals(Object obj) { if (obj == null) return false; if (!(obj instanceof Pair)) return false; Pair pair = (Pair) obj; return key.equals(pair.key); } } private static class JavaCompatibleIteratorWrapper implements java.util.Iterator> { private HashMap map = null; private java.util.Iterator> iter = null; private java.util.Map.Entry lastEntry = null; public JavaCompatibleIteratorWrapper(HashMap map, java.util.Iterator> iter) { this.map = map; this.iter = iter; } /** * {@inheritDoc} */ @Override public boolean hasNext() { if (iter==null) return false; return iter.hasNext(); } /** * {@inheritDoc} */ @Override public java.util.Map.Entry next() { if (iter==null) return null; lastEntry = iter.next(); return lastEntry; } /** * {@inheritDoc} */ @Override public void remove() { if (iter==null || lastEntry==null) return; map.remove(lastEntry.getKey()); iter.remove(); } } private static class JavaCompatibleMapEntry extends java.util.AbstractMap.SimpleEntry { private static final long serialVersionUID = 3282082818647198608L; public JavaCompatibleMapEntry(K key, V value) { super(key, value); } } }