Skip to content

Commit d183803

Browse files
author
Koushik Sai
committed
Add immutable HashMap implementation
1 parent 48e02b3 commit d183803

File tree

2 files changed

+181
-0
lines changed

2 files changed

+181
-0
lines changed
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package com.thealgorithms.datastructures.hashmap.hashing;
2+
3+
/**
4+
* Immutable HashMap implementation using separate chaining.
5+
* <p>
6+
* This HashMap does not allow modification of existing instances. Any update
7+
* operation returns a new ImmutableHashMap.
8+
* </p>
9+
*
10+
* @param <K> key type
11+
* @param <V> value type
12+
*/
13+
public final class ImmutableHashMap<K, V> {
14+
15+
private static final int DEFAULT_CAPACITY = 16;
16+
17+
private final Node<K, V>[] table;
18+
private final int size;
19+
20+
/**
21+
* Private constructor to enforce immutability.
22+
*/
23+
private ImmutableHashMap(Node<K, V>[] table, int size) {
24+
this.table = table;
25+
this.size = size;
26+
}
27+
28+
/**
29+
* Creates an empty ImmutableHashMap.
30+
*
31+
* @param <K> key type
32+
* @param <V> value type
33+
* @return empty ImmutableHashMap
34+
*/
35+
@SuppressWarnings("unchecked")
36+
public static <K, V> ImmutableHashMap<K, V> empty() {
37+
return new ImmutableHashMap<>(new Node[DEFAULT_CAPACITY], 0);
38+
}
39+
40+
/**
41+
* Returns a new ImmutableHashMap with the given key-value pair added.
42+
*
43+
* @param key key to add
44+
* @param value value to associate
45+
* @return new ImmutableHashMap instance
46+
*/
47+
public ImmutableHashMap<K, V> put(K key, V value) {
48+
Node<K, V>[] newTable = table.clone();
49+
int index = hash(key);
50+
51+
newTable[index] = new Node<>(key, value, newTable[index]);
52+
return new ImmutableHashMap<>(newTable, size + 1);
53+
}
54+
55+
/**
56+
* Retrieves the value associated with the given key.
57+
*
58+
* @param key key to search
59+
* @return value if found, otherwise null
60+
*/
61+
public V get(K key) {
62+
int index = hash(key);
63+
Node<K, V> current = table[index];
64+
65+
while (current != null) {
66+
if ((key == null && current.key == null)
67+
|| (key != null && key.equals(current.key))) {
68+
return current.value;
69+
}
70+
current = current.next;
71+
}
72+
return null;
73+
}
74+
75+
/**
76+
* Checks whether the given key exists in the map.
77+
*
78+
* @param key key to check
79+
* @return true if key exists, false otherwise
80+
*/
81+
public boolean containsKey(K key) {
82+
return get(key) != null;
83+
}
84+
85+
/**
86+
* Returns the number of key-value pairs.
87+
*
88+
* @return size of the map
89+
*/
90+
public int size() {
91+
return size;
92+
}
93+
94+
/**
95+
* Computes hash index for a given key.
96+
*/
97+
private int hash(K key) {
98+
return (key == null
99+
? 0
100+
: (key.hashCode() & Integer.MAX_VALUE) % table.length);
101+
}
102+
103+
/**
104+
* Node class for separate chaining.
105+
*/
106+
private static final class Node<K, V> {
107+
108+
private final K key;
109+
private final V value;
110+
private final Node<K, V> next;
111+
112+
private Node(K key, V value, Node<K, V> next) {
113+
this.key = key;
114+
this.value = value;
115+
this.next = next;
116+
}
117+
}
118+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package com.thealgorithms.datastructures.hashmap.hashing;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertFalse;
5+
import static org.junit.jupiter.api.Assertions.assertNull;
6+
import static org.junit.jupiter.api.Assertions.assertTrue;
7+
import org.junit.jupiter.api.Test;
8+
9+
class ImmutableHashMapTest {
10+
11+
@Test
12+
void testEmptyMap() {
13+
ImmutableHashMap<String, Integer> map
14+
= ImmutableHashMap.<String, Integer>empty();
15+
16+
assertEquals(0, map.size());
17+
assertNull(map.get("A"));
18+
}
19+
20+
@Test
21+
void testPutDoesNotModifyOriginalMap() {
22+
ImmutableHashMap<String, Integer> map1
23+
= ImmutableHashMap.<String, Integer>empty();
24+
25+
ImmutableHashMap<String, Integer> map2 = map1.put("A", 1);
26+
27+
assertEquals(0, map1.size());
28+
assertEquals(1, map2.size());
29+
assertNull(map1.get("A"));
30+
assertEquals(1, map2.get("A"));
31+
}
32+
33+
@Test
34+
void testMultiplePuts() {
35+
ImmutableHashMap<String, Integer> map
36+
= ImmutableHashMap.<String, Integer>empty()
37+
.put("A", 1)
38+
.put("B", 2);
39+
40+
assertEquals(2, map.size());
41+
assertEquals(1, map.get("A"));
42+
assertEquals(2, map.get("B"));
43+
}
44+
45+
@Test
46+
void testContainsKey() {
47+
ImmutableHashMap<String, Integer> map
48+
= ImmutableHashMap.<String, Integer>empty()
49+
.put("X", 100);
50+
51+
assertTrue(map.containsKey("X"));
52+
assertFalse(map.containsKey("Y"));
53+
}
54+
55+
@Test
56+
void testNullKey() {
57+
ImmutableHashMap<String, Integer> map
58+
= ImmutableHashMap.<String, Integer>empty()
59+
.put(null, 50);
60+
61+
assertEquals(50, map.get(null));
62+
}
63+
}

0 commit comments

Comments
 (0)