refactor: HashMap (#5426)

* refactor: HashMap

* checkstyle: fix formatting

* refactor: remove redundant comments

---------

Co-authored-by: alxkm <alx@alx.com>
This commit is contained in:
Alex Klymenko 2024-08-28 22:29:24 +02:00 committed by GitHub
parent 6b7a1fdbe8
commit a23e9b0ba8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 367 additions and 144 deletions

View File

@ -1,146 +1,275 @@
package com.thealgorithms.datastructures.hashmap.hashing; 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 <K> the type of keys maintained by this map
* @param <V> the type of mapped values
*/
public class HashMap<K, V> {
private final int hashSize;
private final LinkedList<K, V>[] buckets;
private final int hsize; /**
private final LinkedList[] buckets; * Constructs a HashMap with the specified hash size.
*
public HashMap(int hsize) { * @param hashSize the number of buckets in the hash map
buckets = new LinkedList[hsize]; */
for (int i = 0; i < hsize; i++) { @SuppressWarnings("unchecked")
buckets[i] = new LinkedList(); public HashMap(int hashSize) {
// Java requires explicit initialization of each object 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; * Computes the hash code for the specified key.
if (hash < 0) { * Null keys are hashed to bucket 0.
hash += hsize; *
* @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); * Inserts the specified key-value pair into the hash map.
buckets[hash].insert(key); * 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); buckets[hash].delete(key);
} }
public void displayHashtable() { /**
for (int i = 0; i < hsize; i++) { * Searches for the value associated with the specified key in the hash map.
System.out.printf("Bucket %d :", i); *
System.out.println(buckets[i].display()); * @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<K, V> node = buckets[hash].findKey(key);
return node != null ? node.getValue() : 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 static class LinkedList { /**
* A nested static class that represents a linked list used for separate chaining in the hash map.
*
* @param <K> the type of keys maintained by this linked list
* @param <V> the type of mapped values
*/
public static class LinkedList<K, V> {
private Node<K, V> head;
private Node first; /**
* Inserts the specified key-value pair into the linked list.
public LinkedList() { * If the linked list is empty, the pair becomes the head.
first = null; * Otherwise, the pair is added to the end of the list.
} *
* @param key the key to be inserted
public void insert(int key) { * @param value the value to be associated with the key
if (isEmpty()) { */
first = new Node(key); public void insert(K key, V value) {
return; Node<K, V> 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<K, V> temp = findEnd(head);
temp.setNext(new Node<>(key, value));
}
} }
Node temp = findEnd(first);
temp.setNext(new Node(key));
} }
private Node findEnd(Node n) { /**
while (n.getNext() != null) { * Finds the last node in the linked list.
n = n.getNext(); *
* @param node the starting node
* @return the last node in the linked list
*/
private Node<K, V> findEnd(Node<K, V> node) {
while (node.getNext() != null) {
node = node.getNext();
} }
return n; return node;
} }
public Node findKey(int key) { /**
if (!isEmpty()) { * Finds the node associated with the specified key in the linked list.
Node temp = first; *
if (temp.getKey() == key) { * @param key the key to search for
* @return the node associated with the specified key, or null if not found
*/
public Node<K, V> findKey(K key) {
Node<K, V> temp = head;
while (temp != null) {
if ((key == null && temp.getKey() == null) || (temp.getKey() != null && temp.getKey().equals(key))) {
return temp; return temp;
} }
temp = temp.getNext();
while ((temp = temp.getNext()) != null) {
if (temp.getKey() == key) {
return temp;
}
}
} }
return null; return null;
} }
public void delete(int key) { /**
if (!isEmpty()) { * Deletes the node associated with the specified key from the linked list.
if (first.getKey() == key) { * Handles the case where the key could be null.
Node next = first.next; *
first.next = null; // help GC * @param key the key whose associated node is to be deleted
first = next; */
} else { public void delete(K key) {
delete(first, key); if (isEmpty()) {
return;
}
// 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<K, V> 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;
} }
current = current.getNext();
} }
} }
private void delete(Node n, int key) { /**
if (n.getNext().getKey() == key) { * Displays the contents of the linked list as a string.
if (n.getNext().getNext() == null) { *
n.setNext(null); * @return a string representation of the linked list
} else { */
n.setNext(n.getNext().getNext());
}
} else {
delete(n.getNext(), key);
}
}
public String display() { public String display() {
return display(first); return display(head);
} }
private String display(Node n) { /**
if (n == null) { * Constructs a string representation of the linked list non-recursively.
return "null"; *
} else { * @param node the starting node
return n.getKey() + "->" + display(n.getNext()); * @return a string representation of the linked list starting from the given node
*/
private String display(Node<K, V> 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() { 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 <K> the type of key maintained by this node
* @param <V> the type of value maintained by this node
*/
public static class Node<K, V> {
private final K key;
private V value;
private Node<K, V> next;
private Node next; /**
private final int key; * Constructs a Node with the specified key and value.
*
public Node(int key) { * @param key the key associated with this node
next = null; * @param value the value associated with this node
*/
public Node(K key, V value) {
this.key = key; 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 int getKey() { */
public K getKey() {
return key; return key;
} }
public void setNext(Node next) { /**
* 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<K, V> getNext() {
return next;
}
/**
* Sets the next node in the linked list.
*
* @param next the next node to be linked
*/
public void setNext(Node<K, V> next) {
this.next = next; this.next = next;
} }
} }

View File

@ -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);
}
}
}
}

