Enhance docs, add tests in HashMapCuckooHashing (#5975)

This commit is contained in:
Hardik Pawar
2024-10-26 21:47:58 +05:30
committed by GitHub
parent 84cd883e95
commit cd40dfbb41
4 changed files with 204 additions and 149 deletions

View File

@ -857,12 +857,12 @@
* hashing * hashing
* [GenericHashMapUsingArrayListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayListTest.java) * [GenericHashMapUsingArrayListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayListTest.java)
* [GenericHashMapUsingArrayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayTest.java) * [GenericHashMapUsingArrayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayTest.java)
* [HashMapCuckooHashingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapCuckooHashingTest.java)
* [HashMapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapTest.java) * [HashMapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapTest.java)
* [IntersectionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/IntersectionTest.java) * [IntersectionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/IntersectionTest.java)
* [LinearProbingHashMapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/LinearProbingHashMapTest.java) * [LinearProbingHashMapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/LinearProbingHashMapTest.java)
* [MajorityElementTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/MajorityElementTest.java) * [MajorityElementTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/MajorityElementTest.java)
* [MapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/MapTest.java) * [MapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/MapTest.java)
* [HashMapCuckooHashingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/hashmap/HashMapCuckooHashingTest.java)
* heaps * heaps
* [FibonacciHeapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/FibonacciHeapTest.java) * [FibonacciHeapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/FibonacciHeapTest.java)
* [GenericHeapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/GenericHeapTest.java) * [GenericHeapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/GenericHeapTest.java)

View File

@ -3,25 +3,26 @@ package com.thealgorithms.datastructures.hashmap.hashing;
import java.util.Objects; import java.util.Objects;
/** /**
* This class is an implementation of a hash table using Cuckoo Hashing It uses * This class implements a hash table using Cuckoo Hashing.
* a dynamic array to lengthen the size of the hash table when load factor > .7 * Cuckoo hashing is a type of open-addressing hash table that resolves collisions
* by relocating existing keys. It utilizes two hash functions to minimize collisions
* and automatically resizes the table when the load factor exceeds 0.7.
* *
* <a href="https://en.wikipedia.org/wiki/Cuckoo_hashing">...</a> * For more information on cuckoo hashing, refer to
* <a href="https://en.wikipedia.org/wiki/Cuckoo_hashing">this Wikipedia page</a>.
*/ */
public class HashMapCuckooHashing { public class HashMapCuckooHashing {
private int tableSize; // size of the hash table private int tableSize; // Size of the hash table
private Integer[] buckets; // array representing the table private Integer[] buckets; // Array representing the hash table
private final Integer emptySlot; private final Integer emptySlot; // Placeholder for deleted slots
private int size; // number of elements in the hash table private int size; // Number of elements in the hash table
private int thresh; // Threshold for detecting infinite loops during insertion
private int thresh; // threshold for infinite loop checking
/** /**
* Constructor initializes buckets array, hsize, and creates dummy object * Constructs a HashMapCuckooHashing object with the specified initial table size.
* for emptySlot
* *
* @param tableSize the desired size of the hash map * @param tableSize the initial size of the hash map
*/ */
public HashMapCuckooHashing(int tableSize) { public HashMapCuckooHashing(int tableSize) {
this.buckets = new Integer[tableSize]; this.buckets = new Integer[tableSize];
@ -32,13 +33,11 @@ public class HashMapCuckooHashing {
} }
/** /**
* The 2 Hash Functions takes a given key and finds an index based on its data, 2 distinctive * Computes the first hash index for a given key using the modulo operation.
* ways to minimize collisions
* *
* @param key the desired key to be converted * @param key the key for which the hash index is computed
* @return int an index corresponding to the key * @return an integer index corresponding to the key
*/ */
public int hashFunction1(int key) { public int hashFunction1(int key) {
int hash = key % tableSize; int hash = key % tableSize;
if (hash < 0) { if (hash < 0) {
@ -47,6 +46,12 @@ public class HashMapCuckooHashing {
return hash; return hash;
} }
/**
* Computes the second hash index for a given key using integer division.
*
* @param key the key for which the hash index is computed
* @return an integer index corresponding to the key
*/
public int hashFunction2(int key) { public int hashFunction2(int key) {
int hash = key / tableSize; int hash = key / tableSize;
hash %= tableSize; hash %= tableSize;
@ -57,14 +62,14 @@ public class HashMapCuckooHashing {
} }
/** /**
* inserts the key into the hash map by wrapping it as an Integer object, then uses while loop * Inserts a key into the hash table using cuckoo hashing.
* to insert new key if desired place is empty, return. if already occupied, continue while loop * If the target bucket is occupied, it relocates the existing key and attempts to insert
* over the new key that has just been pushed out. if while loop continues more than Thresh, * it into its alternate location. If the insertion process exceeds the threshold,
* rehash table to new size, then push again. * the table is resized.
* *
* @param key the desired key to be inserted in the hash map * @param key the key to be inserted into the hash table
* @throws IllegalArgumentException if the key already exists in the table
*/ */
public void insertKey2HashTable(int key) { public void insertKey2HashTable(int key) {
Integer wrappedInt = key; Integer wrappedInt = key;
Integer temp; Integer temp;
@ -77,7 +82,7 @@ public class HashMapCuckooHashing {
} }
if (checkTableContainsKey(key)) { if (checkTableContainsKey(key)) {
throw new IllegalArgumentException("Key already inside, no duplicates allowed"); throw new IllegalArgumentException("Key already exists; duplicates are not allowed.");
} }
while (loopCounter <= thresh) { while (loopCounter <= thresh) {
@ -117,9 +122,7 @@ public class HashMapCuckooHashing {
} }
/** /**
* creates new HashMapCuckooHashing object, then inserts each of the elements in the previous * Rehashes the current table to a new size (double the current size) and reinserts existing keys.
* table to it with its new hash functions. then refers current array to new table.
*
*/ */
public void reHashTableIncreasesTableSize() { public void reHashTableIncreasesTableSize() {
HashMapCuckooHashing newT = new HashMapCuckooHashing(tableSize * 2); HashMapCuckooHashing newT = new HashMapCuckooHashing(tableSize * 2);
@ -134,15 +137,16 @@ public class HashMapCuckooHashing {
} }
/** /**
* deletes a key from the hash map and adds an available placeholder * Deletes a key from the hash table, marking its position as available.
* *
* @param key the desired key to be deleted * @param key the key to be deleted from the hash table
* @throws IllegalArgumentException if the table is empty or if the key is not found
*/ */
public void deleteKeyFromHashTable(int key) { public void deleteKeyFromHashTable(int key) {
Integer wrappedInt = key; Integer wrappedInt = key;
int hash = hashFunction1(key); int hash = hashFunction1(key);
if (isEmpty()) { if (isEmpty()) {
throw new IllegalArgumentException("Table is empty"); throw new IllegalArgumentException("Table is empty, cannot delete.");
} }
if (Objects.equals(buckets[hash], wrappedInt)) { if (Objects.equals(buckets[hash], wrappedInt)) {
@ -157,11 +161,11 @@ public class HashMapCuckooHashing {
size--; size--;
return; return;
} }
throw new IllegalArgumentException("Key " + key + " already inside, no duplicates allowed"); throw new IllegalArgumentException("Key " + key + " not found in the table.");
} }
/** /**
* Displays the hash table line by line * Displays the hash table contents, bucket by bucket.
*/ */
public void displayHashtable() { public void displayHashtable() {
for (int i = 0; i < tableSize; i++) { for (int i = 0; i < tableSize; i++) {
@ -175,17 +179,18 @@ public class HashMapCuckooHashing {
} }
/** /**
* Finds the index of location based on an inputted key * Finds the index of a given key in the hash table.
* *
* @param key the desired key to be found * @param key the key to be found
* @return int the index where the key is located * @return the index where the key is located
* @throws IllegalArgumentException if the table is empty or the key is not found
*/ */
public int findKeyInTable(int key) { public int findKeyInTable(int key) {
Integer wrappedInt = key; Integer wrappedInt = key;
int hash = hashFunction1(key); int hash = hashFunction1(key);
if (isEmpty()) { if (isEmpty()) {
throw new IllegalArgumentException("Table is empty"); throw new IllegalArgumentException("Table is empty; cannot find keys.");
} }
if (Objects.equals(buckets[hash], wrappedInt)) { if (Objects.equals(buckets[hash], wrappedInt)) {
@ -194,66 +199,70 @@ public class HashMapCuckooHashing {
hash = hashFunction2(key); hash = hashFunction2(key);
if (!Objects.equals(buckets[hash], wrappedInt)) { if (!Objects.equals(buckets[hash], wrappedInt)) {
throw new IllegalArgumentException("Key " + key + " not found in table"); throw new IllegalArgumentException("Key " + key + " not found in the table.");
} else { } else {
return hash; return hash;
} }
} }
/** /**
* checks if key is inside without any output other than returned boolean. * Checks if the given key is present in the hash table.
* *
* @param key the desired key to be found * @param key the key to be checked
* @return int the index where the key is located * @return true if the key exists, false otherwise
*/ */
public boolean checkTableContainsKey(int key) { public boolean checkTableContainsKey(int key) {
return ((buckets[hashFunction1(key)] != null && buckets[hashFunction1(key)].equals(key)) || (buckets[hashFunction2(key)] != null && buckets[hashFunction2(key)] == key)); return ((buckets[hashFunction1(key)] != null && buckets[hashFunction1(key)].equals(key)) || (buckets[hashFunction2(key)] != null && buckets[hashFunction2(key)].equals(key)));
} }
/** /**
* Checks the load factor of the hash table if greater than .7, * Checks the load factor of the hash table. If the load factor exceeds 0.7,
* automatically lengthens table to prevent further collisions * the table is resized to prevent further collisions.
*
* @return the current load factor of the hash table
*/ */
public double checkLoadFactor() { public double checkLoadFactor() {
double factor = (double) size / tableSize; double factor = (double) size / tableSize;
if (factor > .7) { if (factor > .7) {
System.out.printf("Load factor is %.2f , rehashing table%n", factor); System.out.printf("Load factor is %.2f, rehashing table.%n", factor);
reHashTableIncreasesTableSize(); reHashTableIncreasesTableSize();
} }
return factor; return factor;
} }
/** /**
* isFull returns true if the hash map is full and false if not full * Checks if the hash map is full.
* *
* @return boolean is Empty * @return true if the hash map is full, false otherwise
*/ */
public boolean isFull() { public boolean isFull() {
boolean response = true;
for (int i = 0; i < tableSize; i++) { for (int i = 0; i < tableSize; i++) {
if (buckets[i] == null || Objects.equals(buckets[i], emptySlot)) { if (buckets[i] == null || Objects.equals(buckets[i], emptySlot)) {
return false; return false;
} }
} }
return response; return true;
} }
/** /**
* isEmpty returns true if the hash map is empty and false if not empty * Checks if the hash map is empty.
* *
* @return boolean is Empty * @return true if the hash map is empty, false otherwise
*/ */
public boolean isEmpty() { public boolean isEmpty() {
boolean response = true;
for (int i = 0; i < tableSize; i++) { for (int i = 0; i < tableSize; i++) {
if (buckets[i] != null) { if (buckets[i] != null) {
response = false; return false;
break;
} }
} }
return response; return true;
} }
/**
* Returns the current number of keys in the hash table.
*
* @return the number of keys present in the hash table
*/
public int getNumberOfKeysInTable() { public int getNumberOfKeysInTable() {
return size; return size;
} }

View File

@ -1,94 +0,0 @@
package com.thealgorithms.datastructures.hashmap;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import com.thealgorithms.datastructures.hashmap.hashing.HashMapCuckooHashing;
import org.junit.jupiter.api.Test;
class HashMapCuckooHashingTest {
@Test
void insertKey() {
HashMapCuckooHashing hashTable = new HashMapCuckooHashing(10);
assertEquals(0, hashTable.getNumberOfKeysInTable());
hashTable.insertKey2HashTable(3);
assertEquals(1, hashTable.getNumberOfKeysInTable());
}
@Test
void getKeyIndex() {
HashMapCuckooHashing hashTable = new HashMapCuckooHashing(10);
hashTable.insertKey2HashTable(8);
hashTable.insertKey2HashTable(4);
assertNotEquals(-1, hashTable.findKeyInTable(8));
}
@Test
void containsKey() {
HashMapCuckooHashing hashTable = new HashMapCuckooHashing(10);
hashTable.insertKey2HashTable(8);
boolean contains = hashTable.checkTableContainsKey(8);
assertTrue(contains);
}
@Test
void removeKey() {
HashMapCuckooHashing hashTable = new HashMapCuckooHashing(10);
hashTable.insertKey2HashTable(3);
int initialSize = hashTable.getNumberOfKeysInTable();
hashTable.deleteKeyFromHashTable(3);
assertEquals(initialSize - 1, hashTable.getNumberOfKeysInTable());
}
@Test
void removeNone() {
HashMapCuckooHashing hashTable = new HashMapCuckooHashing(10);
try {
hashTable.deleteKeyFromHashTable(3);
} catch (Exception e) {
assertTrue(true);
return;
}
fail();
}
@Test
void reHashTableIncreasesTableSize() {
HashMapCuckooHashing hashTable = new HashMapCuckooHashing(10);
int initialSize = hashTable.getNumberOfKeysInTable();
hashTable.reHashTableIncreasesTableSize();
assertEquals(initialSize * 2, hashTable.getNumberOfKeysInTable());
}
@Test
void hashFunctionsAreDifferent() {
HashMapCuckooHashing hashTable = new HashMapCuckooHashing(10);
hashTable.insertKey2HashTable(33);
assertNotEquals(hashTable.hashFunction1(3), hashTable.hashFunction2(3));
}
@Test
void avoidInfiniteLoops() {
HashMapCuckooHashing hashTable = new HashMapCuckooHashing(10);
hashTable.insertKey2HashTable(0);
hashTable.insertKey2HashTable(10);
hashTable.insertKey2HashTable(100);
assertTrue(hashTable.checkTableContainsKey(0));
assertTrue(hashTable.checkTableContainsKey(10));
assertTrue(hashTable.checkTableContainsKey(100));
}
}

View File

@ -0,0 +1,140 @@
package com.thealgorithms.datastructures.hashmap.hashing;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
class HashMapCuckooHashingTest {
@Test
void insertKey() {
HashMapCuckooHashing hashTable = new HashMapCuckooHashing(10);
assertEquals(0, hashTable.getNumberOfKeysInTable());
hashTable.insertKey2HashTable(3);
assertEquals(1, hashTable.getNumberOfKeysInTable());
}
@Test
void getKeyIndex() {
HashMapCuckooHashing hashTable = new HashMapCuckooHashing(10);
hashTable.insertKey2HashTable(8);
hashTable.insertKey2HashTable(4);
assertNotEquals(-1, hashTable.findKeyInTable(8));
}
@Test
void containsKey() {
HashMapCuckooHashing hashTable = new HashMapCuckooHashing(10);
hashTable.insertKey2HashTable(8);
boolean contains = hashTable.checkTableContainsKey(8);
assertTrue(contains);
}
@Test
void removeKey() {
HashMapCuckooHashing hashTable = new HashMapCuckooHashing(10);
hashTable.insertKey2HashTable(3);
int initialSize = hashTable.getNumberOfKeysInTable();
hashTable.deleteKeyFromHashTable(3);
assertEquals(initialSize - 1, hashTable.getNumberOfKeysInTable());
}
@Test
void removeNone() {
HashMapCuckooHashing hashTable = new HashMapCuckooHashing(10);
try {
hashTable.deleteKeyFromHashTable(3);
} catch (Exception e) {
assertTrue(true);
return;
}
Assertions.fail("Expected exception when trying to delete a non-existing key.");
}
@Test
void reHashTableIncreasesTableSize() {
HashMapCuckooHashing hashTable = new HashMapCuckooHashing(10);
hashTable.insertKey2HashTable(1);
hashTable.insertKey2HashTable(2);
hashTable.insertKey2HashTable(3);
hashTable.insertKey2HashTable(4);
hashTable.insertKey2HashTable(5);
hashTable.insertKey2HashTable(6);
hashTable.insertKey2HashTable(7);
hashTable.insertKey2HashTable(8);
hashTable.insertKey2HashTable(9);
hashTable.insertKey2HashTable(10); // This should trigger rehashing
assertEquals(10, hashTable.getNumberOfKeysInTable());
}
@Test
void hashFunctionsAreDifferent() {
HashMapCuckooHashing hashTable = new HashMapCuckooHashing(10);
hashTable.insertKey2HashTable(33);
assertNotEquals(hashTable.hashFunction1(3), hashTable.hashFunction2(3), "Hash functions should produce different indices.");
}
@Test
void avoidInfiniteLoops() {
HashMapCuckooHashing hashTable = new HashMapCuckooHashing(10);
hashTable.insertKey2HashTable(0);
hashTable.insertKey2HashTable(10);
hashTable.insertKey2HashTable(100);
assertTrue(hashTable.checkTableContainsKey(0));
assertTrue(hashTable.checkTableContainsKey(10));
assertTrue(hashTable.checkTableContainsKey(100));
}
@Test
void testLoadFactor() {
HashMapCuckooHashing hashTable = new HashMapCuckooHashing(10);
for (int i = 1; i <= 8; i++) { // Insert 8 keys to test load factor
hashTable.insertKey2HashTable(i);
}
assertEquals(8, hashTable.getNumberOfKeysInTable());
// Check that rehashing occurs when a 9th key is added
hashTable.insertKey2HashTable(9);
assertTrue(hashTable.getNumberOfKeysInTable() > 8, "Load factor exceeded, table should have been resized.");
}
@Test
void testDeleteNonExistentKey() {
HashMapCuckooHashing hashTable = new HashMapCuckooHashing(10);
hashTable.insertKey2HashTable(1);
hashTable.insertKey2HashTable(2);
Exception exception = null;
try {
hashTable.deleteKeyFromHashTable(3); // Try deleting a non-existent key
} catch (IllegalArgumentException e) {
exception = e; // Capture the exception
}
assertNotNull(exception, "Expected an IllegalArgumentException when deleting a non-existent key.");
}
@Test
void testInsertDuplicateKey() {
HashMapCuckooHashing hashTable = new HashMapCuckooHashing(10);
hashTable.insertKey2HashTable(1);
Exception exception = null;
try {
hashTable.insertKey2HashTable(1); // Attempt to insert duplicate key
} catch (IllegalArgumentException e) {
exception = e; // Capture the exception
}
assertNotNull(exception, "Expected an IllegalArgumentException for duplicate key insertion.");
}
}