diff --git a/DIRECTORY.md b/DIRECTORY.md index 1c1c010221a3..284c396b2796 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -857,12 +857,12 @@ * hashing * [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) + * [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) * [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) * [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) - * [HashMapCuckooHashingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/hashmap/HashMapCuckooHashingTest.java) * heaps * [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) diff --git a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapCuckooHashing.java b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapCuckooHashing.java index a67968d7e659..8bcf00730acb 100644 --- a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapCuckooHashing.java +++ b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapCuckooHashing.java @@ -3,25 +3,26 @@ import java.util.Objects; /** - * This class is an implementation of a hash table using Cuckoo Hashing It uses - * a dynamic array to lengthen the size of the hash table when load factor > .7 + * This class implements a hash table using Cuckoo Hashing. + * 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. * - * ... + * For more information on cuckoo hashing, refer to + * this Wikipedia page. */ public class HashMapCuckooHashing { - private int tableSize; // size of the hash table - private Integer[] buckets; // array representing the table - private final Integer emptySlot; - private int size; // number of elements in the hash table - - private int thresh; // threshold for infinite loop checking + private int tableSize; // Size of the hash table + private Integer[] buckets; // Array representing the hash table + private final Integer emptySlot; // Placeholder for deleted slots + private int size; // Number of elements in the hash table + private int thresh; // Threshold for detecting infinite loops during insertion /** - * Constructor initializes buckets array, hsize, and creates dummy object - * for emptySlot + * Constructs a HashMapCuckooHashing object with the specified initial table size. * - * @param tableSize the desired size of the hash map + * @param tableSize the initial size of the hash map */ public HashMapCuckooHashing(int tableSize) { this.buckets = new Integer[tableSize]; @@ -32,13 +33,11 @@ public HashMapCuckooHashing(int tableSize) { } /** - * The 2 Hash Functions takes a given key and finds an index based on its data, 2 distinctive - * ways to minimize collisions + * Computes the first hash index for a given key using the modulo operation. * - * @param key the desired key to be converted - * @return int an index corresponding to the key + * @param key the key for which the hash index is computed + * @return an integer index corresponding to the key */ - public int hashFunction1(int key) { int hash = key % tableSize; if (hash < 0) { @@ -47,6 +46,12 @@ public int hashFunction1(int key) { 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) { int hash = key / tableSize; hash %= tableSize; @@ -57,14 +62,14 @@ public int hashFunction2(int key) { } /** - * inserts the key into the hash map by wrapping it as an Integer object, then uses while loop - * to insert new key if desired place is empty, return. if already occupied, continue while loop - * over the new key that has just been pushed out. if while loop continues more than Thresh, - * rehash table to new size, then push again. + * Inserts a key into the hash table using cuckoo hashing. + * If the target bucket is occupied, it relocates the existing key and attempts to insert + * it into its alternate location. If the insertion process exceeds the threshold, + * 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) { Integer wrappedInt = key; Integer temp; @@ -77,7 +82,7 @@ public void insertKey2HashTable(int 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) { @@ -117,9 +122,7 @@ public void insertKey2HashTable(int key) { } /** - * creates new HashMapCuckooHashing object, then inserts each of the elements in the previous - * table to it with its new hash functions. then refers current array to new table. - * + * Rehashes the current table to a new size (double the current size) and reinserts existing keys. */ public void reHashTableIncreasesTableSize() { HashMapCuckooHashing newT = new HashMapCuckooHashing(tableSize * 2); @@ -134,15 +137,16 @@ public void reHashTableIncreasesTableSize() { } /** - * 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) { Integer wrappedInt = key; int hash = hashFunction1(key); if (isEmpty()) { - throw new IllegalArgumentException("Table is empty"); + throw new IllegalArgumentException("Table is empty, cannot delete."); } if (Objects.equals(buckets[hash], wrappedInt)) { @@ -157,11 +161,11 @@ public void deleteKeyFromHashTable(int key) { size--; 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() { for (int i = 0; i < tableSize; i++) { @@ -175,17 +179,18 @@ public void displayHashtable() { } /** - * 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 - * @return int the index where the key is located + * @param key the key to be found + * @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) { Integer wrappedInt = key; int hash = hashFunction1(key); if (isEmpty()) { - throw new IllegalArgumentException("Table is empty"); + throw new IllegalArgumentException("Table is empty; cannot find keys."); } if (Objects.equals(buckets[hash], wrappedInt)) { @@ -194,66 +199,70 @@ public int findKeyInTable(int key) { hash = hashFunction2(key); 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 { 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 - * @return int the index where the key is located + * @param key the key to be checked + * @return true if the key exists, false otherwise */ 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, - * automatically lengthens table to prevent further collisions + * Checks the load factor of the hash table. If the load factor exceeds 0.7, + * the table is resized to prevent further collisions. + * + * @return the current load factor of the hash table */ public double checkLoadFactor() { double factor = (double) size / tableSize; 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(); } 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() { - boolean response = true; for (int i = 0; i < tableSize; i++) { if (buckets[i] == null || Objects.equals(buckets[i], emptySlot)) { 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() { - boolean response = true; for (int i = 0; i < tableSize; i++) { if (buckets[i] != null) { - response = false; - break; + return false; } } - 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() { return size; } diff --git a/src/test/java/com/thealgorithms/datastructures/hashmap/HashMapCuckooHashingTest.java b/src/test/java/com/thealgorithms/datastructures/hashmap/HashMapCuckooHashingTest.java deleted file mode 100644 index 14bddeae1c91..000000000000 --- a/src/test/java/com/thealgorithms/datastructures/hashmap/HashMapCuckooHashingTest.java +++ /dev/null @@ -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)); - } -} diff --git a/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapCuckooHashingTest.java b/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapCuckooHashingTest.java new file mode 100644 index 000000000000..c2f80bfe3210 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapCuckooHashingTest.java @@ -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."); + } +}