View File

@ -0,0 +1,146 @@
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<Integer, String> 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<Integer, String> 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<Integer, String> hashMap = new HashMap<>(5);
hashMap.insert(15, "Value15");
hashMap.insert(25, "Value25");
hashMap.insert(35, "Value35");
hashMap.display();
}
@Test
public void testInsertNullKey() {
HashMap<Integer, String> hashMap = new HashMap<>(10);
hashMap.insert(null, "NullValue");
assertEquals("NullValue", hashMap.search(null));
}
@Test
public void testInsertNullValue() {
HashMap<Integer, String> hashMap = new HashMap<>(10);
hashMap.insert(15, null);
assertNull(hashMap.search(15));
}
@Test
public void testUpdateExistingKey() {
HashMap<Integer, String> hashMap = new HashMap<>(10);
hashMap.insert(15, "Value15");
hashMap.insert(15, "UpdatedValue15");
assertEquals("UpdatedValue15", hashMap.search(15));
}
@Test
public void testHandleCollisions() {
HashMap<Integer, String> 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<Integer, String> hashMap = new HashMap<>(10);
assertNull(hashMap.search(10));
}
@Test
public void testDeleteNonExistentKey() {
HashMap<Integer, String> hashMap = new HashMap<>(10);
hashMap.insert(15, "Value15");
hashMap.delete(25);
assertEquals("Value15", hashMap.search(15));
assertNull(hashMap.search(25));
}
@Test
public void testInsertLargeNumberOfElements() {
HashMap<Integer, String> 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<Integer, String> hashMap = new HashMap<>(3);
hashMap.insert(1, "Value1");
hashMap.insert(4, "Value4");
hashMap.insert(7, "Value7");
hashMap.delete(1);
assertNull(hashMap.search(1));
assertEquals("Value4", hashMap.search(4));
assertEquals("Value7", hashMap.search(7));
}
@Test
public void testDeleteTailOfBucket() {
HashMap<Integer, String> hashMap = new HashMap<>(3);
hashMap.insert(1, "Value1");
hashMap.insert(4, "Value4");
hashMap.insert(7, "Value7");
hashMap.delete(7);
assertNull(hashMap.search(7));
assertEquals("Value1", hashMap.search(1));
assertEquals("Value4", hashMap.search(4));
}
@Test
public void testDeleteMiddleElementOfBucket() {
HashMap<Integer, String> hashMap = new HashMap<>(3);
hashMap.insert(1, "Value1");
hashMap.insert(4, "Value4");
hashMap.insert(7, "Value7");
hashMap.delete(4);
assertNull(hashMap.search(4));
assertEquals("Value1", hashMap.search(1));
assertEquals("Value7", hashMap.search(7));
}
}