Add documentation

This commit is contained in:
Kumiho 2018-05-13 15:13:29 +02:00
parent 8535dc356a
commit b342b08533

View File

@ -45,7 +45,7 @@ public class HashTable {
* assume a bucket factor of 1.
*/
public HashTable(int k, String hashFunction, String collisionResolution) {
this.data = new Entry[k];
this.data = new Entry[k]; // Allocate array for storage
this.hashFunction = hashFunction.equals("mid_square") ? 2 : hashFunction.equals("folding") ? 1 : 0;
this.probingMode = collisionResolution.equals("quadratic_probing") ? 1 : 0;
this.valueCount = 0;
@ -72,7 +72,9 @@ public class HashTable {
String[] parts;
int insertionCount = 0;
// Read all lines
while ((line = br.readLine()) != null) {
// Create new entry from line parts separated by a semicolon
parts = line.split(";");
Entry entry = new Entry(parts[0], parts[1], parts[2]);
@ -80,8 +82,6 @@ public class HashTable {
insertionCount += 1;
}
System.out.println(insertionCount);
return insertionCount;
} catch (FileNotFoundException e) {
System.out.println("File not found");
@ -108,23 +108,28 @@ public class HashTable {
int address = this.getHashAddr(insertEntry);
int base = address;
int i = 1;
// Find a free or deleted slot to insert the entry
while(this.data[address] != null && !this.data[address].isDeleted()) {
// Don't insert if there is no more space (this shouldn't happen due to auto-rehashing) or if the key is already present
if(i > this.capacity || this.data[address].getKey().equals(insertEntry.getKey()))
return false;
address = this.getProbingAddr(base, i);
i += 1;
}
// If we are using a previously unused slot, increase the valueCount for accurate load calculation
if(this.data[address] == null)
this.valueCount += 1;
// Save the entry
this.data[address] = insertEntry;
// Trigger rehash if load factor > 0.75
if(((double) this.valueCount) / this.capacity > 0.75)
this.rehash();
System.out.println("Load: "+Double.toString(((double) this.valueCount) / this.capacity));
// System.out.println("Load: "+Double.toString(((double) this.valueCount) / this.capacity));
return true;
}
@ -143,6 +148,8 @@ public class HashTable {
int address = this.getHashAddr(deleteKey);
int base = address;
int i = 1;
// Find the address where deleteKey is stored
while(this.data[address] != null && !this.data[address].getKey().equals(deleteKey)) {
if(i > this.capacity)
return null;
@ -150,10 +157,12 @@ public class HashTable {
i += 1;
}
// If an address was found, mark the entry as deleted and return it
if(this.data[address] != null) {
this.data[address].markDeleted();
return this.data[address];
}
// If no address was found, the key is not in the table
else
return null;
}
@ -169,16 +178,20 @@ public class HashTable {
* null if the entry is not found in the Hash-Table
*/
public Entry find(String searchKey) {
// Get home address of the key
int address = this.getHashAddr(searchKey);
int base = address;
int i = 1;
// Try next probing addresses as long as the key we're looking for has not been found
// If a free (=null) field is found, the key cannot be part of the table
while(this.data[address] != null && !this.data[address].getKey().equals(searchKey)) {
if(i > this.capacity)
if(i > this.capacity) // Don't loop forever
return null;
address = this.getProbingAddr(base, i);
i += 1;
}
// If we found the data but it has been deleted, return null anyway
return this.data[address].isDeleted() ? null : this.data[address];
}
@ -192,11 +205,15 @@ public class HashTable {
*/
public ArrayList<String> getHashTable() {
ArrayList<String> dot = new ArrayList<String>();
// Add static output
dot.add("digraph {");
dot.add("splines=true;");
dot.add("nodesep=.01;");
dot.add("rankdir=LR;");
dot.add("node[fontsize=8,shape=record,height=.1;]");
// Generate definition for address boxes / labels
String addressdef = "[fontsize=12,label=\"";
for(int i = 0; i < this.capacity; i++) {
addressdef += "<f"+Integer.toString(i)+">"+Integer.toString(i)+(i < this.capacity - 1 ? "|" : "");
@ -204,27 +221,28 @@ public class HashTable {
addressdef += "\"];";
dot.add(addressdef);
// Generate value box definitions with insertion sequence
int j = 1;
for(int i = 0; i < this.capacity; i++) {
if(this.data[i] != null) {
String insertSequence = getInsertionSequence(this.data[i].getKey());
String line = "node"+Integer.toString(j)+"[label=\"{<l>" + this.data[i].getKey() + '|' + this.data[i].getData() + insertSequence + "}\"];";
dot.add(line);
System.out.println(line);
j += 1;
}
}
// Generate 'edge' definitions / key value pairings
j = 1;
for(int i = 0; i < this.capacity; i++) {
if(this.data[i] != null) {
String line = "ht:f"+Integer.toString(i)+"->node" + Integer.toString(j) + ":l;";
dot.add(line);
System.out.println(line);
j += 1;
}
}
// Finally, add a closing bracket
dot.add("}");
return dot;
}
@ -240,23 +258,23 @@ public class HashTable {
* increased to 1009, which is the closest primary number less than (101*10).
*/
private void rehash() {
System.out.println("REHASH");
// Increase capacity by factor 10
int newSize = this.capacity * 10;
// Find nearest lower prime to use as capacity
while(!isPrime(newSize)) {
newSize -= 1;
}
System.out.println(newSize);
// Save old HashTable
int oldSize = this.capacity;
Entry[] oldData = this.data;
// Create new HashTable
this.data = new Entry[newSize];
this.capacity = newSize;
// Loop over old table and insert all found values into the new table
for(int i = 0; i < oldSize; i++) {
if(oldData[i] != null && !oldData[i].isDeleted()) {
this.insert(oldData[i]);
@ -264,16 +282,31 @@ public class HashTable {
}
}
/**
* Creates a decimal representation of a string by joining the ASCII values of its first five characters
* @param value The string to calculate a decimal representation of
* @return Decimal representation of the string
*/
private long decimalRepresentation(String value) {
char[] key = value.toCharArray();
String str = Integer.toString((int) key[0]) + Integer.toString((int) key[1]) + Integer.toString((int) key[2]) + Integer.toString((int) key[3]) + Integer.toString((int) key[4]);
return Long.parseLong(str);
return Long.parseLong(str); // Result is usually too large to fit into an integer variable
}
/**
* Hashes the key of an Entry object using the appropriate hash function
* @param value The object to get the key hash of
* @return Hash value of the key
*/
private int getHashAddr(Entry value) {
return this.getHashAddr(value.getKey());
}
/**
* Hashes a key using the appropriate hash function
* @param key The key to hash
* @return Hash value of the key
*/
private int getHashAddr(String key) {
if(this.hashFunction == 0)
return this.divisionHash(key);
@ -285,22 +318,38 @@ public class HashTable {
return 0;
}
/**
* Calculates the hash of a string by taking its decimal representation modulo the HashTable capacity
* @param value The key to hash
* @return Hash value of the key between 0 and capacity
*/
private int divisionHash(String value) {
long decimal = decimalRepresentation(value);
return Math.toIntExact(decimal % this.capacity);
}
/**
* Calculates the mid_square hash of a string by taking a substring of the length of a Hash Table address out of the
* middle of the square of the decimal representation of the given value
* @param value The key to hash
* @return Hash value of the key between 0 and capacity
*/
private int midSquareHash(String value) {
// Length of a HashTable address is log10 of its capacity rounded up (maximum number of decimal digits needed to store an address)
int addrLength = (int) Math.ceil(Math.log10(this.capacity));
System.out.println("AddrLen: "+addrLength);
BigInteger dec = BigInteger.valueOf(decimalRepresentation(value));
BigInteger sqr = dec.pow(2);
BigInteger sqr = BigInteger.valueOf(decimalRepresentation(value)).pow(2);
String str = sqr.toString();
// Get addrLength digits from sqr starting at the 10th digit from the right
str = str.substring(str.length() - (9+addrLength), str.length() - 9);
System.out.println(str);
return Integer.parseInt(str) % this.capacity;
}
/**
* Calculates the folding hash of a string by splitting its decimal representation into equal parts of the length
* of a HashTable address, then summing those parts while reading right-to-left and left-to-right alternatingly.
* @param value The key to hash
* @return Hash value of the key between 0 and capacity
*/
private int foldingHash(String value) {
int addrLength = (int) Math.ceil(Math.log10(this.capacity));
String str = Long.toString(decimalRepresentation(value));
@ -314,32 +363,51 @@ public class HashTable {
boolean reverse = true;
String part;
// Walk through string in steps of addrLength, summing up parts
for(int i = 0; i < str.length(); i += addrLength) {
// Part is substring of length addrLenght starting from the right
part = str.substring(str.length() - (i + addrLength), str.length() - i);
// Parts in odd iterations are read right-to-left (i.e. reversed)
if(reverse)
part = new StringBuilder(part).reverse().toString();
sum += Integer.parseInt(part);
reverse = !reverse;
}
// Convert sum back to string to cut off excess leading digits, then take mod capacity
String res = Integer.toString(sum);
return Integer.parseInt(res.substring(res.length() - addrLength, res.length())) % this.capacity;
}
/**
* Get the i-th probing address for a given home address, using linear or quadratic probing when appropriate
* @param base The home address of the key to store
* @param i Try count, starting at 1 for first non-home address
* @return The i-th probing address
*/
private int getProbingAddr(int base, int i) {
if(this.probingMode == 0)
// Just add one for every try in linear mode
return (base + i) % this.capacity;
else {
// i-th try in quadratic mode is ([home address] - ceil((i/2)² * (-1)^i) mod capacity
int nextTry = ((int)(base - Math.ceil(Math.pow(((float) i)/2, 2))*Math.pow(-1, i))) % this.capacity;
return nextTry < 0 ? nextTry + this.capacity : nextTry;
return nextTry < 0 ? nextTry + this.capacity : nextTry; // Add capacity to result if negative
}
}
/**
* Reconstruct the insertion sequence of a value for dotfile exports
* @param searchKey The key to calculate insertion sequence for
* @return A string of the format "|[home addr], [first probing], ... " if the value is not at home address, empty string otherwise
*/
private String getInsertionSequence(String searchKey) {
int address = this.getHashAddr(searchKey);
int base = address;
int i = 1;
String sequence = "";
// Add probing addresses to output as long as actual storage address is not reached
while(this.data[address] != null && !this.data[address].getKey().equals(searchKey)) {
sequence += Integer.toString(address) + ", ";
address = this.getProbingAddr(base, i);
@ -349,8 +417,14 @@ public class HashTable {
return sequence.length() > 2 ? "|" + sequence.substring(0, sequence.length() - 2) : "";
}
/**
* Helper function to check if a number is prime
* @param p Number to test
* @return True if the number is prime, false otherwise
*/
private boolean isPrime(int p) {
int i = 2;
// Check possible factors from 2 to sqrt(p) inclusive
while(i <= Math.sqrt(p)) {
if(p % i == 0)
return false;