refactor: HashMap
(#5426)
* refactor: HashMap * checkstyle: fix formatting * refactor: remove redundant comments --------- Co-authored-by: alxkm <alx@alx.com>
This commit is contained in:
parent
6b7a1fdbe8
commit
a23e9b0ba8
@ -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
|
||||||
|
*/
|
||||||
|
public void insert(K key, V value) {
|
||||||
|
Node<K, V> existingNode = findKey(key);
|
||||||
|
if (existingNode != null) {
|
||||||
|
existingNode.setValue(value); // Update the value, even if it's null
|
||||||
|
} else {
|
||||||
if (isEmpty()) {
|
if (isEmpty()) {
|
||||||
first = new Node(key);
|
head = new Node<>(key, value);
|
||||||
return;
|
} else {
|
||||||
|
Node<K, V> temp = findEnd(head);
|
||||||
|
temp.setNext(new Node<>(key, value));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Node temp = findEnd(first);
|
/**
|
||||||
temp.setNext(new Node(key));
|
* Finds the last node in the linked list.
|
||||||
|
*
|
||||||
|
* @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 node;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Node findEnd(Node n) {
|
/**
|
||||||
while (n.getNext() != null) {
|
* Finds the node associated with the specified key in the linked list.
|
||||||
n = n.getNext();
|
*
|
||||||
}
|
* @param key the key to search for
|
||||||
return n;
|
* @return the node associated with the specified key, or null if not found
|
||||||
}
|
*/
|
||||||
|
public Node<K, V> findKey(K key) {
|
||||||
public Node findKey(int key) {
|
Node<K, V> temp = head;
|
||||||
if (!isEmpty()) {
|
while (temp != null) {
|
||||||
Node temp = first;
|
if ((key == null && temp.getKey() == null) || (temp.getKey() != null && temp.getKey().equals(key))) {
|
||||||
if (temp.getKey() == 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user