Skip to content

Commit 428d04b

Browse files
authored
Refactor PatriciaTrie for improved readability
1 parent 7835a10 commit 428d04b

File tree

1 file changed

+29
-45
lines changed

1 file changed

+29
-45
lines changed

src/main/java/com/thealgorithms/datastructures/tries/PatriciaTrie.java

Lines changed: 29 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,38 @@
11
package com.thealgorithms.datastructures.tries;
22

3-
// FIX: Placed each import on its own line for proper formatting.
43
import java.util.HashMap;
54
import java.util.Map;
65

76
/**
87
* Patricia (radix) trie for String keys and generic values.
98
*
10-
* <p>A Patricia Trie is a memory-optimized trie where each node with only one
11-
* child is merged with its child. This results in edges being labeled with
12-
* strings instead of single characters.</p>
9+
* <p>A Patricia Trie is a memory-optimized trie where nodes with a single child
10+
* are merged, so edges are labeled with strings instead of single characters.</p>
1311
*
1412
* <p>Operations are O(L) where L is the key length.</p>
1513
*
1614
* <p>Contracts:
1715
* <ul>
18-
* <li>Null keys are not allowed (IllegalArgumentException).</li>
19-
* <li>The empty-string key ("") is a valid key.</li>
20-
* <li>Null values are not allowed (IllegalArgumentException).</li>
16+
* <li>Null keys are not allowed (IllegalArgumentException).</li>
17+
* <li>The empty-string key ("") is a valid key.</li>
18+
* <li>Null values are not allowed (IllegalArgumentException).</li>
2119
* </ul>
2220
* </p>
2321
*/
24-
// FIX: Added a newline after the Javadoc for proper formatting.
2522
public final class PatriciaTrie<V> {
2623

2724
/** Node with compressed outgoing edges (label -> child). */
2825
private static final class Node<V> {
29-
Map<String, Node<V>> children = new HashMap<>();
30-
boolean hasValue;
31-
V value;
26+
private final Map<String, Node<V>> children = new HashMap<>();
27+
private boolean hasValue;
28+
private V value;
3229
}
3330

3431
private final Node<V> root = new Node<>();
3532
private int size; // number of stored keys
3633

3734
/** Creates an empty Patricia trie. */
38-
public PatriciaTrie() {
39-
}
35+
public PatriciaTrie() {}
4036

4137
/**
4238
* Inserts or updates the value associated with {@code key}.
@@ -95,7 +91,6 @@ public boolean remove(String key) {
9591
if (key == null) {
9692
throw new IllegalArgumentException("key must not be null");
9793
}
98-
// The empty key is a special case as it's stored on the root node.
9994
if (key.isEmpty()) {
10095
if (root.hasValue) {
10196
root.hasValue = false;
@@ -154,11 +149,10 @@ private void insert(Node<V> node, String key, V value) {
154149
int cpl = commonPrefixLen(edge, key);
155150

156151
if (cpl == 0) {
157-
continue; // No common prefix, check next child.
152+
continue; // No common prefix, try next edge.
158153
}
159154

160-
// Case 1: The entire edge and key match perfectly.
161-
// e.g., edge="apple", key="apple". Update the value at the child node.
155+
// Case 1: edge == key
162156
if (cpl == edge.length() && cpl == key.length()) {
163157
Node<V> child = e.getValue();
164158
if (!child.hasValue) {
@@ -169,17 +163,15 @@ private void insert(Node<V> node, String key, V value) {
169163
return;
170164
}
171165

172-
// Case 2: The edge is a prefix of the key.
173-
// e.g., edge="apple", key="applepie". Recurse on the child node.
166+
// Case 2: edge is prefix of key
174167
if (cpl == edge.length() && cpl < key.length()) {
175168
Node<V> child = e.getValue();
176169
String rest = key.substring(cpl);
177170
insert(child, rest, value);
178171
return;
179172
}
180173

181-
// Case 3: The key and edge partially match, requiring a split.
182-
// e.g., edge="romane", key="romanus" -> split at "roman".
174+
// Case 3: partial overlap → split
183175
String prefix = edge.substring(0, cpl);
184176
String edgeSuffix = edge.substring(cpl);
185177
String keySuffix = key.substring(cpl);
@@ -188,30 +180,29 @@ private void insert(Node<V> node, String key, V value) {
188180
node.children.remove(edge);
189181
node.children.put(prefix, mid);
190182

191-
// The old child is re-attached to the new middle node.
183+
// Reattach old child under the split node
192184
Node<V> oldChild = e.getValue();
193185
mid.children.put(edgeSuffix, oldChild);
194186

195187
if (keySuffix.isEmpty()) {
196-
// The new key ends at the split point, e.g., inserting "roman"
188+
// New key ends at split
197189
if (!mid.hasValue) {
198190
size++;
199191
}
200192
mid.hasValue = true;
201193
mid.value = value;
202194
} else {
203-
// The new key branches off after the split point, e.g., "romanus"
195+
// New branch after split
204196
Node<V> leaf = new Node<>();
205197
leaf.hasValue = true;
206198
leaf.value = value;
207199
mid.children.put(keySuffix, leaf);
208-
// FIX: This was the main bug. A new key was added, but size was not incremented.
209-
size++;
200+
size++; // important: new key added
210201
}
211202
return;
212203
}
213204

214-
// Case 4: No existing edge shares a prefix. Create a new one.
205+
// Case 4: no shared prefix; create a new edge
215206
Node<V> leaf = new Node<>();
216207
leaf.hasValue = true;
217208
leaf.value = value;
@@ -250,11 +241,9 @@ private Node<V> findPrefixNode(Node<V> node, String prefix) {
250241
return null;
251242
}
252243

253-
/**
254-
* Recursive removal with cleanup (merging pass-through nodes).
255-
*/
244+
/** Recursive removal with cleanup (merging pass-through nodes). */
256245
private boolean removeRecursive(Node<V> parent, String key) {
257-
// Iterate on a snapshot to allow modification during the loop.
246+
// Iterate on a snapshot to allow modification during the loop
258247
for (Map.Entry<String, Node<V>> entry : new HashMap<>(parent.children).entrySet()) {
259248
String edge = entry.getKey();
260249
Node<V> child = entry.getValue();
@@ -266,40 +255,35 @@ private boolean removeRecursive(Node<V> parent, String key) {
266255
String rest = key.substring(edge.length());
267256
boolean removed;
268257
if (rest.isEmpty()) {
269-
// This is the node to delete.
270258
if (!child.hasValue) {
271-
return false; // Key doesn't actually exist.
259+
return false; // key not present
272260
}
273261
child.hasValue = false;
274262
child.value = null;
275263
size--;
276264
removed = true;
277265
} else {
278-
// Continue search down the tree.
279266
removed = removeRecursive(child, rest);
280267
}
281268

282269
if (!removed) {
283270
return false;
284271
}
285272

286-
// Post-recursion cleanup: merge nodes if necessary.
273+
// Cleanup/compaction
287274
if (!child.hasValue) {
288-
int childDegree = child.children.size();
289-
if (childDegree == 0) {
290-
// If child is now a valueless leaf, remove it.
275+
int degree = child.children.size();
276+
if (degree == 0) {
291277
parent.children.remove(edge);
292-
} else if (childDegree == 1) {
293-
// If child is a pass-through node, merge it with its own child.
294-
Map.Entry<String, Node<V>> grandchildEntry = child.children.entrySet().iterator().next();
295-
String grandEdge = grandchildEntry.getKey();
296-
Node<V> grandchild = grandchildEntry.getValue();
297-
278+
} else if (degree == 1) {
279+
Map.Entry<String, Node<V>> gc = child.children.entrySet().iterator().next();
280+
String grandEdge = gc.getKey();
281+
Node<V> grandchild = gc.getValue();
298282
parent.children.remove(edge);
299283
parent.children.put(edge + grandEdge, grandchild);
300284
}
301285
}
302-
return true; // Key was found and processed in this path.
286+
return true; // processed on this path
303287
}
304288
return false;
305289
}

0 commit comments

Comments
 (0)