forked from nus-cs2103-AY2324S2/tp
-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #60 from ForAeons/trie-datastructure
feat: add trie data structure for autocomplete feature
- Loading branch information
Showing
5 changed files
with
408 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
package seedu.address.commons.util; | ||
|
||
/** | ||
* A class that represents a Trie data structure. | ||
*/ | ||
public class Trie { | ||
private final TrieNode root; | ||
|
||
/** | ||
* Constructor for Trie. Accepts a list of words to be inserted into the Trie. | ||
* @param words the words to be inserted into the Trie | ||
*/ | ||
public Trie(String... words) { | ||
this.root = new TrieNode(null); | ||
for (String word : words) { | ||
insert(word); | ||
} | ||
} | ||
|
||
//@@author <a href="https://github.com/eugenp/tutorials">eugenp</a> | ||
//Reused from https://www.baeldung.com/trie-java with minor modifications | ||
/** | ||
* Insert a word into the Trie. | ||
* @author <a href="https://github.com/eugenp/tutorials">eugenp</a> | ||
* @param word the word to be inserted | ||
*/ | ||
public void insert(String word) { | ||
TrieNode current = root; | ||
for (char c : word.toCharArray()) { | ||
if (current.getChild(c) == null) { | ||
current.setChild(c); | ||
} | ||
current = current.getChild(c); | ||
} | ||
current.setEndOfWord(true); | ||
} | ||
//@@author | ||
|
||
//@@author <a href="https://github.com/eugenp/tutorials">eugenp</a> | ||
//Reused from https://www.baeldung.com/trie-java with minor modifications | ||
/** | ||
* Delete a word from the Trie. | ||
* @author <a href="https://github.com/eugenp/tutorials">eugenp</a> | ||
* @param word the word to be deleted | ||
*/ | ||
public void delete(String word) { | ||
delete(root, word, 0); | ||
} | ||
//@@author | ||
|
||
//@@author <a href="https://github.com/eugenp/tutorials">eugenp</a> | ||
//Reused from https://www.baeldung.com/trie-java with minor modifications | ||
private boolean delete(TrieNode current, String word, int index) { | ||
if (index == word.length()) { | ||
if (!current.isEndOfWord()) { | ||
return false; | ||
} | ||
current.setEndOfWord(false); | ||
return current.getChildren().isEmpty(); | ||
} | ||
|
||
char c = word.charAt(index); | ||
TrieNode node = current.getChild(c); | ||
if (node == null) { | ||
return false; | ||
} | ||
|
||
boolean shouldDeleteCurrentNode = delete(node, word, index + 1) && !node.isEndOfWord(); | ||
if (shouldDeleteCurrentNode) { | ||
current.deleteChild(c); | ||
return current.getChildren().isEmpty(); | ||
} | ||
|
||
return false; | ||
} | ||
//@@author | ||
|
||
//@@author <a href="https://github.com/eugenp/tutorials">eugenp</a> | ||
//Reused from https://www.baeldung.com/trie-java with minor modifications | ||
/** | ||
* Search for a word in the Trie. | ||
* @author <a href="https://github.com/eugenp/tutorials">eugenp</a> | ||
* @param word the word to be searched | ||
* @return true if the word is found, false otherwise | ||
*/ | ||
public boolean search(String word) { | ||
TrieNode current = root; | ||
for (char c : word.toCharArray()) { | ||
if (current.getChild(c) == null) { | ||
return false; | ||
} | ||
current = current.getChild(c); | ||
} | ||
return current.isEndOfWord(); | ||
} | ||
//@@author | ||
|
||
/** | ||
* Find the first word in the Trie that starts with the given prefix. | ||
* @param prefix the prefix to be searched | ||
* @return the first word that starts with the given prefix. If no such word exists, return null. | ||
*/ | ||
public String findFirstWordWithPrefix(String prefix) { | ||
TrieNode current = root; | ||
StringBuilder sb = new StringBuilder(); | ||
for (char c : prefix.toCharArray()) { | ||
if (current.getChild(c) == null) { | ||
return null; | ||
} | ||
sb.append(c); | ||
current = current.getChild(c); | ||
} | ||
return findFirstWordWithPrefixHelper(current, sb); | ||
} | ||
|
||
private String findFirstWordWithPrefixHelper(TrieNode current, StringBuilder sb) { | ||
if (current.isEndOfWord()) { | ||
return sb.toString(); | ||
} | ||
for (char c : current.getChildren().keySet()) { | ||
sb.append(c); | ||
return findFirstWordWithPrefixHelper(current.getChild(c), sb); | ||
} | ||
return null; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
package seedu.address.commons.util; | ||
|
||
import java.util.HashMap; | ||
|
||
/** | ||
* A class that represents a TrieNode data structure. | ||
*/ | ||
public class TrieNode { | ||
private final HashMap<Character, TrieNode> children; | ||
private final Character character; | ||
private boolean isEndOfWord; | ||
|
||
/** | ||
* Constructor for TrieNode. | ||
*/ | ||
public TrieNode(Character c) { | ||
this.children = new HashMap<>(); | ||
this.character = c; | ||
this.isEndOfWord = false; | ||
} | ||
|
||
/** | ||
* Retrieve the child node of the given character. | ||
* @param c the character of the child TrieNode to be retrieved | ||
* @return the child TrieNode of the given character | ||
*/ | ||
public TrieNode getChild(char c) { | ||
return children.get(c); | ||
} | ||
|
||
/** | ||
* Retrieve all children | ||
* @return the children of the current TrieNode | ||
*/ | ||
public HashMap<Character, TrieNode> getChildren() { | ||
return children; | ||
} | ||
|
||
/** | ||
* Set a TrieNode as a child of the current TrieNode. | ||
* @param c the character of the child TrieNode | ||
*/ | ||
public void setChild(char c) { | ||
this.children.put(c, new TrieNode(c)); | ||
} | ||
|
||
/** | ||
* Delete a child TrieNode. | ||
* @param c the character of the child TrieNode | ||
*/ | ||
public void deleteChild(char c) { | ||
this.children.remove(c); | ||
} | ||
|
||
/** | ||
* Set end of word. | ||
*/ | ||
public void setEndOfWord(boolean isEndOfWord) { | ||
this.isEndOfWord = isEndOfWord; | ||
} | ||
|
||
/** | ||
* Retrieve the end of word status. | ||
*/ | ||
public boolean isEndOfWord() { | ||
return isEndOfWord; | ||
} | ||
|
||
@Override | ||
public boolean equals(Object other) { | ||
if (other == this) { | ||
return true; | ||
} | ||
|
||
if (!(other instanceof TrieNode)) { | ||
return false; | ||
} | ||
|
||
TrieNode otherTrieNode = (TrieNode) other; | ||
return otherTrieNode.character.equals(this.character) | ||
&& otherTrieNode.isEndOfWord == this.isEndOfWord; | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return character.hashCode(); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return String.format("TrieNode: %s%s", | ||
this.character, | ||
this.isEndOfWord ? "\0" : "" | ||
); | ||
} | ||
} |
73 changes: 73 additions & 0 deletions
73
src/test/java/seedu/address/commons/util/TrieNodeTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
package seedu.address.commons.util; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||
import static org.junit.jupiter.api.Assertions.assertNotEquals; | ||
import static org.junit.jupiter.api.Assertions.assertNotNull; | ||
import static org.junit.jupiter.api.Assertions.assertNull; | ||
|
||
import org.junit.jupiter.api.Test; | ||
|
||
class TrieNodeTest { | ||
|
||
@Test | ||
void getChild() { | ||
TrieNode trieNode = new TrieNode('a'); | ||
assertNull(trieNode.getChild('b')); | ||
|
||
trieNode.setChild('b'); | ||
assertNotNull(trieNode.getChild('b')); | ||
|
||
trieNode.setChild('c'); | ||
assertNotNull(trieNode.getChild('c')); | ||
|
||
trieNode.deleteChild('b'); | ||
assertNull(trieNode.getChild('b')); | ||
} | ||
|
||
@Test | ||
void getChildren() { | ||
TrieNode trieNode = new TrieNode('a'); | ||
assertNotNull(trieNode.getChildren()); | ||
|
||
assert(trieNode.getChildren().isEmpty()); | ||
|
||
trieNode.setChild('b'); | ||
trieNode.setChild('c'); | ||
assertEquals(2, trieNode.getChildren().size()); | ||
|
||
trieNode.deleteChild('b'); | ||
assertEquals(1, trieNode.getChildren().size()); | ||
} | ||
@Test | ||
void testEquals() { | ||
TrieNode trieNode = new TrieNode('a'); | ||
|
||
assertEquals(trieNode, trieNode); | ||
assertNotEquals(trieNode, null); | ||
assertNotEquals(trieNode, "a"); | ||
|
||
TrieNode trieNode2 = new TrieNode('a'); | ||
|
||
assertEquals(trieNode, trieNode2); | ||
|
||
trieNode.setEndOfWord(true); | ||
assertNotEquals(trieNode, trieNode2); | ||
} | ||
|
||
@Test | ||
void testHashCode() { | ||
TrieNode trieNode = new TrieNode('a'); | ||
TrieNode trieNode2 = new TrieNode('a'); | ||
|
||
assertEquals(trieNode.hashCode(), trieNode2.hashCode()); | ||
} | ||
|
||
@Test | ||
void testToString() { | ||
TrieNode trieNode = new TrieNode('a'); | ||
assertEquals("TrieNode: a", trieNode.toString()); | ||
|
||
trieNode.setEndOfWord(true); | ||
assertEquals("TrieNode: a\0", trieNode.toString()); | ||
} | ||
} |
Oops, something went wrong.