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();
}
};
}
}