From 6324d489257f885478387aa1103c6a7e492a93b1 Mon Sep 17 00:00:00 2001 From: alxkm Date: Wed, 28 Aug 2024 22:05:20 +0200 Subject: [PATCH 1/3] refactor: HashMap --- .../hashmap/hashing/HashMap.java | 308 +++++++++++++----- .../datastructures/hashmap/hashing/Main.java | 52 --- .../hashmap/hashing/HashMapTest.java | 153 +++++++++ 3 files changed, 372 insertions(+), 141 deletions(-) delete mode 100644 src/main/java/com/thealgorithms/datastructures/hashmap/hashing/Main.java create mode 100644 src/test/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapTest.java diff --git a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/HashMap.java b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/HashMap.java index e0b394b12bf6..daf34ffbd4a6 100644 --- a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/HashMap.java +++ b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/HashMap.java @@ -1,146 +1,276 @@ package com.thealgorithms.datastructures.hashmap.hashing; -public class HashMap { +/** + * A generic HashMap implementation that uses separate chaining with linked lists + * to handle collisions. The class supports basic operations such as insert, delete, + * and search, as well as displaying the contents of the hash map. + * + * @param the type of keys maintained by this map + * @param the type of mapped values + */ +public class HashMap { + private final int hashSize; + private final LinkedList[] buckets; - private final int hsize; - private final LinkedList[] buckets; - - public HashMap(int hsize) { - buckets = new LinkedList[hsize]; - for (int i = 0; i < hsize; i++) { - buckets[i] = new LinkedList(); - // Java requires explicit initialization of each object + /** + * Constructs a HashMap with the specified hash size. + * + * @param hashSize the number of buckets in the hash map + */ + @SuppressWarnings("unchecked") + public HashMap(int hashSize) { + this.hashSize = hashSize; + // Safe to suppress warning because we are creating an array of generic type + this.buckets = new LinkedList[hashSize]; + for (int i = 0; i < hashSize; i++) { + buckets[i] = new LinkedList<>(); } - this.hsize = hsize; } - public int hashing(int key) { - int hash = key % hsize; - if (hash < 0) { - hash += hsize; + /** + * Computes the hash code for the specified key. + * Null keys are hashed to bucket 0. + * + * @param key the key for which the hash code is to be computed + * @return the hash code corresponding to the key + */ + private int computeHash(K key) { + if (key == null) { + return 0; // Use a special bucket (e.g., bucket 0) for null keys } - return hash; + int hash = key.hashCode() % hashSize; + return hash < 0 ? hash + hashSize : hash; } - public void insertHash(int key) { - int hash = hashing(key); - buckets[hash].insert(key); + /** + * Inserts the specified key-value pair into the hash map. + * If the key already exists, the value is updated. + * + * @param key the key to be inserted + * @param value the value to be associated with the key + */ + public void insert(K key, V value) { + int hash = computeHash(key); + buckets[hash].insert(key, value); } - public void deleteHash(int key) { - int hash = hashing(key); - + /** + * Deletes the key-value pair associated with the specified key from the hash map. + * + * @param key the key whose key-value pair is to be deleted + */ + public void delete(K key) { + int hash = computeHash(key); buckets[hash].delete(key); } - public void displayHashtable() { - for (int i = 0; i < hsize; i++) { - System.out.printf("Bucket %d :", i); - System.out.println(buckets[i].display()); - } + /** + * Searches for the value associated with the specified key in the hash map. + * + * @param key the key whose associated value is to be returned + * @return the value associated with the specified key, or null if the key does not exist + */ + public V search(K key) { + int hash = computeHash(key); + Node node = buckets[hash].findKey(key); + return node != null ? node.getValue() : null; } - public static class LinkedList { - - private Node first; - - public LinkedList() { - first = null; + /** + * Displays the contents of the hash map, showing each bucket and its key-value pairs. + */ + public void display() { + for (int i = 0; i < hashSize; i++) { + System.out.printf("Bucket %d: %s%n", i, buckets[i].display()); } + } - public void insert(int key) { - if (isEmpty()) { - first = new Node(key); - return; - } + /** + * A nested static class that represents a linked list used for separate chaining in the hash map. + * + * @param the type of keys maintained by this linked list + * @param the type of mapped values + */ + public static class LinkedList { + private Node head; - Node temp = findEnd(first); - temp.setNext(new Node(key)); + /** + * Inserts the specified key-value pair into the linked list. + * If the linked list is empty, the pair becomes the head. + * Otherwise, the pair is added to the end of the list. + * + * @param key the key to be inserted + * @param value the value to be associated with the key + */ + public void insert(K key, V value) { + Node existingNode = findKey(key); + if (existingNode != null) { + existingNode.setValue(value); // Update the value, even if it's null + } else { + if (isEmpty()) { + head = new Node<>(key, value); + } else { + Node temp = findEnd(head); + temp.setNext(new Node<>(key, value)); + } + } } - private Node findEnd(Node n) { - while (n.getNext() != null) { - n = n.getNext(); + /** + * Finds the last node in the linked list. + * + * @param node the starting node + * @return the last node in the linked list + */ + private Node findEnd(Node node) { + while (node.getNext() != null) { + node = node.getNext(); } - return n; + return node; } - public Node findKey(int key) { - if (!isEmpty()) { - Node temp = first; - if (temp.getKey() == key) { + /** + * Finds the node associated with the specified key in the linked list. + * + * @param key the key to search for + * @return the node associated with the specified key, or null if not found + */ + public Node findKey(K key) { + Node temp = head; + while (temp != null) { + if ((key == null && temp.getKey() == null) || (temp.getKey() != null && temp.getKey().equals(key))) { return temp; } - - while ((temp = temp.getNext()) != null) { - if (temp.getKey() == key) { - return temp; - } - } + temp = temp.getNext(); } return null; } - public void delete(int key) { - if (!isEmpty()) { - if (first.getKey() == key) { - Node next = first.next; - first.next = null; // help GC - first = next; - } else { - delete(first, key); - } + /** + * Deletes the node associated with the specified key from the linked list. + * Handles the case where the key could be null. + * + * @param key the key whose associated node is to be deleted + */ + public void delete(K key) { + if (isEmpty()) { + return; } - } - private void delete(Node n, int key) { - if (n.getNext().getKey() == key) { - if (n.getNext().getNext() == null) { - n.setNext(null); - } else { - n.setNext(n.getNext().getNext()); + // Handle the case where the head node has the key to delete + if ((key == null && head.getKey() == null) || (head.getKey() != null && head.getKey().equals(key))) { + head = head.getNext(); + return; + } + + // Traverse the list to find and delete the node + Node current = head; + while (current.getNext() != null) { + if ((key == null && current.getNext().getKey() == null) || + (current.getNext().getKey() != null && current.getNext().getKey().equals(key))) { + current.setNext(current.getNext().getNext()); + return; } - } else { - delete(n.getNext(), key); + current = current.getNext(); } } + /** + * Displays the contents of the linked list as a string. + * + * @return a string representation of the linked list + */ public String display() { - return display(first); + return display(head); } - private String display(Node n) { - if (n == null) { - return "null"; - } else { - return n.getKey() + "->" + display(n.getNext()); + /** + * Constructs a string representation of the linked list non-recursively. + * + * @param node the starting node + * @return a string representation of the linked list starting from the given node + */ + private String display(Node node) { + StringBuilder sb = new StringBuilder(); + while (node != null) { + sb.append(node.getKey()).append("=").append(node.getValue()); + node = node.getNext(); + if (node != null) { + sb.append(" -> "); + } } + return sb.toString().isEmpty() ? "null" : sb.toString(); } + /** + * Checks if the linked list is empty. + * + * @return true if the linked list is empty, false otherwise + */ public boolean isEmpty() { - return first == null; + return head == null; } } - public static class Node { + /** + * A nested static class representing a node in the linked list. + * + * @param the type of key maintained by this node + * @param the type of value maintained by this node + */ + public static class Node { + private final K key; + private V value; + private Node next; - private Node next; - private final int key; - - public Node(int key) { - next = null; + /** + * Constructs a Node with the specified key and value. + * + * @param key the key associated with this node + * @param value the value associated with this node + */ + public Node(K key, V value) { this.key = key; + this.value = value; } - public Node getNext() { - return next; + /** + * Gets the key associated with this node. + * + * @return the key associated with this node + */ + public K getKey() { + return key; } - public int getKey() { - return key; + /** + * Gets the value associated with this node. + * + * @return the value associated with this node + */ + public V getValue() { + return value; + } + + public void setValue(V value) { // This method allows updating the value + this.value = value; + } + + /** + * Gets the next node in the linked list. + * + * @return the next node in the linked list + */ + public Node getNext() { + return next; } - public void setNext(Node next) { + /** + * Sets the next node in the linked list. + * + * @param next the next node to be linked + */ + public void setNext(Node next) { this.next = next; } } diff --git a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/Main.java b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/Main.java deleted file mode 100644 index 4d9b33b115c7..000000000000 --- a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/Main.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.thealgorithms.datastructures.hashmap.hashing; - -import java.util.Scanner; - -public final class Main { - private Main() { - } - - public static void main(String[] args) { - int choice; - int key; - - HashMap h = new HashMap(7); - Scanner scan = new Scanner(System.in); - - while (true) { - System.out.println("Enter your Choice :"); - System.out.println("1. Add Key"); - System.out.println("2. Delete Key"); - System.out.println("3. Print Table"); - System.out.println("4. Exit"); - - choice = scan.nextInt(); - - switch (choice) { - case 1: - System.out.println("Enter the Key: "); - key = scan.nextInt(); - h.insertHash(key); - break; - - case 2: - System.out.println("Enter the Key delete: "); - key = scan.nextInt(); - h.deleteHash(key); - break; - - case 3: - System.out.println("Print table"); - h.displayHashtable(); - break; - - case 4: - scan.close(); - return; - - default: - throw new IllegalArgumentException("Unexpected value: " + choice); - } - } - } -} diff --git a/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapTest.java b/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapTest.java new file mode 100644 index 000000000000..cc8ca8b7f262 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapTest.java @@ -0,0 +1,153 @@ +package com.thealgorithms.datastructures.hashmap.hashing; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.junit.jupiter.api.Test; + +public class HashMapTest { + + @Test + public void testInsertAndSearch() { + HashMap hashMap = new HashMap<>(10); + hashMap.insert(15, "Value15"); + hashMap.insert(25, "Value25"); + hashMap.insert(35, "Value35"); + + assertEquals("Value15", hashMap.search(15)); + assertEquals("Value25", hashMap.search(25)); + assertEquals("Value35", hashMap.search(35)); + assertNull(hashMap.search(45)); + } + + @Test + public void testDelete() { + HashMap hashMap = new HashMap<>(10); + hashMap.insert(15, "Value15"); + hashMap.insert(25, "Value25"); + hashMap.insert(35, "Value35"); + + assertEquals("Value25", hashMap.search(25)); + hashMap.delete(25); + assertNull(hashMap.search(25)); + } + + @Test + public void testDisplay() { + HashMap hashMap = new HashMap<>(5); + hashMap.insert(15, "Value15"); + hashMap.insert(25, "Value25"); + hashMap.insert(35, "Value35"); + + // Expected output: + // Bucket 0: null + // Bucket 1: 15=Value15 -> 25=Value25 -> 35=Value35 -> null + // Bucket 2: null + // Bucket 3: null + // Bucket 4: null + hashMap.display(); + } + + @Test + public void testInsertNullKey() { + HashMap hashMap = new HashMap<>(10); + hashMap.insert(null, "NullValue"); + assertEquals("NullValue", hashMap.search(null)); + } + + @Test + public void testInsertNullValue() { + HashMap hashMap = new HashMap<>(10); + hashMap.insert(15, null); + assertNull(hashMap.search(15)); + } + + @Test + public void testUpdateExistingKey() { + HashMap hashMap = new HashMap<>(10); + hashMap.insert(15, "Value15"); + hashMap.insert(15, "UpdatedValue15"); + + assertEquals("UpdatedValue15", hashMap.search(15)); + } + + @Test + public void testHandleCollisions() { + HashMap hashMap = new HashMap<>(3); + // These keys should collide if the hash function is modulo 3 + hashMap.insert(1, "Value1"); + hashMap.insert(4, "Value4"); + hashMap.insert(7, "Value7"); + + assertEquals("Value1", hashMap.search(1)); + assertEquals("Value4", hashMap.search(4)); + assertEquals("Value7", hashMap.search(7)); + } + + @Test + public void testSearchInEmptyHashMap() { + HashMap hashMap = new HashMap<>(10); + assertNull(hashMap.search(10)); + } + + @Test + public void testDeleteNonExistentKey() { + HashMap hashMap = new HashMap<>(10); + hashMap.insert(15, "Value15"); + hashMap.delete(25); // Deleting a key that doesn't exist + + assertEquals("Value15", hashMap.search(15)); + assertNull(hashMap.search(25)); + } + + @Test + public void testInsertLargeNumberOfElements() { + HashMap hashMap = new HashMap<>(10); + for (int i = 0; i < 100; i++) { + hashMap.insert(i, "Value" + i); + } + + for (int i = 0; i < 100; i++) { + assertEquals("Value" + i, hashMap.search(i)); + } + } + + @Test + public void testDeleteHeadOfBucket() { + HashMap hashMap = new HashMap<>(3); + hashMap.insert(1, "Value1"); + hashMap.insert(4, "Value4"); + hashMap.insert(7, "Value7"); + + hashMap.delete(1); // Deleting the head of the bucket + assertNull(hashMap.search(1)); + assertEquals("Value4", hashMap.search(4)); + assertEquals("Value7", hashMap.search(7)); + } + + @Test + public void testDeleteTailOfBucket() { + HashMap hashMap = new HashMap<>(3); + hashMap.insert(1, "Value1"); + hashMap.insert(4, "Value4"); + hashMap.insert(7, "Value7"); + + hashMap.delete(7); // Deleting the tail of the bucket + assertNull(hashMap.search(7)); + assertEquals("Value1", hashMap.search(1)); + assertEquals("Value4", hashMap.search(4)); + } + + @Test + public void testDeleteMiddleElementOfBucket() { + HashMap hashMap = new HashMap<>(3); + hashMap.insert(1, "Value1"); + hashMap.insert(4, "Value4"); + hashMap.insert(7, "Value7"); + + hashMap.delete(4); // Deleting the middle element of the bucket + assertNull(hashMap.search(4)); + assertEquals("Value1", hashMap.search(1)); + assertEquals("Value7", hashMap.search(7)); + } +} \ No newline at end of file From 5f753b5e183bcb741673c5f7b10ccd1c47292957 Mon Sep 17 00:00:00 2001 From: alxkm Date: Wed, 28 Aug 2024 22:08:04 +0200 Subject: [PATCH 2/3] checkstyle: fix formatting --- .../datastructures/hashmap/hashing/HashMap.java | 5 ++--- .../datastructures/hashmap/hashing/HashMapTest.java | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/HashMap.java b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/HashMap.java index daf34ffbd4a6..1aae122b48ec 100644 --- a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/HashMap.java +++ b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/HashMap.java @@ -166,8 +166,7 @@ public void delete(K key) { // Traverse the list to find and delete the node Node current = head; while (current.getNext() != null) { - if ((key == null && current.getNext().getKey() == null) || - (current.getNext().getKey() != null && current.getNext().getKey().equals(key))) { + if ((key == null && current.getNext().getKey() == null) || (current.getNext().getKey() != null && current.getNext().getKey().equals(key))) { current.setNext(current.getNext().getNext()); return; } @@ -252,7 +251,7 @@ public V getValue() { return value; } - public void setValue(V value) { // This method allows updating the value + public void setValue(V value) { // This method allows updating the value this.value = value; } diff --git a/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapTest.java b/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapTest.java index cc8ca8b7f262..520f991ae0a4 100644 --- a/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapTest.java +++ b/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapTest.java @@ -150,4 +150,4 @@ public void testDeleteMiddleElementOfBucket() { assertEquals("Value1", hashMap.search(1)); assertEquals("Value7", hashMap.search(7)); } -} \ No newline at end of file +} From b00126b003f258ab7f13417a5f92c8f5f7a6a8ac Mon Sep 17 00:00:00 2001 From: alxkm Date: Wed, 28 Aug 2024 22:09:25 +0200 Subject: [PATCH 3/3] refactor: remove redundant comments --- .../hashmap/hashing/HashMapTest.java | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapTest.java b/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapTest.java index 520f991ae0a4..3552bc1aa9c5 100644 --- a/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapTest.java +++ b/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapTest.java @@ -38,13 +38,6 @@ public void testDisplay() { hashMap.insert(15, "Value15"); hashMap.insert(25, "Value25"); hashMap.insert(35, "Value35"); - - // Expected output: - // Bucket 0: null - // Bucket 1: 15=Value15 -> 25=Value25 -> 35=Value35 -> null - // Bucket 2: null - // Bucket 3: null - // Bucket 4: null hashMap.display(); } @@ -94,7 +87,7 @@ public void testSearchInEmptyHashMap() { public void testDeleteNonExistentKey() { HashMap hashMap = new HashMap<>(10); hashMap.insert(15, "Value15"); - hashMap.delete(25); // Deleting a key that doesn't exist + hashMap.delete(25); assertEquals("Value15", hashMap.search(15)); assertNull(hashMap.search(25)); @@ -119,7 +112,7 @@ public void testDeleteHeadOfBucket() { hashMap.insert(4, "Value4"); hashMap.insert(7, "Value7"); - hashMap.delete(1); // Deleting the head of the bucket + hashMap.delete(1); assertNull(hashMap.search(1)); assertEquals("Value4", hashMap.search(4)); assertEquals("Value7", hashMap.search(7)); @@ -132,7 +125,7 @@ public void testDeleteTailOfBucket() { hashMap.insert(4, "Value4"); hashMap.insert(7, "Value7"); - hashMap.delete(7); // Deleting the tail of the bucket + hashMap.delete(7); assertNull(hashMap.search(7)); assertEquals("Value1", hashMap.search(1)); assertEquals("Value4", hashMap.search(4)); @@ -145,7 +138,7 @@ public void testDeleteMiddleElementOfBucket() { hashMap.insert(4, "Value4"); hashMap.insert(7, "Value7"); - hashMap.delete(4); // Deleting the middle element of the bucket + hashMap.delete(4); assertNull(hashMap.search(4)); assertEquals("Value1", hashMap.search(1)); assertEquals("Value7", hashMap.search(7));