Skip to content

Commit

Permalink
Merge pull request #60 from ForAeons/trie-datastructure
Browse files Browse the repository at this point in the history
feat: add trie data structure for autocomplete feature
  • Loading branch information
Anant1902 authored Mar 19, 2024
2 parents 69bbfe5 + fccdf65 commit 6f3f15d
Show file tree
Hide file tree
Showing 5 changed files with 408 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/DeveloperGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

## **Acknowledgements**

* Trie implementation is reused from [eugenp's tutorials](https://github.com/eugenp/tutorials) with minor modifications.
_{ list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well }_

--------------------------------------------------------------------------------------------------------------------
Expand Down
126 changes: 126 additions & 0 deletions src/main/java/seedu/address/commons/util/Trie.java
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;
}
}
96 changes: 96 additions & 0 deletions src/main/java/seedu/address/commons/util/TrieNode.java
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 src/test/java/seedu/address/commons/util/TrieNodeTest.java
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());
}
}
Loading

0 comments on commit 6f3f15d

Please sign in to comment.