Skip to content

Commit 42badbf

Browse files
Implemented Trie Data Structure (#162)
* Added Disjoint Sets Data structure * Moved DisjointSetTest.php to tests/DataStructures * Update DataStructures/DisjointSets/DisjointSet.php Co-authored-by: Brandon Johnson <[email protected]> * Update DataStructures/DisjointSets/DisjointSetNode.php Co-authored-by: Brandon Johnson <[email protected]> * Update DataStructures/DisjointSets/DisjointSetNode.php Co-authored-by: Brandon Johnson <[email protected]> * Update tests/DataStructures/DisjointSetTest.php Co-authored-by: Brandon Johnson <[email protected]> * Update tests/DataStructures/DisjointSetTest.php Co-authored-by: Brandon Johnson <[email protected]> * Update tests/DataStructures/DisjointSetTest.php Co-authored-by: Brandon Johnson <[email protected]> * Considered PHPCS remarks. Unit Testing is now working. * Remove data type mixed. Considered annotations for php7.4. * Remove data type mixed. Considered annotations for php7.4. * updating DIRECTORY.md * Implemented Trie DataStructure * Added Trie to DIRECTORY.md --------- Co-authored-by: Brandon Johnson <[email protected]> Co-authored-by: Ramy-Badr-Ahmed <[email protected]>
1 parent 193d032 commit 42badbf

File tree

4 files changed

+347
-0
lines changed

4 files changed

+347
-0
lines changed

DIRECTORY.md

+4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
* Disjointsets
2121
* [Disjointset](./DataStructures/DisjointSets/DisjointSet.php)
2222
* [Disjointsetnode](./DataStructures/DisjointSets/DisjointSetNode.php)
23+
* Trie
24+
* [Trie](./DataStructures/Trie/Trie.php)
25+
* [TrieNode](./DataStructures/Trie/TrieNode.php)
2326
* [Doublylinkedlist](./DataStructures/DoublyLinkedList.php)
2427
* [Node](./DataStructures/Node.php)
2528
* [Queue](./DataStructures/Queue.php)
@@ -115,6 +118,7 @@
115118
* [Conversionstest](./tests/Conversions/ConversionsTest.php)
116119
* Datastructures
117120
* [Disjointsettest](./tests/DataStructures/DisjointSetTest.php)
121+
* [Trie](./tests/DataStructures/TrieTest.php)
118122
* [Doublylinkedlisttest](./tests/DataStructures/DoublyLinkedListTest.php)
119123
* [Queuetest](./tests/DataStructures/QueueTest.php)
120124
* [Singlylinkedlisttest](./tests/DataStructures/SinglyLinkedListTest.php)

DataStructures/Trie/Trie.php

+163
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
<?php
2+
3+
namespace DataStructures\Trie;
4+
5+
class Trie
6+
{
7+
private TrieNode $root;
8+
9+
public function __construct()
10+
{
11+
$this->root = new TrieNode();
12+
}
13+
14+
/**
15+
* Get the root node of the Trie.
16+
*/
17+
public function getRoot(): TrieNode
18+
{
19+
return $this->root;
20+
}
21+
22+
/**
23+
* Insert a word into the Trie.
24+
*/
25+
public function insert(string $word): void
26+
{
27+
$node = $this->root;
28+
for ($i = 0; $i < strlen($word); $i++) {
29+
$char = $word[$i];
30+
$node = $node->addChild($char);
31+
}
32+
$node->isEndOfWord = true;
33+
}
34+
35+
/**
36+
* Search for a word in the Trie.
37+
*/
38+
public function search(string $word): bool
39+
{
40+
$node = $this->root;
41+
for ($i = 0; $i < strlen($word); $i++) {
42+
$char = $word[$i];
43+
if (!$node->hasChild($char)) {
44+
return false;
45+
}
46+
$node = $node->getChild($char);
47+
}
48+
return $node->isEndOfWord;
49+
}
50+
51+
/**
52+
* Find all words that start with a given prefix.
53+
*/
54+
public function startsWith(string $prefix): array
55+
{
56+
$node = $this->root;
57+
for ($i = 0; $i < strlen($prefix); $i++) {
58+
$char = $prefix[$i];
59+
if (!$node->hasChild($char)) {
60+
return [];
61+
}
62+
$node = $node->getChild($char);
63+
}
64+
return $this->findWordsFromNode($node, $prefix);
65+
}
66+
67+
/**
68+
* Helper function to find all words from a given node.
69+
*/
70+
private function findWordsFromNode(TrieNode $node, string $prefix): array
71+
{
72+
$words = [];
73+
if ($node->isEndOfWord) {
74+
$words[] = $prefix;
75+
}
76+
77+
foreach ($node->children as $char => $childNode) {
78+
$words = array_merge($words, $this->findWordsFromNode($childNode, $prefix . $char));
79+
}
80+
81+
return $words;
82+
}
83+
84+
/**
85+
* Delete a word from the Trie.
86+
* Recursively traverses the Trie and removes nodes
87+
*
88+
* @param string $word The word to delete.
89+
* @return bool Returns true if the word was successfully deleted, otherwise false.
90+
*/
91+
public function delete(string $word): bool
92+
{
93+
return $this->deleteHelper($this->root, $word, 0);
94+
}
95+
96+
/**
97+
* Helper function for deleting a word.
98+
* Recursively traverse the Trie and removes nodes.
99+
*
100+
* @param TrieNode $node The current node in the Trie.
101+
* @param string $word The word being deleted.
102+
* @param int $index The current index in the word.
103+
* @return bool Returns true if the current node should be deleted, otherwise false.
104+
*/
105+
private function deleteHelper(TrieNode $node, string &$word, int $index): bool
106+
{
107+
if ($index === strlen($word)) {
108+
if (!$node->isEndOfWord) {
109+
return false;
110+
}
111+
$node->isEndOfWord = false;
112+
return empty($node->children);
113+
}
114+
115+
$char = $word[$index];
116+
$childNode = $node->getChild($char);
117+
if ($childNode === null) {
118+
return false;
119+
}
120+
121+
// Recursively delete the child node
122+
$shouldDeleteCurrentNode = $this->deleteHelper($childNode, $word, $index + 1);
123+
124+
if ($shouldDeleteCurrentNode) {
125+
unset($node->children[$char]);
126+
return !$node->isEndOfWord; // true if current node is not the end of another word
127+
}
128+
129+
return false;
130+
}
131+
132+
/**
133+
* Recursively traverses the Trie starting from the given node and collects all words.
134+
*
135+
* @param TrieNode $node The starting node for traversal.
136+
* @param string $prefix The prefix of the current path in the Trie.
137+
* @return array An array of words found in the Trie starting from the given node.
138+
*/
139+
public function traverseTrieNode(TrieNode $node, string $prefix = ''): array
140+
{
141+
$words = [];
142+
143+
if ($node->isEndOfWord) {
144+
$words[] = $prefix;
145+
}
146+
147+
foreach ($node->children as $char => $childNode) {
148+
$words = array_merge($words, $this->traverseTrieNode($childNode, $prefix . $char));
149+
}
150+
151+
return $words;
152+
}
153+
154+
/**
155+
* Gets all words stored in the Trie.
156+
*
157+
* @return array An array of all words in the Trie.
158+
*/
159+
public function getWords(): array
160+
{
161+
return $this->traverseTrieNode($this->root);
162+
}
163+
}

DataStructures/Trie/TrieNode.php

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
namespace DataStructures\Trie;
4+
5+
class TrieNode
6+
{
7+
/** @var array<string, TrieNode> */
8+
public array $children;
9+
public bool $isEndOfWord;
10+
11+
public function __construct()
12+
{
13+
$this->children = []; // Associative array where [ char => TrieNode ]
14+
$this->isEndOfWord = false;
15+
}
16+
17+
/**
18+
* Add a child node for a character.
19+
*/
20+
public function addChild(string $char): TrieNode
21+
{
22+
if (!isset($this->children[$char])) {
23+
$this->children[$char] = new TrieNode();
24+
}
25+
return $this->children[$char];
26+
}
27+
28+
/**
29+
* Check if a character has a child node.
30+
*/
31+
public function hasChild(string $char): bool
32+
{
33+
return isset($this->children[$char]);
34+
}
35+
36+
/**
37+
* Get the child node corresponding to a character.
38+
*/
39+
public function getChild(string $char): ?TrieNode
40+
{
41+
return $this->children[$char] ?? null;
42+
}
43+
}

tests/DataStructures/TrieTest.php

+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
<?php
2+
3+
namespace DataStructures;
4+
5+
require_once __DIR__ . '/../../DataStructures/Trie/Trie.php';
6+
require_once __DIR__ . '/../../DataStructures/Trie/TrieNode.php';
7+
8+
use DataStructures\Trie\Trie;
9+
use PHPUnit\Framework\TestCase;
10+
11+
class TrieTest extends TestCase
12+
{
13+
private Trie $trie;
14+
15+
protected function setUp(): void
16+
{
17+
$this->trie = new Trie();
18+
}
19+
20+
public function testInsertAndSearch()
21+
{
22+
$this->trie->insert('the');
23+
$this->trie->insert('universe');
24+
$this->trie->insert('is');
25+
$this->trie->insert('vast');
26+
27+
$this->assertTrue($this->trie->search('the'), 'Expected "the" to be found in the Trie.');
28+
$this->assertTrue($this->trie->search('universe'), 'Expected "universe" to be found in the Trie.');
29+
$this->assertTrue($this->trie->search('is'), 'Expected "is" to be found in the Trie.');
30+
$this->assertTrue($this->trie->search('vast'), 'Expected "vast" to be found in the Trie.');
31+
$this->assertFalse(
32+
$this->trie->search('the universe'),
33+
'Expected "the universe" not to be found in the Trie.'
34+
);
35+
}
36+
37+
public function testStartsWith()
38+
{
39+
$this->trie->insert('hello');
40+
$this->assertEquals(['hello'], $this->trie->startsWith('he'), 'Expected words starting with "he" to be found.');
41+
$this->assertEquals(
42+
['hello'],
43+
$this->trie->startsWith('hello'),
44+
'Expected words starting with "hello" to be found.'
45+
);
46+
$this->assertEquals(
47+
[],
48+
$this->trie->startsWith('world'),
49+
'Expected no words starting with "world" to be found.'
50+
);
51+
}
52+
53+
public function testDelete()
54+
{
55+
// Insert words into the Trie
56+
$this->trie->insert('the');
57+
$this->trie->insert('universe');
58+
$this->trie->insert('is');
59+
$this->trie->insert('vast');
60+
$this->trie->insert('big');
61+
$this->trie->insert('rather');
62+
63+
// Test deleting an existing word
64+
$this->trie->delete('the');
65+
$this->assertFalse($this->trie->search('the'), 'Expected "the" not to be found after deletion.');
66+
67+
// Test that other words are still present
68+
$this->assertTrue($this->trie->search('universe'), 'Expected "universe" to be found.');
69+
$this->assertTrue($this->trie->search('is'), 'Expected "is" to be found.');
70+
$this->assertTrue($this->trie->search('vast'), 'Expected "vast" to be found.');
71+
$this->assertTrue($this->trie->search('big'), 'Expected "big" to be found.');
72+
$this->assertTrue($this->trie->search('rather'), 'Expected "rather" to be found.');
73+
}
74+
75+
public function testDeleteNonExistentWord()
76+
{
77+
$this->trie->delete('nonexistent');
78+
$this->assertFalse($this->trie->search('nonexistent'), 'Expected "nonexistent" to not be found.');
79+
}
80+
81+
public function testTraverseTrieNode()
82+
{
83+
$this->trie->insert('hello');
84+
$this->trie->insert('helium');
85+
$this->trie->insert('helicopter');
86+
87+
$words = $this->trie->getWords();
88+
$this->assertContains('hello', $words, 'Expected "hello" to be found in the Trie.');
89+
$this->assertContains('helium', $words, 'Expected "helium" to be found in the Trie.');
90+
$this->assertContains('helicopter', $words, 'Expected "helicopter" to be found in the Trie.');
91+
$this->assertCount(3, $words, 'Expected 3 words in the Trie.');
92+
}
93+
94+
public function testEmptyTrie()
95+
{
96+
$this->assertEquals([], $this->trie->getWords(), 'Expected an empty Trie to return an empty array.');
97+
}
98+
99+
public function testGetWords()
100+
{
101+
$this->trie->insert('apple');
102+
$this->trie->insert('app');
103+
$this->trie->insert('applet');
104+
105+
$words = $this->trie->getWords();
106+
$this->assertContains('apple', $words, 'Expected "apple" to be found in the Trie.');
107+
$this->assertContains('app', $words, 'Expected "app" to be found in the Trie.');
108+
$this->assertContains('applet', $words, 'Expected "applet" to be found in the Trie.');
109+
$this->assertCount(3, $words, 'Expected 3 words in the Trie.');
110+
}
111+
112+
public function testInsertEmptyString()
113+
{
114+
$this->trie->insert('');
115+
$this->assertTrue($this->trie->search(''), 'Expected empty string to be found in the Trie.');
116+
}
117+
118+
public function testDeleteEmptyString()
119+
{
120+
$this->trie->insert('');
121+
$this->trie->delete('');
122+
$this->assertFalse($this->trie->search(''), 'Expected empty string not to be found after deletion.');
123+
}
124+
125+
public function testStartsWithWithCommonPrefix()
126+
{
127+
$this->trie->insert('trie');
128+
$this->trie->insert('tried');
129+
$this->trie->insert('trier');
130+
131+
$words = $this->trie->startsWith('tri');
132+
$this->assertContains('trie', $words, 'Expected "trie" to be found with prefix "tri".');
133+
$this->assertContains('tried', $words, 'Expected "tried" to be found with prefix "tri".');
134+
$this->assertContains('trier', $words, 'Expected "trier" to be found with prefix "tri".');
135+
$this->assertCount(3, $words, 'Expected 3 words with prefix "tri".');
136+
}
137+
}

0 commit comments

Comments
 (0)