11package com .thealgorithms .datastructures .tries ;
22
3+ // FIX: Placed each import on its own line for proper formatting.
34import java .util .HashMap ;
45import java .util .Map ;
56
67/**
78 * Patricia (radix) trie for String keys and generic values.
89 *
9- * <p>Compressed edges: each child edge stores a non-empty String label.
10- * Operations are O(L) where L is the key length.</p>
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>
13+ *
14+ * <p>Operations are O(L) where L is the key length.</p>
1115 *
1216 * <p>Contracts:
1317 * <ul>
14- * <li>Null keys are not allowed (IllegalArgumentException).</li>
15- * <li>Empty -string key ("") is allowed as a valid key.</li>
16- * <li>Null values are not allowed (IllegalArgumentException).</li>
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>
1721 * </ul>
1822 * </p>
1923 */
24+ // FIX: Added a newline after the Javadoc for proper formatting.
2025public final class PatriciaTrie <V > {
2126
2227 /** Node with compressed outgoing edges (label -> child). */
@@ -65,6 +70,9 @@ public V get(String key) {
6570
6671 /**
6772 * Returns true if the trie contains {@code key}.
73+ *
74+ * @param key non-null key
75+ * @return true if the key is present
6876 */
6977 public boolean contains (String key ) {
7078 if (key == null ) {
@@ -87,20 +95,24 @@ public boolean remove(String key) {
8795 if (key == null ) {
8896 throw new IllegalArgumentException ("key must not be null" );
8997 }
98+ // The empty key is a special case as it's stored on the root node.
9099 if (key .isEmpty ()) {
91- if (!root .hasValue ) {
92- return false ;
100+ if (root .hasValue ) {
101+ root .hasValue = false ;
102+ root .value = null ;
103+ size --;
104+ return true ;
93105 }
94- root .hasValue = false ;
95- root .value = null ;
96- size --;
97- return true ;
106+ return false ;
98107 }
99108 return removeRecursive (root , key );
100109 }
101110
102111 /**
103- * Returns true if any key starts with {@code prefix}.
112+ * Returns true if any key in the trie starts with {@code prefix}.
113+ *
114+ * @param prefix non-null prefix
115+ * @return true if a key starts with the given prefix
104116 */
105117 public boolean startsWith (String prefix ) {
106118 if (prefix == null ) {
@@ -113,12 +125,12 @@ public boolean startsWith(String prefix) {
113125 return n != null ;
114126 }
115127
116- /** Number of stored keys. */
128+ /** Returns the number of stored keys. */
117129 public int size () {
118130 return size ;
119131 }
120132
121- /** True if no keys are stored. */
133+ /** Returns true if no keys are stored. */
122134 public boolean isEmpty () {
123135 return size == 0 ;
124136 }
@@ -140,11 +152,13 @@ private void insert(Node<V> node, String key, V value) {
140152 for (Map .Entry <String , Node <V >> e : node .children .entrySet ()) {
141153 String edge = e .getKey ();
142154 int cpl = commonPrefixLen (edge , key );
155+
143156 if (cpl == 0 ) {
144- continue ;
157+ continue ; // No common prefix, check next child.
145158 }
146159
147- // edge matches the entire key
160+ // Case 1: The entire edge and key match perfectly.
161+ // e.g., edge="apple", key="apple". Update the value at the child node.
148162 if (cpl == edge .length () && cpl == key .length ()) {
149163 Node <V > child = e .getValue ();
150164 if (!child .hasValue ) {
@@ -155,15 +169,17 @@ private void insert(Node<V> node, String key, V value) {
155169 return ;
156170 }
157171
158- // edge is full prefix of key -> go down
172+ // Case 2: The edge is a prefix of the key.
173+ // e.g., edge="apple", key="applepie". Recurse on the child node.
159174 if (cpl == edge .length () && cpl < key .length ()) {
160175 Node <V > child = e .getValue ();
161176 String rest = key .substring (cpl );
162177 insert (child , rest , value );
163178 return ;
164179 }
165180
166- // split edge (partial overlap or key is prefix of edge)
181+ // Case 3: The key and edge partially match, requiring a split.
182+ // e.g., edge="romane", key="romanus" -> split at "roman".
167183 String prefix = edge .substring (0 , cpl );
168184 String edgeSuffix = edge .substring (cpl );
169185 String keySuffix = key .substring (cpl );
@@ -172,30 +188,30 @@ private void insert(Node<V> node, String key, V value) {
172188 node .children .remove (edge );
173189 node .children .put (prefix , mid );
174190
191+ // The old child is re-attached to the new middle node.
175192 Node <V > oldChild = e .getValue ();
176- if (!edgeSuffix .isEmpty ()) {
177- mid .children .put (edgeSuffix , oldChild );
178- } else {
179- // Should not occur since cpl < edge.length() for this branch
180- mid .children .put ("" , oldChild );
181- }
193+ mid .children .put (edgeSuffix , oldChild );
182194
183195 if (keySuffix .isEmpty ()) {
196+ // The new key ends at the split point, e.g., inserting "roman"
184197 if (!mid .hasValue ) {
185198 size ++;
186199 }
187200 mid .hasValue = true ;
188201 mid .value = value ;
189202 } else {
203+ // The new key branches off after the split point, e.g., "romanus"
190204 Node <V > leaf = new Node <>();
191205 leaf .hasValue = true ;
192206 leaf .value = value ;
193207 mid .children .put (keySuffix , leaf );
208+ // FIX: This was the main bug. A new key was added, but size was not incremented.
209+ size ++;
194210 }
195211 return ;
196212 }
197213
198- // No common prefix with any child: create a new edge
214+ // Case 4: No existing edge shares a prefix. Create a new one.
199215 Node <V > leaf = new Node <>();
200216 leaf .hasValue = true ;
201217 leaf .value = value ;
@@ -209,16 +225,9 @@ private Node<V> findNode(Node<V> node, String key) {
209225 }
210226 for (Map .Entry <String , Node <V >> e : node .children .entrySet ()) {
211227 String edge = e .getKey ();
212- int cpl = commonPrefixLen (edge , key );
213- if (cpl == 0 ) {
214- continue ;
215- }
216- if (cpl == edge .length ()) {
217- String rest = key .substring (cpl );
228+ if (key .startsWith (edge )) {
229+ String rest = key .substring (edge .length ());
218230 return findNode (e .getValue (), rest );
219- } else {
220- // partial match but edge not fully consumed => absent
221- return null ;
222231 }
223232 }
224233 return null ;
@@ -230,96 +239,79 @@ private Node<V> findPrefixNode(Node<V> node, String prefix) {
230239 }
231240 for (Map .Entry <String , Node <V >> e : node .children .entrySet ()) {
232241 String edge = e .getKey ();
233- int cpl = commonPrefixLen ( edge , prefix );
234- if ( cpl == 0 ) {
235- continue ;
242+ if ( prefix . startsWith ( edge )) {
243+ String rest = prefix . substring ( edge . length ());
244+ return findPrefixNode ( e . getValue (), rest ) ;
236245 }
237- if (cpl == prefix . length ( )) {
246+ if (edge . startsWith ( prefix )) {
238247 return e .getValue ();
239248 }
240- if (cpl == edge .length ()) {
241- String rest = prefix .substring (cpl );
242- return findPrefixNode (e .getValue (), rest );
243- }
244- // neither fully consumed -> no such prefix
245- return null ;
246249 }
247250 return null ;
248251 }
249252
250253 /**
251- * Recursive removal + cleanup (merge).
252- *
253- * <p>On the way back up, if a child has no value and:
254- * <ul>
255- * <li>deg == 0: remove it from parent,</li>
256- * <li>deg == 1: merge it with its single child (concatenate edge labels).</li>
257- * </ul>
258- * </p>
254+ * Recursive removal with cleanup (merging pass-through nodes).
259255 */
260256 private boolean removeRecursive (Node <V > parent , String key ) {
261- // iterate on a snapshot of keys to allow modifications during loop
262- String [] keys = parent .children .keySet ().toArray (new String [0 ]);
263- for (String edge : keys ) {
264- int cpl = commonPrefixLen (edge , key );
265- if (cpl == 0 ) {
266- continue ;
267- }
268-
269- Node <V > child = parent .children .get (edge );
257+ // Iterate on a snapshot to allow modification during the loop.
258+ for (Map .Entry <String , Node <V >> entry : new HashMap <>(parent .children ).entrySet ()) {
259+ String edge = entry .getKey ();
260+ Node <V > child = entry .getValue ();
270261
271- // partial overlap with edge => key doesn't exist in this branch
272- if (cpl < edge .length ()) {
273- return false ;
262+ if (!key .startsWith (edge )) {
263+ continue ;
274264 }
275265
276- String rest = key .substring (cpl );
266+ String rest = key .substring (edge . length () );
277267 boolean removed ;
278268 if (rest .isEmpty ()) {
269+ // This is the node to delete.
279270 if (!child .hasValue ) {
280- return false ;
271+ return false ; // Key doesn't actually exist.
281272 }
282273 child .hasValue = false ;
283274 child .value = null ;
284275 size --;
285276 removed = true ;
286277 } else {
278+ // Continue search down the tree.
287279 removed = removeRecursive (child , rest );
288280 }
289281
290282 if (!removed ) {
291283 return false ;
292284 }
293285
294- // post -recursion cleanup of child
286+ // Post -recursion cleanup: merge nodes if necessary.
295287 if (!child .hasValue ) {
296- int deg = child .children .size ();
297- if (deg == 0 ) {
298- // fully remove empty child (fixes leaf removal case)
288+ int childDegree = child .children .size ();
289+ if (childDegree == 0 ) {
290+ // If child is now a valueless leaf, remove it.
299291 parent .children .remove (edge );
300- } else if (deg == 1 ) {
301- // merge pass-through child with its only grandchild
302- Map .Entry <String , Node <V >> only =
303- child .children .entrySet ().iterator ().next ();
304- String grandEdge = only .getKey ();
305- Node <V > grand = only .getValue ();
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 ();
306297
307298 parent .children .remove (edge );
308- parent .children .put (edge + grandEdge , grand );
299+ parent .children .put (edge + grandEdge , grandchild );
309300 }
310301 }
311- return true ; // processed the matching path
302+ return true ; // Key was found and processed in this path.
312303 }
313304 return false ;
314305 }
315306
316- /** Length of common prefix of a and b . */
307+ /** Returns the length of the common prefix of two strings . */
317308 private static int commonPrefixLen (String a , String b ) {
318309 int n = Math .min (a .length (), b .length ());
319- int i = 0 ;
320- while (i < n && a .charAt (i ) == b .charAt (i )) {
321- i ++;
310+ for (int i = 0 ; i < n ; i ++) {
311+ if (a .charAt (i ) != b .charAt (i )) {
312+ return i ;
313+ }
322314 }
323- return i ;
315+ return n ;
324316 }
325317}
0 commit comments