From b8a502399e43da9d122e728041ceca3723786f27 Mon Sep 17 00:00:00 2001 From: Hardik Pawar Date: Thu, 24 Oct 2024 11:23:14 +0530 Subject: [PATCH 1/3] refactor: Enhance docs, add tests in `GenericHashMapUsingArray` --- .../hashing/GenericHashMapUsingArray.java | 116 +++++++++++++++--- .../hashing/GenericHashMapUsingArrayTest.java | 43 +++++++ 2 files changed, 139 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArray.java b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArray.java index 416cee99d028..5a05d8273f6b 100644 --- a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArray.java +++ b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArray.java @@ -2,23 +2,46 @@ import java.util.LinkedList; -// implementation of generic hashmaps using array of Linked Lists - +/** + * A generic implementation of a hash map using an array of linked lists for collision resolution. + * This class provides a way to store key-value pairs efficiently, allowing for average-case + * constant time complexity for insertion, deletion, and retrieval operations. + * + *

+ * The hash map uses separate chaining for collision resolution. Each bucket in the hash map is a + * linked list that stores nodes containing key-value pairs. When a collision occurs (i.e., when + * two keys hash to the same index), the new key-value pair is simply added to the corresponding + * linked list. + *

+ * + *

+ * The hash map automatically resizes itself when the load factor exceeds 0.75. The load factor is + * defined as the ratio of the number of entries to the number of buckets. When resizing occurs, + * all existing entries are rehashed and inserted into the new buckets. + *

