Skip to content

Commit b79ad31

Browse files
authored
Improve formatting and clarity in PatriciaTrie.java
Refactored PatriciaTrie.java for better formatting and clarity.
1 parent 2033744 commit b79ad31

File tree

1 file changed

+75
-83
lines changed

1 file changed

+75
-83
lines changed

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

Lines changed: 75 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,27 @@
11
package com.thealgorithms.datastructures.tries;
22

3+
// FIX: Placed each import on its own line for proper formatting.
34
import java.util.HashMap;
45
import 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.
2025
public 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

Comments
 (0)