Skip to content

Commit eb12fcc

Browse files
committed
feat: implement additional functionality for Trie
1 parent 5ec7ce7 commit eb12fcc

File tree

4 files changed

+90
-294
lines changed

4 files changed

+90
-294
lines changed
Lines changed: 87 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
package dataStructures.trie;
22

3+
import java.util.ArrayList;
4+
import java.util.List;
5+
36
/**
4-
* Implementation of Trie structure.
5-
* Supports the follwing common operations (see below for doc):
6-
* insert(String word)
7-
* search(String word)
8-
* startsWith(String prefix)
9-
* prune(String word)
7+
* Implementation of a Trie; Here we consider strings (not case-sensitive)
108
*/
119
public class Trie {
1210
private final TrieNode root;
@@ -15,99 +13,114 @@ public Trie() {
1513
root = new TrieNode();
1614
}
1715

18-
/**
19-
* Insert a word into the trie; converts word to
20-
* to lower-case characters before insertion.
21-
*
22-
* @param word the string to be inserted
23-
*/
2416
public void insert(String word) {
25-
word = word.toLowerCase();
26-
System.out.printf("~~~~~~~Inserting '%s'~~~~~~~%n", word);
27-
TrieNode node = root;
17+
word = word.toLowerCase(); // ignore case-sensitivity
18+
TrieNode trav = root;
2819
for (int i = 0; i < word.length(); i++) {
2920
char curr = word.charAt(i);
30-
if (!node.containsKey(curr)) {
31-
node.insertKey(curr);
21+
if (!trav.children.containsKey(curr)) {
22+
trav.children.put(curr, new TrieNode()); // recall, the edges represent the characters
3223
}
33-
node = node.getNext(curr); // go to the subsequent node!
24+
trav = trav.children.get(curr);
3425
}
35-
node.makeEnd();
26+
trav.isEnd = true; // set word
3627
}
3728

38-
/**
39-
* Search for a word (converted to lower-case) in the trie.
40-
*
41-
* @param word the string to look for
42-
* @return boolean representing whether the word was found
43-
*/
4429
public boolean search(String word) {
45-
word.toLowerCase();
46-
System.out.printf("~~~~~~~Searching '%s'~~~~~~~%n", word);
47-
TrieNode node = root;
30+
word = word.toLowerCase();
31+
TrieNode trav = root;
4832
for (int i = 0; i < word.length(); i++) {
4933
char curr = word.charAt(i);
50-
if (node.containsKey(curr)) {
51-
node = node.getNext(curr);
52-
} else {
34+
if (!trav.children.containsKey(curr)) {
5335
return false;
5436
}
37+
trav = trav.children.get(curr);
5538
}
56-
return node.isEnd();
39+
return trav.isEnd;
5740
}
5841

59-
/**
60-
* Search for a prefix (converted to lower-case) in the trie.
61-
* Note: very similar in implementation to search method
62-
* except the search here does not need to look for end flag
63-
*
64-
* @param prefix the string to look for
65-
* @return boolean representing whether the prefix exists
66-
*/
67-
public boolean startsWith(String prefix) {
68-
prefix = prefix.toLowerCase();
69-
System.out.printf("~~~~~~~Looking for prefix '%s'~~~~~~~%n", prefix);
70-
TrieNode node = root;
71-
for (int i = 0; i < prefix.length(); i++) {
72-
char curr = prefix.charAt(i);
73-
if (node.containsKey(curr)) {
74-
node = node.getNext(curr);
75-
} else {
76-
return false;
42+
public void delete(String word) {
43+
word = word.toLowerCase();
44+
TrieNode trav = root;
45+
for (int i = 0; i < word.length(); i++) {
46+
char curr = word.charAt(i);
47+
if (!trav.children.containsKey(curr)) {
48+
return; // word does not exist in trie, so just return
7749
}
50+
trav = trav.children.get(curr);
7851
}
79-
return true;
52+
trav.isEnd = false; // remove word from being tracked
8053
}
8154

82-
/**
83-
* Removes a word from the trie by toggling the end flag;
84-
* if any of the end nodes (next nodes relative to current)
85-
* do not hold further characters, repetitively prune the trie
86-
* by removing these nodes from the hashmap of the current node.
87-
* Note: This method is useful in optimizing searching for a set of known words
88-
* especially when the data to be traversed has words that are similar in spelling/
89-
* repeated words which might have been previously found.
90-
*
91-
* @param word the word to be removed
92-
*/
93-
public void prune(String word) {
94-
word = word.toLowerCase();
95-
System.out.printf("~~~~~~~Removing '%s'~~~~~~~%n", word);
96-
TrieNode node = root;
97-
TrieNode[] track = new TrieNode[word.length()];
55+
// ABOVE ARE STANDARD METHODS OF A TYPICAL TRIE IMPLEMENTATION
56+
// BELOW IMPLEMENTS TWO MORE COMMON / USEFUL METHODS FOR TRIE; IN PARTICULAR, NOTE THE PRUNING METHOD
57+
58+
public void deleteAndPrune(String word) {
59+
List<TrieNode> trackNodes = new ArrayList<>();
60+
TrieNode trav = root;
9861
for (int i = 0; i < word.length(); i++) {
9962
char curr = word.charAt(i);
100-
track[i] = node;
101-
node = node.getNext(curr);
63+
if (!trav.children.containsKey(curr)) {
64+
return; // word does not exist in trie
65+
}
66+
trackNodes.add(trav);
67+
trav = trav.children.get(curr);
10268
}
103-
node.removeEnd();
69+
trav.isEnd = false;
70+
71+
// now we start pruning
10472
for (int i = word.length() - 1; i >= 0; i--) {
10573
char curr = word.charAt(i);
106-
if (track[i].getNext(curr).getCharacters().size() > 0) {
107-
break; // done further nodes are required
108-
} else {
109-
track[i].getCharacters().remove(curr);
74+
TrieNode nodeBeforeCurr = trackNodes.get(i);
75+
TrieNode nextNode = nodeBeforeCurr.children.get(curr);
76+
if (nextNode.children.size() == 0) { // this node essentially doesn't track anything, remove
77+
nodeBeforeCurr.children.remove(curr);
78+
} else { // children.size() > 0; i.e. this node is still useful; no need to further prune upwards
79+
break;
80+
}
81+
}
82+
return;
83+
}
84+
85+
public List<String> wordsWithPrefix(String prefix) {
86+
List<String> ret = new ArrayList<>();
87+
TrieNode trav = root;
88+
for (int i = 0; i < prefix.length(); i++) {
89+
char curr = prefix.charAt(i);
90+
if (!trav.children.containsKey(curr)) {
91+
return ret; // no words with this prefix
92+
}
93+
trav = trav.children.get(curr);
94+
}
95+
List<StringBuilder> allSuffix = getAllSuffixFromNode(trav);
96+
for (StringBuilder sb : allSuffix) {
97+
ret.add(prefix + sb.toString());
98+
}
99+
return ret;
100+
}
101+
102+
public List<String> getAllWords() {
103+
List<StringBuilder> allWords = getAllSuffixFromNode(root);
104+
List<String> ret = new ArrayList<>();
105+
for (StringBuilder sb : allWords) {
106+
ret.add(sb.toString());
107+
}
108+
return ret;
109+
}
110+
111+
private List<StringBuilder> getAllSuffixFromNode(TrieNode node) {
112+
List<StringBuilder> ret = new ArrayList<>();
113+
if (node.isEnd) {
114+
ret.add(new StringBuilder(""));
115+
}
116+
for (char c : node.children.keySet()) {
117+
TrieNode nextNode = node.children.get(c);
118+
List<StringBuilder> allSuffix = getAllSuffixFromNode(nextNode);
119+
for (StringBuilder sb : allSuffix) {
120+
sb.insert(0, c); // insert c at the front
121+
ret.add(sb);
110122
}
111123
}
124+
return ret;
112125
}
113126
}
Lines changed: 3 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,85 +1,11 @@
11
package dataStructures.trie;
22

3-
import java.util.HashMap;
43
import java.util.Map;
54

65
/**
7-
* Implementation of TrieNode for Trie structure (not restricited to alphabets)
8-
* Each TrieNode has the following attributes:
9-
* characters: HashMap whose keys represent the available characters and
10-
* values represent the nodes to select the next characters
11-
* NOTE: Presence of a character in the hashmap means that this character
12-
* is part of the trie structure and can be used to continue forming a searched word
13-
* or return the accumulation of letters as the word if the end flag is toggled to true
14-
* end : Tells us whether the current TrieNode is actually a terminating flag
6+
* Implementation of a TrieNode.
157
*/
16-
178
public class TrieNode {
18-
private final Map<Character, TrieNode> characters;
19-
private boolean end;
20-
21-
public TrieNode() {
22-
characters = new HashMap<>();
23-
}
24-
25-
/**
26-
* Checks if node has a character.
27-
*
28-
* @param c character to check for presence
29-
* @return boolean representing if the character exists
30-
*/
31-
public boolean containsKey(char c) {
32-
return characters.containsKey(c);
33-
}
34-
35-
/**
36-
* Get the next node at the index represented by the character.
37-
*
38-
* @param c character whose index holds the desired next node
39-
* @return the desired node at that index
40-
*/
41-
public TrieNode getNext(char c) {
42-
return characters.get(c);
43-
}
44-
45-
/**
46-
* Get the avaialble characters can be used as the next letter from the current node.
47-
*
48-
* @return hashmap of characters and next nodes
49-
*/
50-
public Map<Character, TrieNode> getCharacters() {
51-
return characters;
52-
}
53-
54-
/**
55-
* Inserts a character to the current TrieNode.
56-
*
57-
* @param c character whose index represents where to insert the node
58-
*/
59-
public void insertKey(char c) {
60-
characters.put(c, new TrieNode());
61-
}
62-
63-
/**
64-
* Checks if the current TrieNode is a terminating flag.
65-
*
66-
* @return boolean value
67-
*/
68-
public boolean isEnd() {
69-
return end;
70-
}
71-
72-
/**
73-
* Make the current TrieNode a terminating flag/node.
74-
*/
75-
public void makeEnd() {
76-
end = true;
77-
}
78-
79-
/**
80-
* Remove terminating flag from current TrieNode.
81-
*/
82-
public void removeEnd() {
83-
end = false;
84-
}
9+
public Map<Character, TrieNode> children; // or an array of size 26 (assume not case-sensitive) to denote each char
10+
public boolean isEnd; // the marker to indicate whether the path from the root to this node forms a known word
8511
}

src/main/java/dataStructures/trie/legacy/Trie.java

Lines changed: 0 additions & 76 deletions
This file was deleted.

0 commit comments

Comments
 (0)