+ * + * @param the type of keys maintained by this hash map + * @param the type of mapped values + */ public class GenericHashMapUsingArray { - private int size; // n (total number of key-value pairs) - private LinkedList[] buckets; // N = buckets.length - private float lf = 0.75f; + private int size; // Total number of key-value pairs + private LinkedList[] buckets; // Array of linked lists (buckets) for storing entries + private final float loadFactorThreshold = 0.75f; // Load factor threshold for resizing + /** + * Constructs a new empty hash map with an initial capacity of 16. + */ public GenericHashMapUsingArray() { initBuckets(16); size = 0; } - // load factor = 0.75 means if we need to add 100 items and we have added - // 75, then adding 76th item it will double the size, copy all elements - // & then add 76th item. - + /** + * Initializes the buckets for the hash map with the specified number of buckets. + * + * @param n the number of buckets to initialize + */ private void initBuckets(int n) { buckets = new LinkedList[n]; for (int i = 0; i < buckets.length; i++) { @@ -26,43 +49,65 @@ private void initBuckets(int n) { } } + /** + * Associates the specified value with the specified key in this map. + * If the map previously contained a mapping for the key, the old value is replaced. + * + * @param key the key with which the specified value is to be associated + * @param value the value to be associated with the specified key + */ public void put(K key, V value) { int bucketIndex = hashFunction(key); LinkedList nodes = buckets[bucketIndex]; - for (Node node : nodes) { // if key present => update + // Update existing key's value if present + for (Node node : nodes) { if (node.key.equals(key)) { node.value = value; return; } } - // key is not present => insert + // Insert new key-value pair nodes.add(new Node(key, value)); size++; - if ((float) size / buckets.length > lf) { + // Check if rehashing is needed + if ((float) size / buckets.length > loadFactorThreshold) { reHash(); } } - // tells which bucket to go to + /** + * Returns the index of the bucket in which the key would be stored. + * + * @param key the key whose bucket index is to be computed + * @return the bucket index + */ private int hashFunction(K key) { return Math.floorMod(key.hashCode(), buckets.length); } + /** + * Rehashes the map by doubling the number of buckets and re-inserting all entries. + */ private void reHash() { System.out.println("Rehashing!"); - LinkedList[] old = buckets; - initBuckets(old.length * 2); + LinkedList[] oldBuckets = buckets; + initBuckets(oldBuckets.length * 2); this.size = 0; - for (LinkedList nodes : old) { + for (LinkedList nodes : oldBuckets) { for (Node node : nodes) { put(node.key, node.value); } } } + /** + * Removes the mapping for the specified key from this map if present. + * + * @param key the key whose mapping is to be removed from the map + */ public void remove(K key) { int bucketIndex = hashFunction(key); LinkedList nodes = buckets[bucketIndex]; @@ -74,14 +119,28 @@ public void remove(K key) { break; } } - nodes.remove(target); - size--; + + if (target != null) { + nodes.remove(target); + size--; + } } + /** + * Returns the number of key-value pairs in this map. + * + * @return the number of key-value pairs + */ public int size() { return this.size; } + /** + * Returns the value to which the specified key is mapped, or null if this map contains no mapping for the key. + * + * @param key the key whose associated value is to be returned + * @return the value associated with the specified key, or null if no mapping exists + */ public V get(K key) { int bucketIndex = hashFunction(key); LinkedList nodes = buckets[bucketIndex]; @@ -96,7 +155,6 @@ public V get(K key) { @Override public String toString() { StringBuilder builder = new StringBuilder(); - builder.append("{"); for (LinkedList nodes : buckets) { for (Node node : nodes) { @@ -106,19 +164,37 @@ public String toString() { builder.append(", "); } } + // Remove trailing comma and space + if (builder.length() > 1) { + builder.setLength(builder.length() - 2); + } builder.append("}"); return builder.toString(); } + /** + * Returns true if this map contains a mapping for the specified key. + * + * @param key the key whose presence in this map is to be tested + * @return true if this map contains a mapping for the specified key + */ public boolean containsKey(K key) { return get(key) != null; } + /** + * A private class representing a key-value pair (node) in the hash map. + */ public class Node { - K key; V value; + /** + * Constructs a new Node with the specified key and value. + * + * @param key the key of the key-value pair + * @param value the value of the key-value pair + */ public Node(K key, V value) { this.key = key; this.value = value; diff --git a/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayTest.java b/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayTest.java index 483e43bb5cb3..5d1733a3e97c 100644 --- a/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayTest.java +++ b/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayTest.java @@ -50,4 +50,47 @@ void testGenericHashmapWhichUsesArrayAndKeyIsIntegerValueIsString() { assertEquals("Washington DC", map.get(101)); assertTrue(map.containsKey(46)); } + + @Test + void testRemoveNonExistentKey() { + GenericHashMapUsingArray map = new GenericHashMapUsingArray<>(); + map.put("USA", "Washington DC"); + map.remove("Nepal"); // Attempting to remove a non-existent key + assertEquals(1, map.size()); // Size should remain the same + } + + @Test + void testRehashing() { + GenericHashMapUsingArray map = new GenericHashMapUsingArray<>(); + for (int i = 0; i < 20; i++) { + map.put("Key" + i, "Value" + i); + } + assertEquals(20, map.size()); // Ensure all items were added + assertEquals("Value5", map.get("Key5")); // Check retrieval after rehash + } + + @Test + void testUpdateValueForExistingKey() { + GenericHashMapUsingArray map = new GenericHashMapUsingArray<>(); + map.put("USA", "Washington DC"); + map.put("USA", "New Washington DC"); // Updating value for existing key + assertEquals("New Washington DC", map.get("USA")); + } + + @Test + void testToStringMethod() { + GenericHashMapUsingArray map = new GenericHashMapUsingArray<>(); + map.put("USA", "Washington DC"); + map.put("Nepal", "Kathmandu"); + String expected = "{USA : Washington DC, Nepal : Kathmandu}"; + assertEquals(expected, map.toString()); + } + + @Test + void testContainsKey() { + GenericHashMapUsingArray map = new GenericHashMapUsingArray<>(); + map.put("USA", "Washington DC"); + assertTrue(map.containsKey("USA")); + assertFalse(map.containsKey("Nepal")); + } } From 00b0301312457bf0c3563c8925cbb3250a79c247 Mon Sep 17 00:00:00 2001 From: Hardik Pawar Date: Thu, 24 Oct 2024 11:29:49 +0530 Subject: [PATCH 2/3] Fix --- .../hashmap/hashing/GenericHashMapUsingArray.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArray.java b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArray.java index 5a05d8273f6b..ed89dacd5ffa 100644 --- a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArray.java +++ b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArray.java @@ -27,7 +27,6 @@ public class GenericHashMapUsingArray { private int size; // Total number of key-value pairs private LinkedList[] buckets; // Array of linked lists (buckets) for storing entries - private final float loadFactorThreshold = 0.75f; // Load factor threshold for resizing /** * Constructs a new empty hash map with an initial capacity of 16. @@ -72,6 +71,8 @@ public void put(K key, V value) { size++; // Check if rehashing is needed + // Load factor threshold for resizing + float loadFactorThreshold = 0.75f; if ((float) size / buckets.length > loadFactorThreshold) { reHash(); } From 57a26e64ffa737ee8beb981943bd3001eaba16b7 Mon Sep 17 00:00:00 2001 From: Hardik Pawar Date: Tue, 29 Oct 2024 10:04:06 +0530 Subject: [PATCH 3/3] Fix --- .../datastructures/hashmap/hashing/GenericHashMapUsingArray.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArray.java b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArray.java index ed89dacd5ffa..3637e323f097 100644 --- a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArray.java +++ b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArray.java @@ -92,7 +92,6 @@ private int hashFunction(K key) { * Rehashes the map by doubling the number of buckets and re-inserting all entries. */ private void reHash() { - System.out.println("Rehashing!"); LinkedList[] oldBuckets = buckets; initBuckets(oldBuckets.length * 2); this.size = 0;