11package com .thealgorithms .datastructures .tries ;
22
3- // FIX: Placed each import on its own line for proper formatting.
43import java .util .HashMap ;
54import 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.
2522public 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