In: Computer Science
In this lab, you will implement and evaluate the performance (run time and # of conflicts), for different load factors, of three hashing schemes: • linear, • quadratic, and • double hashing. The required files for this lab are Driver.java, DoubleHash.java, KeyValuePair.java, LinearProbe.java, PrimeNumber.java, QuadraticProbe.java, and country-capitals.xls. You are required to submit a short report comparing, using plots, the performance of the hashing schemes for adding and retrieving items for different load factors (try at least 0.25, 0.5, 0.65).
/** * Quadratic probing class */ public class QuadraticProbe<E> { // status contain 1 if an item is present, 0 if not private int[] status; // stores the elements of type KeyValuePair<E> private KeyValuePair<E>[] table; // constructor public QuadraticProbe(int lengthOfTable) { status = new int[lengthOfTable]; table = new KeyValuePair[lengthOfTable]; } public int[] getStatus() { return status; } public KeyValuePair<E>[] getTable() { return table; } /** * hashing function * @param str * @return int: hashed value of string str */ public int hash(String str) { return Math.abs(str.hashCode())%status.length; } /** * TO BE COMPLETED * method to add an item of type KeyValuePair<E> * @param kv of type KeyValuePair<E> * @return int: number of collisions due to addition of kv */ public int add(KeyValuePair<E> kv) { } /** * TO BE COMPLETED * method to retrieve an item from the hashtable * given the key to look for * @param String: key * @return KeyValuePair<E>: item */ public KeyValuePair<E> retrieve(String key){ } }
public class PrimeNumber { public boolean isPrime(int n) { for(int i = 2; i*i <=n ;i++) { if (n%i == 0) { return false; } } return true; } public int findNextPrime(int x) { if (x%2 == 0) x++; while(!isPrime(x)) { x+=2; } return x; } }
/** * Linear probing class */ public class LinearProbe<E> { // status contain 1 if an item is present, 0 if not private int[] status; // stores the elements of type KeyValuePair<E> private KeyValuePair<E>[] table; // constructor public LinearProbe(int lengthOfTable) { status = new int[lengthOfTable]; table = new KeyValuePair[lengthOfTable]; } public int[] getStatus() { return status; } public KeyValuePair<E>[] getTable() { return table; } /** * hashing function * @param str * @return int: hashed value of string str */ public int hash(String str) { return Math.abs(str.hashCode())%status.length; } /** * TO BE COMPLETED * method to add an item of type KeyValuePair<E> * @param kv of type KeyValuePair<E> * @return int: number of collisions due to addition of kv */ public int add(KeyValuePair<E> kv) { } /** * TO BE COMPLETED * method to retrieve an item from the hashtable * given the key to look for * @param String: key * @return KeyValuePair<E>: item */ public KeyValuePair<E> retrieve(String key){ } }
public class KeyValuePair<E> { private String key; private E value; public KeyValuePair(String k, E val){ key = k; value = val; } public String getKey() { return key; } public E getValue() { return value; } }
import java.io.File; import java.io.FileNotFoundException; import java.util.Scanner; import java.util.ArrayList; import java.util.Random; /** * Driver class to test the three implementations of: * Linear Probing, Quadratic Probing, and Double Hashing */ public class Driver { public static <E> void main(String args[]) { // Create array list to store KeyValue Pairs from file ArrayList<KeyValuePair<E>> list = new ArrayList<KeyValuePair<E>>(); try { // Read file and store values in list Scanner scanner = new Scanner(new File("country-capitals.csv")); while (scanner.hasNextLine()) { String line = scanner.nextLine(); String[] splitLine = line.split(","); list.add(new KeyValuePair(splitLine[0], splitLine[1])); } // TO COMPLETE : Choose/change load factor int loadFactor = 50; PrimeNumber primeNumber = new PrimeNumber(); int tableSize = primeNumber.findNextPrime(list.size()*100/loadFactor); System.out.println(tableSize); // Create instances of the 3 hash classes LinearProbe<E> linHash = new LinearProbe(tableSize); QuadraticProbe<E> quadHash = new QuadraticProbe(tableSize); DoubleHash<E> doubleHash = new DoubleHash(tableSize); // Create placeholders for storing number of collisions and runtimes int numCollisionsLin = 0, numCollisionsQuad = 0, numCollisionsDouble = 0; long runTimeLin = 0, runTimeQuad = 0, runTimeDouble = 0; // TEST for addition of elements // Iterate over list and add elements to respective hash objects for (KeyValuePair<E> kv : list) { // Linear probing long startTime = System.nanoTime(); int linCollision = linHash.add(kv); runTimeLin += System.nanoTime() - startTime; // Quadratic probing startTime = System.nanoTime(); int quadCollision = quadHash.add(kv); runTimeQuad += System.nanoTime() - startTime; // Double hashing startTime = System.nanoTime(); int doubleCollision = doubleHash.add(kv); runTimeDouble += System.nanoTime() - startTime; numCollisionsLin += linCollision; numCollisionsQuad += quadCollision; numCollisionsDouble += doubleCollision; } System.out.println("Adding time and collision analysis:"); System.out.println("Linear Probing had " + numCollisionsLin + " collisions with runtime " + runTimeLin + " ns"); System.out.println("Quadratic Probing had " + numCollisionsQuad + " collisions with runtime " + runTimeQuad + " ns"); System.out.println("Double Probing had " + numCollisionsDouble + " collisions with runtime " + runTimeDouble + " ns"); // Create arraylist and store random elements to be retrieved ArrayList<KeyValuePair<E>> randomList = new ArrayList<KeyValuePair<E>>(); Random randomNumGenerator = new Random(); for (int i = 0; i < 200; i++) { randomList.add(list.get(randomNumGenerator.nextInt(list.size()))); } // TEST for retrieval of elements runTimeLin = 0; runTimeQuad = 0; runTimeDouble = 0; for (KeyValuePair<E> kv : randomList) { // Linear probing long startTime = System.nanoTime(); KeyValuePair<E> item = linHash.retrieve(kv.getKey()); runTimeLin += System.nanoTime() - startTime; // Quadratic probing startTime = System.nanoTime(); item = quadHash.retrieve(kv.getKey()); runTimeQuad += System.nanoTime() - startTime; // Double hashing startTime = System.nanoTime(); item = doubleHash.retrieve(kv.getKey()); runTimeDouble += System.nanoTime() - startTime; } System.out.println("\nRetrieval time analysis:"); System.out.println("Linear Probing retrieval time: " + runTimeLin + " ns"); System.out.println("Quadratic Probing retrieval time: " + runTimeQuad + " ns"); System.out.println("Double Probing retrieval time: " + runTimeDouble + " ns"); } catch (FileNotFoundException e) { e.printStackTrace(); } } }
/** * Double hash class */ public class DoubleHash<E> { // status contain 1 if an item is present, 0 if not private int[] status; // stores the elements of type KeyValuePair<E> private KeyValuePair<E>[] table; // constructor public DoubleHash(int lengthOfTable) { status = new int[lengthOfTable]; table = new KeyValuePair[lengthOfTable]; } public int[] getStatus() { return status; } public KeyValuePair<E>[] getTable() { return table; } /** * hashing function * @param str * @return int: hashed value of string str */ public int hash1(String str) { return Math.abs(str.hashCode())%status.length; } /** * hashing function # 2 * TO BE COMPLETED * must return a hash value for string str * representing step size for double hash * @param str * @return int: hashed value of string str */ public int hash2(String str) { } /** * TO BE COMPLETED * method to add an item of type KeyValuePair<E> * @param kv of type KeyValuePair<E> * @return int: number of collisions due to addition of kv */ public int add(KeyValuePair<E> kv) { } /** * TO BE COMPLETED * method to retrieve an item from the hashtable * given the key to look for * @param String: key * @return KeyValuePair<E>: item */ public KeyValuePair<E> retrieve(String key){ } }
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.List;
@SuppressWarnings("unchecked")
public abstract class HashTableOpenAddressingBase<K, V>
implements Iterable<K> {
protected double loadFactor;
protected int capacity, threshold,modificationCount;
// 'usedBuckets' counts the total number of used buckets inside
the
// hash-table (includes cells marked as deleted). While
'keyCount'
// tracks the number of unique keys currently inside the
hash-table.
protected int usedBuckets, keyCount;
// These arrays store the key-value pairs.
protected K[] keys;
protected V[] values;
// Special marker token used to indicate the deletion of a
key-value pair
protected final K TOMBSTONE = (K) (new Object());
private static final int DEFAULT_CAPACITY = 7;
private static final double DEFAULT_LOAD_FACTOR = 0.65;
protected HashTableOpenAddressingBase() {
this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR);
}
protected HashTableOpenAddressingBase(int capacity) {
this(capacity, DEFAULT_LOAD_FACTOR);
}
// Designated constructor
protected HashTableOpenAddressingBase(int capacity, double
loadFactor) {
if (capacity <= 0) throw new IllegalArgumentException("Illegal
capacity: " + capacity);
if (loadFactor <= 0 || Double.isNaN(loadFactor) ||
Double.isInfinite(loadFactor))
throw new IllegalArgumentException("Illegal loadFactor: " +
loadFactor);
this.loadFactor = loadFactor;
this.capacity = Math.max(DEFAULT_CAPACITY, capacity);
adjustCapacity();
threshold = (int) (this.capacity * loadFactor);
keys = (K[]) new Object[this.capacity];
values = (V[]) new Object[this.capacity];
}
// These three methods are used to dictate how the probing is to
actually
// occur for whatever open addressing scheme you are
implementing.
protected abstract void setupProbing(K key);
protected abstract int probe(int x);
// Adjusts the capacity of the hash table after it's been made
larger.
// This is important to be able to override because the size of the
hashtable
// controls the functionality of the probing function.
protected abstract void adjustCapacity();
// Increases the capacity of the hash table.
protected void increaseCapacity() {
capacity = (2 * capacity) + 1;
}
public void clear() {
for (int i = 0; i < capacity; i++) {
keys[i] = null;
values[i] = null;
}
keyCount = usedBuckets = 0;
modificationCount++;
}
// Returns the number of keys currently inside the
hash-table
public int size() {
return keyCount;
}
// Returns the capacity of the hashtable (used mostly for
testing)
public int getCapacity() {
return capacity;
}
// Returns true/false depending on whether the hash-table is
empty
public boolean isEmpty() {
return keyCount == 0;
}
public V put(K key, V value) {
return insert(key, value);
}
public V add(K key, V value) {
return insert(key, value);
}
// Returns true/false on whether a given key exists within the
hash-table.
public boolean containsKey(K key) {
return hasKey(key);
}
// Returns a list of keys found in the hash table
public List<K> keys() {
List<K> hashtableKeys = new ArrayList<>(size());
for (int i = 0; i < capacity; i++)
if (keys[i] != null && keys[i] != TOMBSTONE)
hashtableKeys.add(keys[i]);
return hashtableKeys;
}
// Returns a list of non-unique values found in the hash
table
public List<V> values() {
List<V> hashtableValues = new
ArrayList<>(size());
for (int i = 0; i < capacity; i++)
if (keys[i] != null && keys[i] != TOMBSTONE)
hashtableValues.add(values[i]);
return hashtableValues;
}
// Double the size of the hash-table
protected void resizeTable() {
increaseCapacity();
adjustCapacity();
threshold = (int) (capacity * loadFactor);
K[] oldKeyTable = (K[]) new Object[capacity];
V[] oldValueTable = (V[]) new Object[capacity];
// Perform key table pointer swap
K[] keyTableTmp = keys;
keys = oldKeyTable;
oldKeyTable = keyTableTmp;
// Perform value table pointer swap
V[] valueTableTmp = values;
values = oldValueTable;
oldValueTable = valueTableTmp;
// Reset the key count and buckets used since we are about
to
// re-insert all the keys into the hash-table.
keyCount = usedBuckets = 0;
for (int i = 0; i < oldKeyTable.length; i++) {
if (oldKeyTable[i] != null && oldKeyTable[i] !=
TOMBSTONE)
insert(oldKeyTable[i], oldValueTable[i]);
oldValueTable[i] = null;
oldKeyTable[i] = null;
}
}
// Converts a hash value to an index. Essentially, this strips
the
// negative sign and places the hash value in the domain [0,
capacity)
protected final int normalizeIndex(int keyHash) {
return (keyHash & 0x7FFFFFFF) % capacity;
}
// Finds the greatest common denominator of a and b.
protected static final int gcd(int a, int b) {
if (b == 0) return a;
return gcd(b, a % b);
}
// Place a key-value pair into the hash-table. If the value
already
// exists inside the hash-table then the value is updated
public V insert(K key, V val) {
if (key == null) throw new IllegalArgumentException("Null
key");
if (usedBuckets >= threshold) resizeTable();
setupProbing(key);
final int offset = normalizeIndex(key.hashCode());
for (int i = offset, j = -1, x = 1; ; i = normalizeIndex(offset + probe(x++))) {
// The current slot was previously deleted
if (keys[i] == TOMBSTONE) {
if (j == -1) j = i;
// The current cell already contains a key
} else if (keys[i] != null) {
// The key we're trying to insert already exists in the
hash-table,
// so update its value with the most recent value
if (keys[i].equals(key)) {
V oldValue = values[i];
if (j == -1) {
values[i] = val;
} else {
keys[i] = TOMBSTONE;
values[i] = null;
keys[j] = key;
values[j] = val;
}
modificationCount++;
return oldValue;
}
// Current cell is null so an insertion/update can occur
} else {
// No previously encountered deleted buckets
if (j == -1) {
usedBuckets++;
keyCount++;
keys[i] = key;
values[i] = val;
// Previously seen deleted bucket. Instead of inserting
// the new element at i where the null element is insert
// it where the deleted token was found.
} else {
keyCount++;
keys[j] = key;
values[j] = val;
}
modificationCount++;
return null;
}
}
}
// Returns true/false on whether a given key exists within the
hash-table
public boolean hasKey(K key) {
if (key == null) throw new IllegalArgumentException("Null
key");
setupProbing(key);
final int offset = normalizeIndex(key.hashCode());
// Starting at the original hash linearly probe until we find a
spot where
// our key is or we hit a null element in which case our element
does not exist.
for (int i = offset, j = -1, x = 1; ; i = normalizeIndex(offset +
probe(x++))) {
// Ignore deleted cells, but record where the first index
// of a deleted cell is found to perform lazy relocation
later.
if (keys[i] == TOMBSTONE) {
if (j == -1) j = i;
// We hit a non-null key, perhaps it's the one we're looking
for.
} else if (keys[i] != null) {
// The key we want is in the hash-table!
if (keys[i].equals(key)) {
// If j != -1 this means we previously encountered a deleted
cell.
// We can perform an optimization by swapping the entries in
cells
// i and j so that the next time we search for this key it will
be
// found faster. This is called lazy deletion/relocation.
if (j != -1) {
// Swap the key-value pairs of positions i and j.
keys[j] = keys[i];
values[j] = values[i];
keys[i] = TOMBSTONE;
values[i] = null;
}
return true;
}
// Key was not found in the hash-table :/
} else return false;
}
}
// Get the value associated with the input key.
// NOTE: returns null if the value is null AND also returns
// null if the key does not exists.
public V get(K key) {
if (key == null) throw new IllegalArgumentException("Null
key");
setupProbing(key);
final int offset = normalizeIndex(key.hashCode());
// Starting at the original hash linearly probe until we find a
spot where
// our key is or we hit a null element in which case our element
does not exist.
for (int i = offset, j = -1, x = 1; ; i = normalizeIndex(offset +
probe(x++))) {
// Ignore deleted cells, but record where the first index
// of a deleted cell is found to perform lazy relocation
later.
if (keys[i] == TOMBSTONE) {
if (j == -1) j = i;
// We hit a non-null key, perhaps it's the one we're looking
for.
} else if (keys[i] != null) {
// The key we want is in the hash-table!
if (keys[i].equals(key)) {
// If j != -1 this means we previously encountered a deleted
cell.
// We can perform an optimization by swapping the entries in
cells
// i and j so that the next time we search for this key it will
be
// found faster. This is called lazy deletion/relocation.
if (j != -1) {
// Swap key-values pairs at indexes i and j.
keys[j] = keys[i];
values[j] = values[i];
keys[i] = TOMBSTONE;
values[i] = null;
return values[j];
} else {
return values[i];
}
}
// Element was not found in the hash-table :/
} else return null;
}
}
// Removes a key from the map and returns the value.
// NOTE: returns null if the value is null AND also returns
// null if the key does not exists.
public V remove(K key) {
if (key == null) throw new IllegalArgumentException("Null
key");
setupProbing(key);
final int offset = normalizeIndex(key.hashCode());
// Starting at the hash linearly probe until we find a spot
where
// our key is or we hit a null element in which case our element
does not exist
for (int i = offset, x = 1; ; i = normalizeIndex(offset +
probe(x++))) {
// Ignore deleted cells
if (keys[i] == TOMBSTONE) continue;
// Key was not found in hash-table.
if (keys[i] == null) return null;
// The key we want to remove is in the hash-table!
if (keys[i].equals(key)) {
keyCount--;
modificationCount++;
V oldValue = values[i];
keys[i] = TOMBSTONE;
values[i] = null;
return oldValue;
}
}
}
// Return a String view of this hash-table.
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("{");
for (int i = 0; i < capacity; i++)
if (keys[i] != null && keys[i] != TOMBSTONE)
sb.append(keys[i] + " => " + values[i] + ", ");
sb.append("}");
return sb.toString();
}
@Override
public Iterator<K> iterator() {
// Before the iteration begins record the number of
modifications
// done to the hash-table. This value should not change as we
iterate
// otherwise a concurrent modification has occurred :0
final int MODIFICATION_COUNT = modificationCount;
return new Iterator<K>() {
int index, keysLeft = keyCount;
@Override
public boolean hasNext() {
// The contents of the table have been altered
if (MODIFICATION_COUNT != modificationCount) throw new
ConcurrentModificationException();
return keysLeft != 0;
}
// Find the next element and return it
@Override
public K next() {
while (keys[index] == null || keys[index] == TOMBSTONE)
index++;
keysLeft--;
return keys[index++];
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
}