From 643dfa2347772c2ddc2b6fb05fe4b91cecd1e329 Mon Sep 17 00:00:00 2001 From: Ramy-Badr-Ahmed <126559907+Ramy-Badr-Ahmed@users.noreply.github.com> Date: Sat, 24 Aug 2024 21:17:19 +0200 Subject: [PATCH 01/38] Added Disjoint Sets Data structure --- DIRECTORY.md | 1 + DataStructures/DisjointSets/DisjointSet.php | 40 +++++++++ .../DisjointSets/DisjointSetNode.php | 16 ++++ .../DisjointSets/DisjointSetTest.php | 82 +++++++++++++++++++ 4 files changed, 139 insertions(+) create mode 100644 DataStructures/DisjointSets/DisjointSet.php create mode 100644 DataStructures/DisjointSets/DisjointSetNode.php create mode 100644 DataStructures/DisjointSets/DisjointSetTest.php diff --git a/DIRECTORY.md b/DIRECTORY.md index 53f60033..f33f9f64 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -22,6 +22,7 @@ * [Queue](./DataStructures/Queue.php) * [Singlylinkedlist](./DataStructures/SinglyLinkedList.php) * [Stack](./DataStructures/Stack.php) + * [DisjointSets](./DataStructures/DisjointSets/DisjointSet.php) ## Graphs * [Bellmanford](./Graphs/BellmanFord.php) diff --git a/DataStructures/DisjointSets/DisjointSet.php b/DataStructures/DisjointSets/DisjointSet.php new file mode 100644 index 00000000..2bfa3d46 --- /dev/null +++ b/DataStructures/DisjointSets/DisjointSet.php @@ -0,0 +1,40 @@ +parent) { + $node->parent = $this->findSet($node->parent); // Path compression: make the parent point directly to the root + } + return $node->parent; + } + + /** + * Unites the sets that contain x and y. + */ + public function unionSet(DisjointSetNode $nodeX, DisjointSetNode $nodeY): void + { + $rootX = $this->findSet($nodeX); + $rootY = $this->findSet($nodeY); + + if ($rootX === $rootY) { + return; // They are already in the same set + } + + // Union by rank: attach the smaller tree under the larger tree + if ($rootX->rank > $rootY->rank) { + $rootY->parent = $rootX; + } else { + $rootX->parent = $rootY; + if ($rootX->rank === $rootY->rank) { + $rootY->rank += 1; + } + } + } +} \ No newline at end of file diff --git a/DataStructures/DisjointSets/DisjointSetNode.php b/DataStructures/DisjointSets/DisjointSetNode.php new file mode 100644 index 00000000..2e628707 --- /dev/null +++ b/DataStructures/DisjointSets/DisjointSetNode.php @@ -0,0 +1,16 @@ +data = $data; + $this->rank = 0; + $this->parent = $this; // Initialize parent to itself + } +} \ No newline at end of file diff --git a/DataStructures/DisjointSets/DisjointSetTest.php b/DataStructures/DisjointSets/DisjointSetTest.php new file mode 100644 index 00000000..ed33e9f9 --- /dev/null +++ b/DataStructures/DisjointSets/DisjointSetTest.php @@ -0,0 +1,82 @@ +ds = new DisjointSet(); + $this->nodes = []; + + // Create 20 nodes + for ($i = 0; $i < 20; $i++) { + $this->nodes[$i] = new DisjointSetNode($i); + } + + // Perform union operations to form several disjoint sets + $this->ds->unionSet($this->nodes[0], $this->nodes[1]); + $this->ds->unionSet($this->nodes[1], $this->nodes[2]); + + $this->ds->unionSet($this->nodes[3], $this->nodes[4]); + $this->ds->unionSet($this->nodes[4], $this->nodes[5]); + + $this->ds->unionSet($this->nodes[6], $this->nodes[7]); + $this->ds->unionSet($this->nodes[7], $this->nodes[8]); + + $this->ds->unionSet($this->nodes[9], $this->nodes[10]); + $this->ds->unionSet($this->nodes[10], $this->nodes[11]); + + $this->ds->unionSet($this->nodes[12], $this->nodes[13]); + $this->ds->unionSet($this->nodes[13], $this->nodes[14]); + + $this->ds->unionSet($this->nodes[15], $this->nodes[16]); + $this->ds->unionSet($this->nodes[16], $this->nodes[17]); + + $this->ds->unionSet($this->nodes[18], $this->nodes[19]); + + } + + public function testFindSet(): void + { + // Nodes in the same sets should have the same root + for ($i = 0; $i < 6; $i++) { + for ($j = 0; $j < 6; $j++) { + $setI = $this->ds->findSet($this->nodes[$i]); + $setJ = $this->ds->findSet($this->nodes[$j]); + + if ($this->inSameSet($i, $j)) { + $this->assertSame($setI, $setJ, "Nodes $i and $j should be in the same set"); + } else { + $this->assertNotSame($setI, $setJ, "Nodes $i and $j should be in different sets"); + } + } + } + } + + private function inSameSet(int $i, int $j): bool + { + // Define which nodes should be in the same set based on union operations + $sets = [ + [0, 1, 2], // Set A + [3, 4, 5], // Set B + [6, 7, 8], // Set C + [9, 10, 11], // Set D + [12, 13, 14], // Set E + [15, 16, 17], // Set F + [18, 19] // Set G + ]; + + foreach ($sets as $set) { + if (in_array($i, $set) && in_array($j, $set)) + return true; + } + + return false; + } + +} From 7cfc8592190d0798ecd8ebf7c337d4c4dc3f681b Mon Sep 17 00:00:00 2001 From: Ramy-Badr-Ahmed Date: Sun, 8 Sep 2024 16:55:11 +0200 Subject: [PATCH 02/38] Moved DisjointSetTest.php to tests/DataStructures --- .../DisjointSets => tests/DataStructures}/DisjointSetTest.php | 3 +++ 1 file changed, 3 insertions(+) rename {DataStructures/DisjointSets => tests/DataStructures}/DisjointSetTest.php (98%) diff --git a/DataStructures/DisjointSets/DisjointSetTest.php b/tests/DataStructures/DisjointSetTest.php similarity index 98% rename from DataStructures/DisjointSets/DisjointSetTest.php rename to tests/DataStructures/DisjointSetTest.php index ed33e9f9..efcaaf9f 100644 --- a/DataStructures/DisjointSets/DisjointSetTest.php +++ b/tests/DataStructures/DisjointSetTest.php @@ -1,6 +1,9 @@ Date: Wed, 11 Sep 2024 13:02:14 +0200 Subject: [PATCH 03/38] Update DataStructures/DisjointSets/DisjointSet.php Co-authored-by: Brandon Johnson --- DataStructures/DisjointSets/DisjointSet.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DataStructures/DisjointSets/DisjointSet.php b/DataStructures/DisjointSets/DisjointSet.php index 2bfa3d46..af7fdb88 100644 --- a/DataStructures/DisjointSets/DisjointSet.php +++ b/DataStructures/DisjointSets/DisjointSet.php @@ -37,4 +37,4 @@ public function unionSet(DisjointSetNode $nodeX, DisjointSetNode $nodeY): void } } } -} \ No newline at end of file +} From b6ae1036ba83d0b938b3cd195939f75ac39efa30 Mon Sep 17 00:00:00 2001 From: Ramy <126559907+Ramy-Badr-Ahmed@users.noreply.github.com> Date: Wed, 11 Sep 2024 13:02:33 +0200 Subject: [PATCH 04/38] Update DataStructures/DisjointSets/DisjointSetNode.php Co-authored-by: Brandon Johnson --- DataStructures/DisjointSets/DisjointSetNode.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/DataStructures/DisjointSets/DisjointSetNode.php b/DataStructures/DisjointSets/DisjointSetNode.php index 2e628707..d424b679 100644 --- a/DataStructures/DisjointSets/DisjointSetNode.php +++ b/DataStructures/DisjointSets/DisjointSetNode.php @@ -8,7 +8,8 @@ class DisjointSetNode public int $rank; public DisjointSetNode $parent; - public function __construct(mixed $data = NULL) { + public function __construct(mixed $data = null) + { $this->data = $data; $this->rank = 0; $this->parent = $this; // Initialize parent to itself From 70d981a1b53618dbc21359033c6343eb6e84d05a Mon Sep 17 00:00:00 2001 From: Ramy <126559907+Ramy-Badr-Ahmed@users.noreply.github.com> Date: Wed, 11 Sep 2024 13:02:42 +0200 Subject: [PATCH 05/38] Update DataStructures/DisjointSets/DisjointSetNode.php Co-authored-by: Brandon Johnson --- DataStructures/DisjointSets/DisjointSetNode.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DataStructures/DisjointSets/DisjointSetNode.php b/DataStructures/DisjointSets/DisjointSetNode.php index d424b679..78a94751 100644 --- a/DataStructures/DisjointSets/DisjointSetNode.php +++ b/DataStructures/DisjointSets/DisjointSetNode.php @@ -14,4 +14,4 @@ public function __construct(mixed $data = null) $this->rank = 0; $this->parent = $this; // Initialize parent to itself } -} \ No newline at end of file +} From dc93e41fc9a7ab061da13ec54af7846e1b737551 Mon Sep 17 00:00:00 2001 From: Ramy <126559907+Ramy-Badr-Ahmed@users.noreply.github.com> Date: Wed, 11 Sep 2024 13:02:56 +0200 Subject: [PATCH 06/38] Update tests/DataStructures/DisjointSetTest.php Co-authored-by: Brandon Johnson --- tests/DataStructures/DisjointSetTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/DataStructures/DisjointSetTest.php b/tests/DataStructures/DisjointSetTest.php index efcaaf9f..e1848896 100644 --- a/tests/DataStructures/DisjointSetTest.php +++ b/tests/DataStructures/DisjointSetTest.php @@ -41,7 +41,6 @@ protected function setUp(): void $this->ds->unionSet($this->nodes[16], $this->nodes[17]); $this->ds->unionSet($this->nodes[18], $this->nodes[19]); - } public function testFindSet(): void From 35aa7d07e95bce416b52b93de5e03a77c4034701 Mon Sep 17 00:00:00 2001 From: Ramy <126559907+Ramy-Badr-Ahmed@users.noreply.github.com> Date: Wed, 11 Sep 2024 13:03:06 +0200 Subject: [PATCH 07/38] Update tests/DataStructures/DisjointSetTest.php Co-authored-by: Brandon Johnson --- tests/DataStructures/DisjointSetTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/DataStructures/DisjointSetTest.php b/tests/DataStructures/DisjointSetTest.php index e1848896..1d4787b7 100644 --- a/tests/DataStructures/DisjointSetTest.php +++ b/tests/DataStructures/DisjointSetTest.php @@ -80,5 +80,5 @@ private function inSameSet(int $i, int $j): bool return false; } - } + From 0591a6fd3da1e85cfca1ff55b28f99629ca59b64 Mon Sep 17 00:00:00 2001 From: Ramy <126559907+Ramy-Badr-Ahmed@users.noreply.github.com> Date: Wed, 11 Sep 2024 13:03:14 +0200 Subject: [PATCH 08/38] Update tests/DataStructures/DisjointSetTest.php Co-authored-by: Brandon Johnson --- tests/DataStructures/DisjointSetTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/DataStructures/DisjointSetTest.php b/tests/DataStructures/DisjointSetTest.php index 1d4787b7..0bc59d15 100644 --- a/tests/DataStructures/DisjointSetTest.php +++ b/tests/DataStructures/DisjointSetTest.php @@ -74,8 +74,9 @@ private function inSameSet(int $i, int $j): bool ]; foreach ($sets as $set) { - if (in_array($i, $set) && in_array($j, $set)) + if (in_array($i, $set) && in_array($j, $set)) { return true; + } } return false; From b0086fd6046215b70fb0101bf40c99a2d46e2b7a Mon Sep 17 00:00:00 2001 From: Ramy-Badr-Ahmed Date: Wed, 11 Sep 2024 13:45:24 +0200 Subject: [PATCH 09/38] Considered PHPCS remarks. Unit Testing is now working. --- DataStructures/DisjointSets/DisjointSet.php | 5 +++-- DataStructures/DisjointSets/DisjointSetNode.php | 2 +- composer.json | 3 ++- tests/DataStructures/DisjointSetTest.php | 8 +++++--- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/DataStructures/DisjointSets/DisjointSet.php b/DataStructures/DisjointSets/DisjointSet.php index af7fdb88..4212ece1 100644 --- a/DataStructures/DisjointSets/DisjointSet.php +++ b/DataStructures/DisjointSets/DisjointSet.php @@ -1,6 +1,6 @@ parent) { - $node->parent = $this->findSet($node->parent); // Path compression: make the parent point directly to the root + // Path compression: make the parent point directly to the root + $node->parent = $this->findSet($node->parent); } return $node->parent; } diff --git a/DataStructures/DisjointSets/DisjointSetNode.php b/DataStructures/DisjointSets/DisjointSetNode.php index 78a94751..4ad01d63 100644 --- a/DataStructures/DisjointSets/DisjointSetNode.php +++ b/DataStructures/DisjointSets/DisjointSetNode.php @@ -8,7 +8,7 @@ class DisjointSetNode public int $rank; public DisjointSetNode $parent; - public function __construct(mixed $data = null) + public function __construct(mixed $data = null) { $this->data = $data; $this->rank = 0; diff --git a/composer.json b/composer.json index 8cc0cbd0..534b9823 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name" : "thealgorithms/php", + "name": "thealgorithms/php", "description": "All Algorithms implemented in PHP", "config": { "platform": { @@ -19,3 +19,4 @@ "test": "vendor/bin/phpunit tests" } } + diff --git a/tests/DataStructures/DisjointSetTest.php b/tests/DataStructures/DisjointSetTest.php index 0bc59d15..0a52dea7 100644 --- a/tests/DataStructures/DisjointSetTest.php +++ b/tests/DataStructures/DisjointSetTest.php @@ -2,8 +2,11 @@ namespace DataStructures; +require_once __DIR__ . '/../../DataStructures/DisjointSets/DisjointSet.php'; +require_once __DIR__ . '/../../DataStructures/DisjointSets/DisjointSetNode.php'; + +use DataStructures\DisjointSets\DisjointSet; use DataStructures\DisjointSets\DisjointSetNode; -use DisjointSet; use PHPUnit\Framework\TestCase; class DisjointSetTest extends TestCase @@ -76,10 +79,9 @@ private function inSameSet(int $i, int $j): bool foreach ($sets as $set) { if (in_array($i, $set) && in_array($j, $set)) { return true; - } + } } return false; } } - From b2d1160e597997aa143d51728f87d3f62d069a70 Mon Sep 17 00:00:00 2001 From: Ramy-Badr-Ahmed Date: Wed, 11 Sep 2024 17:52:25 +0200 Subject: [PATCH 10/38] Remove data type mixed. Considered annotations for php7.4. --- DataStructures/DisjointSets/DisjointSetNode.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/DataStructures/DisjointSets/DisjointSetNode.php b/DataStructures/DisjointSets/DisjointSetNode.php index 4ad01d63..e91dd7bd 100644 --- a/DataStructures/DisjointSets/DisjointSetNode.php +++ b/DataStructures/DisjointSets/DisjointSetNode.php @@ -4,11 +4,15 @@ class DisjointSetNode { - public mixed $data; # PHP 8.0^ . Otherwise, define with annotations: @var int|string|float|null + /** + * @var int|string|float|null + */ + // PHP7.4: Defined with annotations + public $data; # replace with type hint "mixed" as for PHP 8.0^. public int $rank; public DisjointSetNode $parent; - public function __construct(mixed $data = null) + public function __construct($data = null) { $this->data = $data; $this->rank = 0; From 926126a9f85dd822ca47d14db968a2c15e32b529 Mon Sep 17 00:00:00 2001 From: Ramy-Badr-Ahmed Date: Wed, 11 Sep 2024 18:45:34 +0200 Subject: [PATCH 11/38] Remove data type mixed. Considered annotations for php7.4. --- DataStructures/DisjointSets/DisjointSetNode.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DataStructures/DisjointSets/DisjointSetNode.php b/DataStructures/DisjointSets/DisjointSetNode.php index e91dd7bd..ed9d7556 100644 --- a/DataStructures/DisjointSets/DisjointSetNode.php +++ b/DataStructures/DisjointSets/DisjointSetNode.php @@ -8,7 +8,7 @@ class DisjointSetNode * @var int|string|float|null */ // PHP7.4: Defined with annotations - public $data; # replace with type hint "mixed" as for PHP 8.0^. + public $data; # replace with type hint "mixed" as of PHP 8.0^. public int $rank; public DisjointSetNode $parent; From 355c5baa3b1e745a3f4a0b331c3adfaacbead646 Mon Sep 17 00:00:00 2001 From: Ramy-Badr-Ahmed Date: Wed, 11 Sep 2024 16:45:52 +0000 Subject: [PATCH 12/38] updating DIRECTORY.md --- DIRECTORY.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index f33f9f64..9e567459 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -17,12 +17,14 @@ * [Speedconversion](./Conversions/SpeedConversion.php) ## Datastructures + * Disjointsets + * [Disjointset](./DataStructures/DisjointSets/DisjointSet.php) + * [Disjointsetnode](./DataStructures/DisjointSets/DisjointSetNode.php) * [Doublylinkedlist](./DataStructures/DoublyLinkedList.php) * [Node](./DataStructures/Node.php) * [Queue](./DataStructures/Queue.php) * [Singlylinkedlist](./DataStructures/SinglyLinkedList.php) * [Stack](./DataStructures/Stack.php) - * [DisjointSets](./DataStructures/DisjointSets/DisjointSet.php) ## Graphs * [Bellmanford](./Graphs/BellmanFord.php) @@ -112,6 +114,7 @@ * Conversions * [Conversionstest](./tests/Conversions/ConversionsTest.php) * Datastructures + * [Disjointsettest](./tests/DataStructures/DisjointSetTest.php) * [Doublylinkedlisttest](./tests/DataStructures/DoublyLinkedListTest.php) * [Queuetest](./tests/DataStructures/QueueTest.php) * [Singlylinkedlisttest](./tests/DataStructures/SinglyLinkedListTest.php) From bc441995ae335dd80fe6ae7c08417f4421685733 Mon Sep 17 00:00:00 2001 From: Ramy-Badr-Ahmed Date: Sat, 14 Sep 2024 17:06:50 +0200 Subject: [PATCH 13/38] Implemented Trie DataStructure --- DataStructures/Trie/Trie.php | 163 ++++++++++++++++++++++++++++++ DataStructures/Trie/TrieNode.php | 43 ++++++++ tests/DataStructures/TrieTest.php | 137 +++++++++++++++++++++++++ 3 files changed, 343 insertions(+) create mode 100644 DataStructures/Trie/Trie.php create mode 100644 DataStructures/Trie/TrieNode.php create mode 100644 tests/DataStructures/TrieTest.php diff --git a/DataStructures/Trie/Trie.php b/DataStructures/Trie/Trie.php new file mode 100644 index 00000000..062d7f33 --- /dev/null +++ b/DataStructures/Trie/Trie.php @@ -0,0 +1,163 @@ +root = new TrieNode(); + } + + /** + * Get the root node of the Trie. + */ + public function getRoot(): TrieNode + { + return $this->root; + } + + /** + * Insert a word into the Trie. + */ + public function insert(string $word): void + { + $node = $this->root; + for ($i = 0; $i < strlen($word); $i++) { + $char = $word[$i]; + $node = $node->addChild($char); + } + $node->isEndOfWord = true; + } + + /** + * Search for a word in the Trie. + */ + public function search(string $word): bool + { + $node = $this->root; + for ($i = 0; $i < strlen($word); $i++) { + $char = $word[$i]; + if (!$node->hasChild($char)) { + return false; + } + $node = $node->getChild($char); + } + return $node->isEndOfWord; + } + + /** + * Find all words that start with a given prefix. + */ + public function startsWith(string $prefix): array + { + $node = $this->root; + for ($i = 0; $i < strlen($prefix); $i++) { + $char = $prefix[$i]; + if (!$node->hasChild($char)) { + return []; + } + $node = $node->getChild($char); + } + return $this->findWordsFromNode($node, $prefix); + } + + /** + * Helper function to find all words from a given node. + */ + private function findWordsFromNode(TrieNode $node, string $prefix): array + { + $words = []; + if ($node->isEndOfWord) { + $words[] = $prefix; + } + + foreach ($node->children as $char => $childNode) { + $words = array_merge($words, $this->findWordsFromNode($childNode, $prefix . $char)); + } + + return $words; + } + + /** + * Delete a word from the Trie. + * Recursively traverses the Trie and removes nodes + * + * @param string $word The word to delete. + * @return bool Returns true if the word was successfully deleted, otherwise false. + */ + public function delete(string $word): bool + { + return $this->deleteHelper($this->root, $word, 0); + } + + /** + * Helper function for deleting a word. + * Recursively traverse the Trie and removes nodes. + * + * @param TrieNode $node The current node in the Trie. + * @param string $word The word being deleted. + * @param int $index The current index in the word. + * @return bool Returns true if the current node should be deleted, otherwise false. + */ + private function deleteHelper(TrieNode $node, string &$word, int $index): bool + { + if ($index === strlen($word)) { + if (!$node->isEndOfWord) { + return false; + } + $node->isEndOfWord = false; + return empty($node->children); + } + + $char = $word[$index]; + $childNode = $node->getChild($char); + if ($childNode === null) { + return false; + } + + // Recursively delete the child node + $shouldDeleteCurrentNode = $this->deleteHelper($childNode, $word, $index + 1); + + if ($shouldDeleteCurrentNode) { + unset($node->children[$char]); + return !$node->isEndOfWord; // true if current node is not the end of another word + } + + return false; + } + + /** + * Recursively traverses the Trie starting from the given node and collects all words. + * + * @param TrieNode $node The starting node for traversal. + * @param string $prefix The prefix of the current path in the Trie. + * @return array An array of words found in the Trie starting from the given node. + */ + public function traverseTrieNode(TrieNode $node, string $prefix = ''): array + { + $words = []; + + if ($node->isEndOfWord) { + $words[] = $prefix; + } + + foreach ($node->children as $char => $childNode) { + $words = array_merge($words, $this->traverseTrieNode($childNode, $prefix . $char)); + } + + return $words; + } + + /** + * Gets all words stored in the Trie. + * + * @return array An array of all words in the Trie. + */ + public function getWords(): array + { + return $this->traverseTrieNode($this->root); + } +} diff --git a/DataStructures/Trie/TrieNode.php b/DataStructures/Trie/TrieNode.php new file mode 100644 index 00000000..fed8dbcc --- /dev/null +++ b/DataStructures/Trie/TrieNode.php @@ -0,0 +1,43 @@ + */ + public array $children; + public bool $isEndOfWord; + + public function __construct() + { + $this->children = []; // Associative array where [ char => TrieNode ] + $this->isEndOfWord = false; + } + + /** + * Add a child node for a character. + */ + public function addChild(string $char): TrieNode + { + if (!isset($this->children[$char])) { + $this->children[$char] = new TrieNode(); + } + return $this->children[$char]; + } + + /** + * Check if a character has a child node. + */ + public function hasChild(string $char): bool + { + return isset($this->children[$char]); + } + + /** + * Get the child node corresponding to a character. + */ + public function getChild(string $char): ?TrieNode + { + return $this->children[$char] ?? null; + } +} diff --git a/tests/DataStructures/TrieTest.php b/tests/DataStructures/TrieTest.php new file mode 100644 index 00000000..d2fa39d5 --- /dev/null +++ b/tests/DataStructures/TrieTest.php @@ -0,0 +1,137 @@ +trie = new Trie(); + } + + public function testInsertAndSearch() + { + $this->trie->insert('the'); + $this->trie->insert('universe'); + $this->trie->insert('is'); + $this->trie->insert('vast'); + + $this->assertTrue($this->trie->search('the'), 'Expected "the" to be found in the Trie.'); + $this->assertTrue($this->trie->search('universe'), 'Expected "universe" to be found in the Trie.'); + $this->assertTrue($this->trie->search('is'), 'Expected "is" to be found in the Trie.'); + $this->assertTrue($this->trie->search('vast'), 'Expected "vast" to be found in the Trie.'); + $this->assertFalse( + $this->trie->search('the universe'), + 'Expected "the universe" not to be found in the Trie.' + ); + } + + public function testStartsWith() + { + $this->trie->insert('hello'); + $this->assertEquals(['hello'], $this->trie->startsWith('he'), 'Expected words starting with "he" to be found.'); + $this->assertEquals( + ['hello'], + $this->trie->startsWith('hello'), + 'Expected words starting with "hello" to be found.' + ); + $this->assertEquals( + [], + $this->trie->startsWith('world'), + 'Expected no words starting with "world" to be found.' + ); + } + + public function testDelete() + { + // Insert words into the Trie + $this->trie->insert('the'); + $this->trie->insert('universe'); + $this->trie->insert('is'); + $this->trie->insert('vast'); + $this->trie->insert('big'); + $this->trie->insert('rather'); + + // Test deleting an existing word + $this->trie->delete('the'); + $this->assertFalse($this->trie->search('the'), 'Expected "the" not to be found after deletion.'); + + // Test that other words are still present + $this->assertTrue($this->trie->search('universe'), 'Expected "universe" to be found.'); + $this->assertTrue($this->trie->search('is'), 'Expected "is" to be found.'); + $this->assertTrue($this->trie->search('vast'), 'Expected "vast" to be found.'); + $this->assertTrue($this->trie->search('big'), 'Expected "big" to be found.'); + $this->assertTrue($this->trie->search('rather'), 'Expected "rather" to be found.'); + } + + public function testDeleteNonExistentWord() + { + $this->trie->delete('nonexistent'); + $this->assertFalse($this->trie->search('nonexistent'), 'Expected "nonexistent" to not be found.'); + } + + public function testTraverseTrieNode() + { + $this->trie->insert('hello'); + $this->trie->insert('helium'); + $this->trie->insert('helicopter'); + + $words = $this->trie->getWords(); + $this->assertContains('hello', $words, 'Expected "hello" to be found in the Trie.'); + $this->assertContains('helium', $words, 'Expected "helium" to be found in the Trie.'); + $this->assertContains('helicopter', $words, 'Expected "helicopter" to be found in the Trie.'); + $this->assertCount(3, $words, 'Expected 3 words in the Trie.'); + } + + public function testEmptyTrie() + { + $this->assertEquals([], $this->trie->getWords(), 'Expected an empty Trie to return an empty array.'); + } + + public function testGetWords() + { + $this->trie->insert('apple'); + $this->trie->insert('app'); + $this->trie->insert('applet'); + + $words = $this->trie->getWords(); + $this->assertContains('apple', $words, 'Expected "apple" to be found in the Trie.'); + $this->assertContains('app', $words, 'Expected "app" to be found in the Trie.'); + $this->assertContains('applet', $words, 'Expected "applet" to be found in the Trie.'); + $this->assertCount(3, $words, 'Expected 3 words in the Trie.'); + } + + public function testInsertEmptyString() + { + $this->trie->insert(''); + $this->assertTrue($this->trie->search(''), 'Expected empty string to be found in the Trie.'); + } + + public function testDeleteEmptyString() + { + $this->trie->insert(''); + $this->trie->delete(''); + $this->assertFalse($this->trie->search(''), 'Expected empty string not to be found after deletion.'); + } + + public function testStartsWithWithCommonPrefix() + { + $this->trie->insert('trie'); + $this->trie->insert('tried'); + $this->trie->insert('trier'); + + $words = $this->trie->startsWith('tri'); + $this->assertContains('trie', $words, 'Expected "trie" to be found with prefix "tri".'); + $this->assertContains('tried', $words, 'Expected "tried" to be found with prefix "tri".'); + $this->assertContains('trier', $words, 'Expected "trier" to be found with prefix "tri".'); + $this->assertCount(3, $words, 'Expected 3 words with prefix "tri".'); + } +} From b056238757c58970d2f9fe2ce25721633fc976ea Mon Sep 17 00:00:00 2001 From: Ramy-Badr-Ahmed Date: Sat, 14 Sep 2024 17:26:07 +0200 Subject: [PATCH 14/38] Added Trie to DIRECTORY.md --- DIRECTORY.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/DIRECTORY.md b/DIRECTORY.md index 9e567459..724b3fa9 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -20,6 +20,9 @@ * Disjointsets * [Disjointset](./DataStructures/DisjointSets/DisjointSet.php) * [Disjointsetnode](./DataStructures/DisjointSets/DisjointSetNode.php) + * Trie + * [Trie](./DataStructures/Trie/Trie.php) + * [TrieNode](./DataStructures/Trie/TrieNode.php) * [Doublylinkedlist](./DataStructures/DoublyLinkedList.php) * [Node](./DataStructures/Node.php) * [Queue](./DataStructures/Queue.php) @@ -115,6 +118,7 @@ * [Conversionstest](./tests/Conversions/ConversionsTest.php) * Datastructures * [Disjointsettest](./tests/DataStructures/DisjointSetTest.php) + * [Trie](./tests/DataStructures/TrieTest.php) * [Doublylinkedlisttest](./tests/DataStructures/DoublyLinkedListTest.php) * [Queuetest](./tests/DataStructures/QueueTest.php) * [Singlylinkedlisttest](./tests/DataStructures/SinglyLinkedListTest.php) From 5a02302b2d00b1586d8720ba052821c1606c22ae Mon Sep 17 00:00:00 2001 From: Ramy-Badr-Ahmed Date: Sat, 14 Sep 2024 15:29:50 +0000 Subject: [PATCH 15/38] updating DIRECTORY.md --- DIRECTORY.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 724b3fa9..7be3da43 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -20,14 +20,14 @@ * Disjointsets * [Disjointset](./DataStructures/DisjointSets/DisjointSet.php) * [Disjointsetnode](./DataStructures/DisjointSets/DisjointSetNode.php) - * Trie - * [Trie](./DataStructures/Trie/Trie.php) - * [TrieNode](./DataStructures/Trie/TrieNode.php) * [Doublylinkedlist](./DataStructures/DoublyLinkedList.php) * [Node](./DataStructures/Node.php) * [Queue](./DataStructures/Queue.php) * [Singlylinkedlist](./DataStructures/SinglyLinkedList.php) * [Stack](./DataStructures/Stack.php) + * Trie + * [Trie](./DataStructures/Trie/Trie.php) + * [Trienode](./DataStructures/Trie/TrieNode.php) ## Graphs * [Bellmanford](./Graphs/BellmanFord.php) @@ -118,11 +118,11 @@ * [Conversionstest](./tests/Conversions/ConversionsTest.php) * Datastructures * [Disjointsettest](./tests/DataStructures/DisjointSetTest.php) - * [Trie](./tests/DataStructures/TrieTest.php) * [Doublylinkedlisttest](./tests/DataStructures/DoublyLinkedListTest.php) * [Queuetest](./tests/DataStructures/QueueTest.php) * [Singlylinkedlisttest](./tests/DataStructures/SinglyLinkedListTest.php) * [Stacktest](./tests/DataStructures/StackTest.php) + * [Trietest](./tests/DataStructures/TrieTest.php) * Graphs * [Bellmanfordtest](./tests/Graphs/BellmanFordTest.php) * [Breadthfirstsearchtest](./tests/Graphs/BreadthFirstSearchTest.php) From 9c76b4b0fc0fb57f63cb770c1726f62ca5e9a81d Mon Sep 17 00:00:00 2001 From: Ramy-Badr-Ahmed Date: Sun, 15 Sep 2024 00:31:17 +0200 Subject: [PATCH 16/38] Implemented AVLTree DataStructure --- DIRECTORY.md | 4 + DataStructures/AVLTree/AVLTree.php | 306 +++++++++++++++++++++++ DataStructures/AVLTree/AVLTreeNode.php | 41 +++ DataStructures/AVLTree/TreeTraversal.php | 80 ++++++ tests/DataStructures/AVLTreeTest.php | 294 ++++++++++++++++++++++ 5 files changed, 725 insertions(+) create mode 100644 DataStructures/AVLTree/AVLTree.php create mode 100644 DataStructures/AVLTree/AVLTreeNode.php create mode 100644 DataStructures/AVLTree/TreeTraversal.php create mode 100644 tests/DataStructures/AVLTreeTest.php diff --git a/DIRECTORY.md b/DIRECTORY.md index 9e567459..1e24e5b9 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -17,6 +17,9 @@ * [Speedconversion](./Conversions/SpeedConversion.php) ## Datastructures + * AVLTree + * [AVLTree](./DataStructures/AVLTree/AVLTree.php) + * [AVLTreeNode](./DataStructures/AVLTree/AVLTreeNode.php) * Disjointsets * [Disjointset](./DataStructures/DisjointSets/DisjointSet.php) * [Disjointsetnode](./DataStructures/DisjointSets/DisjointSetNode.php) @@ -114,6 +117,7 @@ * Conversions * [Conversionstest](./tests/Conversions/ConversionsTest.php) * Datastructures + * [AVLTreeTest](./tests/DataStructures/AVLTreeTest.php) * [Disjointsettest](./tests/DataStructures/DisjointSetTest.php) * [Doublylinkedlisttest](./tests/DataStructures/DoublyLinkedListTest.php) * [Queuetest](./tests/DataStructures/QueueTest.php) diff --git a/DataStructures/AVLTree/AVLTree.php b/DataStructures/AVLTree/AVLTree.php new file mode 100644 index 00000000..a93b5f32 --- /dev/null +++ b/DataStructures/AVLTree/AVLTree.php @@ -0,0 +1,306 @@ +root = null; + $this->counter = 0; + } + + /** + * Get the root node of the AVL Tree. + */ + public function getRoot(): ?AVLTreeNode + { + return $this->root; + } + + /** + * Retrieve a node by its key. + * + * @param mixed $key The key of the node to retrieve. + * @return ?AVLTreeNode The node with the specified key, or null if not found. + */ + public function getNode($key): ?AVLTreeNode + { + return $this->searchNode($this->root, $key); + } + + /** + * Get the number of nodes in the AVL Tree. + */ + public function size(): int + { + return $this->counter; + } + + /** + * Insert a key-value pair into the AVL Tree. + * + * @param mixed $key The key to insert. + * @param mixed $value The value associated with the key. + */ + public function insert($key, $value): void + { + $this->root = $this->insertNode($this->root, $key, $value); + $this->counter++; + } + + /** + * Delete a node by its key from the AVL Tree. + * + * @param mixed $key The key of the node to delete. + */ + public function delete($key): void + { + $this->root = $this->deleteNode($this->root, $key); + $this->counter--; + } + + /** + * Search for a value by its key. + * + * @param mixed $key The key to search for. + * @return mixed The value associated with the key, or null if not found. + */ + public function search($key) + { + $node = $this->searchNode($this->root, $key); + return $node ? $node->value : null; + } + + /** + * Perform an in-order traversal of the AVL Tree. + * Initiates the traversal on the root node directly and returns the array of key-value pairs. + */ + public function inOrderTraversal(): array + { + return TreeTraversal::inOrder($this->root); + } + + /** + * Perform a pre-order traversal of the AVL Tree. + * Initiates the traversal on the root node directly and returns the array of key-value pairs. + */ + public function preOrderTraversal(): array + { + return TreeTraversal::preOrder($this->root); + } + + /** + * Perform a post-order traversal of the AVL Tree. + * Initiates the traversal on the root node directly and returns the array of key-value pairs. + */ + public function postOrderTraversal(): array + { + return TreeTraversal::postOrder($this->root); + } + + /** + * Perform a breadth-first traversal of the AVL Tree. + */ + public function breadthFirstTraversal(): array + { + return TreeTraversal::breadthFirst($this->root); + } + + /** + * Check if the AVL Tree is balanced. + * This method check balance starting from the root node directly + */ + public function isBalanced(): bool + { + return $this->isBalancedHelper($this->root); + } + + /** + * Insert a node into the AVL Tree and balance the tree. + * + * @param ?AVLTreeNode $node The current node. + * @param mixed $key The key to insert. + * @param mixed $value The value to insert. + * @return AVLTreeNode The new root of the subtree. + */ + private function insertNode(?AVLTreeNode $node, $key, $value): AVLTreeNode + { + if ($node === null) { + return new AVLTreeNode($key, $value); + } + + if ($key < $node->key) { + $node->left = $this->insertNode($node->left, $key, $value); + } elseif ($key > $node->key) { + $node->right = $this->insertNode($node->right, $key, $value); + } else { + $node->value = $value; // Update existing value + } + + $node->updateHeight(); + return $this->balance($node); + } + + /** + * Delete a node by its key and balance the tree. + * + * @param ?AVLTreeNode $node The current node. + * @param mixed $key The key of the node to delete. + * @return ?AVLTreeNode The new root of the subtree. + */ + private function deleteNode(?AVLTreeNode $node, $key): ?AVLTreeNode + { + if ($node === null) { + return null; + } + + if ($key < $node->key) { + $node->left = $this->deleteNode($node->left, $key); + } elseif ($key > $node->key) { + $node->right = $this->deleteNode($node->right, $key); + } else { + if (!$node->left) { + return $node->right; + } + if (!$node->right) { + return $node->left; + } + + $minNode = $this->getMinNode($node->right); + $node->key = $minNode->key; + $node->value = $minNode->value; + $node->right = $this->deleteNode($node->right, $minNode->key); + } + + $node->updateHeight(); + return $this->balance($node); + } + + /** + * Search for a node by its key. + * + * @param ?AVLTreeNode $node The current node. + * @param mixed $key The key to search for. + * @return ?AVLTreeNode The node with the specified key, or null if not found. + */ + private function searchNode(?AVLTreeNode $node, $key): ?AVLTreeNode + { + if ($node === null) { + return null; + } + + if ($key < $node->key) { + return $this->searchNode($node->left, $key); + } elseif ($key > $node->key) { + return $this->searchNode($node->right, $key); + } else { + return $node; + } + } + + /** + * Helper method to check if a subtree is balanced. + * + * @param ?AVLTreeNode $node The current node. + * @return bool True if the subtree is balanced, false otherwise. + */ + private function isBalancedHelper(?AVLTreeNode $node): bool + { + if ($node === null) { + return true; + } + + $leftHeight = $node->left ? $node->left->height : 0; + $rightHeight = $node->right ? $node->right->height : 0; + + $balanceFactor = abs($leftHeight - $rightHeight); + if ($balanceFactor > 1) { + return false; + } + + return $this->isBalancedHelper($node->left) && $this->isBalancedHelper($node->right); + } + + /** + * Balance the subtree rooted at the given node. + * + * @param ?AVLTreeNode $node The current node. + * @return ?AVLTreeNode The new root of the subtree. + */ + private function balance(?AVLTreeNode $node): ?AVLTreeNode + { + if ($node->balanceFactor() > 1) { + if ($node->left && $node->left->balanceFactor() < 0) { + $node->left = $this->rotateLeft($node->left); + } + return $this->rotateRight($node); + } + + if ($node->balanceFactor() < -1) { + if ($node->right && $node->right->balanceFactor() > 0) { + $node->right = $this->rotateRight($node->right); + } + return $this->rotateLeft($node); + } + + return $node; + } + + /** + * Perform a left rotation on the given node. + * + * @param AVLTreeNode $node The node to rotate. + * @return AVLTreeNode The new root of the rotated subtree. + */ + private function rotateLeft(AVLTreeNode $node): AVLTreeNode + { + $newRoot = $node->right; + $node->right = $newRoot->left; + $newRoot->left = $node; + + $node->updateHeight(); + $newRoot->updateHeight(); + + return $newRoot; + } + + /** + * Perform a right rotation on the given node. + * + * @param AVLTreeNode $node The node to rotate. + * @return AVLTreeNode The new root of the rotated subtree. + */ + private function rotateRight(AVLTreeNode $node): AVLTreeNode + { + $newRoot = $node->left; + $node->left = $newRoot->right; + $newRoot->right = $node; + + $node->updateHeight(); + $newRoot->updateHeight(); + + return $newRoot; + } + + /** + * Get the node with the minimum key in the given subtree. + * + * @param AVLTreeNode $node The root of the subtree. + * @return AVLTreeNode The node with the minimum key. + */ + private function getMinNode(AVLTreeNode $node): AVLTreeNode + { + while ($node->left) { + $node = $node->left; + } + return $node; + } +} diff --git a/DataStructures/AVLTree/AVLTreeNode.php b/DataStructures/AVLTree/AVLTreeNode.php new file mode 100644 index 00000000..707bf97f --- /dev/null +++ b/DataStructures/AVLTree/AVLTreeNode.php @@ -0,0 +1,41 @@ +key = $key; + $this->value = $value; + $this->left = $left; + $this->right = $right; + $this->height = 1; // New node is initially at height 1 + } + + public function updateHeight(): void + { + $leftHeight = $this->left ? $this->left->height : 0; + $rightHeight = $this->right ? $this->right->height : 0; + $this->height = max($leftHeight, $rightHeight) + 1; + } + + public function balanceFactor(): int + { + $leftHeight = $this->left ? $this->left->height : 0; + $rightHeight = $this->right ? $this->right->height : 0; + return $leftHeight - $rightHeight; + } +} diff --git a/DataStructures/AVLTree/TreeTraversal.php b/DataStructures/AVLTree/TreeTraversal.php new file mode 100644 index 00000000..803a856a --- /dev/null +++ b/DataStructures/AVLTree/TreeTraversal.php @@ -0,0 +1,80 @@ +left)); + $result[] = [$node->key => $node->value]; + $result = array_merge($result, self::inOrder($node->right)); + } + return $result; + } + + /** + * Perform a pre-order traversal of the subtree. + * Recursively traverses the subtree rooted at the given node. + */ + public static function preOrder(?AVLTreeNode $node): array + { + $result = []; + if ($node !== null) { + $result[] = [$node->key => $node->value]; + $result = array_merge($result, self::preOrder($node->left)); + $result = array_merge($result, self::preOrder($node->right)); + } + return $result; + } + + /** + * Perform a post-order traversal of the subtree. + * Recursively traverses the subtree rooted at the given node. + */ + public static function postOrder(?AVLTreeNode $node): array + { + $result = []; + if ($node !== null) { + $result = array_merge($result, self::postOrder($node->left)); + $result = array_merge($result, self::postOrder($node->right)); + $result[] = [$node->key => $node->value]; + } + return $result; + } + + /** + * Perform a breadth-first traversal of the AVL Tree. + */ + public static function breadthFirst(?AVLTreeNode $root): array + { + $result = []; + if ($root === null) { + return $result; + } + + $queue = []; + $queue[] = $root; + + while (!empty($queue)) { + $currentNode = array_shift($queue); + $result[] = [$currentNode->key => $currentNode->value]; + + if ($currentNode->left !== null) { + $queue[] = $currentNode->left; + } + + if ($currentNode->right !== null) { + $queue[] = $currentNode->right; + } + } + + return $result; + } +} diff --git a/tests/DataStructures/AVLTreeTest.php b/tests/DataStructures/AVLTreeTest.php new file mode 100644 index 00000000..e470248e --- /dev/null +++ b/tests/DataStructures/AVLTreeTest.php @@ -0,0 +1,294 @@ +tree = new AVLTree(); + } + + private function populateTree(): void + { + $this->tree->insert(10, 'Value 10'); + $this->tree->insert(20, 'Value 20'); + $this->tree->insert(5, 'Value 5'); + $this->tree->insert(15, 'Value 15'); + } + + public function testInsertAndSearch(): void + { + $this->populateTree(); + + $this->assertEquals('Value 10', $this->tree->search(10), 'Value for key 10 should be "Value 10"'); + $this->assertEquals('Value 20', $this->tree->search(20), 'Value for key 20 should be "Value 20"'); + $this->assertEquals('Value 5', $this->tree->search(5), 'Value for key 5 should be "Value 5"'); + $this->assertNull($this->tree->search(25), 'Value for non-existent key 25 should be null'); + } + + public function testDelete(): void + { + $this->populateTree(); + + $this->tree->delete(20); + $this->tree->delete(5); + + $this->assertNull($this->tree->search(20), 'Value for deleted key 20 should be null'); + $this->assertNull($this->tree->search(5), 'Value for deleted key 5 should be null'); + + $this->tree->delete(50); + + $this->assertNotNull($this->tree->search(10), 'Value for key 10 should still exist'); + $this->assertNotNull($this->tree->search(15), 'Value for key 15 should still exist'); + $this->assertNull($this->tree->search(50), 'Value for non-existent key 50 should be null'); + + $expectedInOrderAfterDelete = [ + [10 => 'Value 10'], + [15 => 'Value 15'] + ]; + + $result = TreeTraversal::inOrder($this->tree->getRoot()); + $this->assertEquals( + $expectedInOrderAfterDelete, + $result, + 'In-order traversal after deletion should match expected result' + ); + } + + public function testInOrderTraversal(): void + { + $this->populateTree(); + + $expectedInOrder = [ + [5 => 'Value 5'], + [10 => 'Value 10'], + [15 => 'Value 15'], + [20 => 'Value 20'] + ]; + + $result = $this->tree->inOrderTraversal(); + $this->assertEquals($expectedInOrder, $result, 'In-order traversal should match expected result'); + } + + public function testPreOrderTraversal(): void + { + $this->populateTree(); + + $expectedPreOrder = [ + [10 => 'Value 10'], + [5 => 'Value 5'], + [20 => 'Value 20'], + [15 => 'Value 15'] + ]; + + $result = $this->tree->preOrderTraversal(); + $this->assertEquals($expectedPreOrder, $result, 'Pre-order traversal should match expected result'); + } + + public function testPostOrderTraversal(): void + { + $this->populateTree(); + + $expectedPostOrder = [ + [5 => 'Value 5'], + [15 => 'Value 15'], + [20 => 'Value 20'], + [10 => 'Value 10'] + ]; + + $result = TreeTraversal::postOrder($this->tree->getRoot()); + $this->assertEquals($expectedPostOrder, $result, 'Post-order traversal should match expected result'); + } + + public function testBreadthFirstTraversal(): void + { + $this->populateTree(); + + $expectedBFT = [ + [10 => 'Value 10'], + [5 => 'Value 5'], + [20 => 'Value 20'], + [15 => 'Value 15'] + ]; + + $result = TreeTraversal::breadthFirst($this->tree->getRoot()); + $this->assertEquals($expectedBFT, $result, 'Breadth-first traversal should match expected result'); + } + + public function testInsertAndDeleteSingleNode(): void + { + $this->tree = new AVLTree(); + + $this->tree->insert(1, 'One'); + $this->assertEquals('One', $this->tree->search(1), 'Value for key 1 should be "One"'); + $this->tree->delete(1); + $this->assertNull($this->tree->search(1), 'Value for key 1 should be null after deletion'); + } + + public function testDeleteFromEmptyTree(): void + { + $this->tree = new AVLTree(); + + $this->tree->delete(1); + $this->assertNull($this->tree->search(1), 'Value for key 1 should be null as it was never inserted'); + } + + public function testInsertDuplicateKeys(): void + { + $this->tree = new AVLTree(); + + $this->tree->insert(1, 'One'); + $this->tree->insert(1, 'One Updated'); + $this->assertEquals( + 'One Updated', + $this->tree->search(1), + 'Value for key 1 should be "One Updated" after updating' + ); + } + + public function testLargeTree(): void + { + // Inserting a large number of nodes + for ($i = 1; $i <= 1000; $i++) { + $this->tree->insert($i, "Value $i"); + } + + // Verify that all inserted nodes can be searched + for ($i = 1; $i <= 1000; $i++) { + $this->assertEquals("Value $i", $this->tree->search($i), "Value for key $i should be 'Value $i'"); + } + + // Verify that all inserted nodes can be deleted + for ($i = 1; $i <= 5; $i++) { + $this->tree->delete($i); + $this->assertNull($this->tree->search($i), "Value for key $i should be null after deletion"); + } + } + + public function testBalance(): void + { + $this->populateTree(); + + // Perform operations that may unbalance the tree + $this->tree->insert(30, 'Value 30'); + $this->tree->insert(25, 'Value 25'); + + // After insertions, check the balance + $this->assertTrue($this->tree->isBalanced(), 'Tree should be balanced after insertions'); + } + + public function testRightRotation(): void + { + $this->populateTreeForRightRotation(); + + // Insert a node that will trigger a right rotation + $this->tree->insert(40, 'Value 40'); + + // Verify the tree structure after rotation + $root = $this->tree->getRoot(); + $this->assertEquals(20, $root->key, 'Root should be 20 after right rotation'); + $this->assertEquals(10, $root->left->key, 'Left child of root should be 10'); + $this->assertEquals(30, $root->right->key, 'Right child of root should be 30'); + } + + private function populateTreeForRightRotation(): void + { + // Insert nodes in a way that requires a right rotation + $this->tree->insert(10, 'Value 10'); + $this->tree->insert(20, 'Value 20'); + $this->tree->insert(30, 'Value 30'); // This should trigger a right rotation around 10 + } + + public function testLeftRotation(): void + { + $this->populateTreeForLeftRotation(); + + // Insert a node that will trigger a left rotation + $this->tree->insert(5, 'Value 5'); + + // Verify the tree structure after rotation + $root = $this->tree->getRoot(); + $this->assertEquals(20, $root->key, 'Root should be 20 after left rotation'); + $this->assertEquals(10, $root->left->key, 'Left child of root should be 10'); + $this->assertEquals(30, $root->right->key, 'Right child of root should be 30'); + } + + private function populateTreeForLeftRotation(): void + { + $this->tree->insert(30, 'Value 30'); + $this->tree->insert(20, 'Value 20'); + $this->tree->insert(10, 'Value 10'); // This should trigger a left rotation around 30 + } + + /** + * @throws ReflectionException + */ + public function testGetMinNode(): void + { + $this->populateTree(); + + // Using Reflection to access the private getMinNode method + $reflection = new ReflectionClass($this->tree); + $method = $reflection->getMethod('getMinNode'); + + $minNode = $method->invoke($this->tree, $this->tree->getRoot()); + + // Verify the minimum node + $this->assertEquals(5, $minNode->key, 'Minimum key in the tree should be 5'); + $this->assertEquals('Value 5', $minNode->value, 'Value for minimum key 5 should be "Value 5"'); + } + + public function testSizeAfterInsertions(): void + { + $this->tree = new AVLTree(); + + $this->assertEquals(0, $this->tree->size(), 'Size should be 0 initially'); + + $this->tree->insert(10, 'Value 10'); + $this->tree->insert(20, 'Value 20'); + $this->tree->insert(5, 'Value 5'); + + $this->assertEquals(3, $this->tree->size(), 'Size should be 3 after 3 insertions'); + + $this->tree->delete(20); + + $this->assertEquals(2, $this->tree->size(), 'Size should be 2 after deleting 1 node'); + } + + public function testSizeAfterMultipleInsertionsAndDeletions(): void + { + $this->tree = new AVLTree(); + + // Insert nodes + for ($i = 1; $i <= 10; $i++) { + $this->tree->insert($i, "Value $i"); + } + + $this->assertEquals(10, $this->tree->size(), 'Size should be 10 after 10 insertions'); + + for ($i = 1; $i <= 5; $i++) { + $this->tree->delete($i); + } + + $this->assertEquals(5, $this->tree->size(), 'Size should be 5 after deleting 5 nodes'); + } + + public function testSizeOnEmptyTree(): void + { + $this->tree = new AVLTree(); + $this->assertEquals(0, $this->tree->size(), 'Size should be 0 for an empty tree'); + } +} From d258e7754c3e9522f8544480b9fe47d7c7fb6f13 Mon Sep 17 00:00:00 2001 From: Ramy-Badr-Ahmed Date: Sat, 14 Sep 2024 22:36:33 +0000 Subject: [PATCH 17/38] updating DIRECTORY.md --- DIRECTORY.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index a1641443..c1ece342 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -17,9 +17,10 @@ * [Speedconversion](./Conversions/SpeedConversion.php) ## Datastructures - * AVLTree - * [AVLTree](./DataStructures/AVLTree/AVLTree.php) - * [AVLTreeNode](./DataStructures/AVLTree/AVLTreeNode.php) + * Avltree + * [Avltree](./DataStructures/AVLTree/AVLTree.php) + * [Avltreenode](./DataStructures/AVLTree/AVLTreeNode.php) + * [Treetraversal](./DataStructures/AVLTree/TreeTraversal.php) * Disjointsets * [Disjointset](./DataStructures/DisjointSets/DisjointSet.php) * [Disjointsetnode](./DataStructures/DisjointSets/DisjointSetNode.php) @@ -120,7 +121,7 @@ * Conversions * [Conversionstest](./tests/Conversions/ConversionsTest.php) * Datastructures - * [AVLTreeTest](./tests/DataStructures/AVLTreeTest.php) + * [Avltreetest](./tests/DataStructures/AVLTreeTest.php) * [Disjointsettest](./tests/DataStructures/DisjointSetTest.php) * [Doublylinkedlisttest](./tests/DataStructures/DoublyLinkedListTest.php) * [Queuetest](./tests/DataStructures/QueueTest.php) From 9766ed83b6400909fbbb1e2b38b996465a0c8263 Mon Sep 17 00:00:00 2001 From: Ramy-Badr-Ahmed Date: Sun, 15 Sep 2024 00:45:55 +0200 Subject: [PATCH 18/38] Implemented AVLTree DataStructure --- tests/DataStructures/AVLTreeTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/DataStructures/AVLTreeTest.php b/tests/DataStructures/AVLTreeTest.php index e470248e..8f556de8 100644 --- a/tests/DataStructures/AVLTreeTest.php +++ b/tests/DataStructures/AVLTreeTest.php @@ -243,6 +243,7 @@ public function testGetMinNode(): void // Using Reflection to access the private getMinNode method $reflection = new ReflectionClass($this->tree); $method = $reflection->getMethod('getMinNode'); + $method->setAccessible(true); $minNode = $method->invoke($this->tree, $this->tree->getRoot()); From a8ca30fa0d75e0aec92662c78b49ec376263584b Mon Sep 17 00:00:00 2001 From: Ramy-Badr-Ahmed Date: Sun, 22 Sep 2024 01:59:08 +0200 Subject: [PATCH 19/38] Implemented SegmentTreeNode.php --- .../SegmentTree/SegmentTreeNode.php | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 DataStructures/SegmentTree/SegmentTreeNode.php diff --git a/DataStructures/SegmentTree/SegmentTreeNode.php b/DataStructures/SegmentTree/SegmentTreeNode.php new file mode 100644 index 00000000..8a6152f3 --- /dev/null +++ b/DataStructures/SegmentTree/SegmentTreeNode.php @@ -0,0 +1,30 @@ +start = $start; + $this->end = $end; + $this->value = $value; + $this->left = null; + $this->right = null; + } +} From ba43a0d187903828669e07d4294678a3fe25f325 Mon Sep 17 00:00:00 2001 From: Ramy-Badr-Ahmed Date: Sun, 22 Sep 2024 03:18:25 +0200 Subject: [PATCH 20/38] Implementing SegmentTree --- DataStructures/SegmentTree/SegmentTree.php | 97 ++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 DataStructures/SegmentTree/SegmentTree.php diff --git a/DataStructures/SegmentTree/SegmentTree.php b/DataStructures/SegmentTree/SegmentTree.php new file mode 100644 index 00000000..186b23f8 --- /dev/null +++ b/DataStructures/SegmentTree/SegmentTree.php @@ -0,0 +1,97 @@ + max($a, $b)); + * + * @param array $arr The input array for the segment tree + * @param callable|null $callback Optional callback function for custom aggregation logic + * @throws InvalidArgumentException if the array is empty, contains non-numeric values, or is associative + */ + public function __construct(array $arr, callable $callback = null) + { + $this->currentArray = $arr; + $this->arraySize = count($this->currentArray); + $this->callback = $callback; + + if ($this->isUnsupportedArray()) { + throw new InvalidArgumentException("Array must not be empty, must contain numeric values + and must be non-associative."); + } + + $this->root = $this->buildTree($this->currentArray, 0, $this->arraySize - 1); + } + + private function isUnsupportedArray(): bool + { + return empty($this->currentArray) || $this->isNonNumeric() || $this->isAssociative(); + } + + /** + * @return bool True if any element is non-numeric, false otherwise + */ + private function isNonNumeric(): bool + { + return !array_reduce($this->currentArray, fn($carry, $item) => $carry && is_numeric($item), true); + } + + /** + * @return bool True if the array is associative, false otherwise + */ + private function isAssociative(): bool + { + return array_keys($this->currentArray) !== range(0, $this->arraySize - 1); + } + + /** + * Gets the root node of the segment tree. + * + * @return SegmentTreeNode The root node of the segment tree + */ + public function getRoot(): SegmentTreeNode + { + return $this->root; + } + + /** + * Builds the segment tree recursively. + * + * @param array $arr The input array + * @param int $start The starting index of the segment + * @param int $end The ending index of the segment + * @return SegmentTreeNode The root node of the constructed segment + */ + private function buildTree(array $arr, int $start, int $end): SegmentTreeNode + { + if ($start == $end) { + return new SegmentTreeNode($start, $end, $arr[$start]); + } + + $mid = $start + (int)(($end - $start) / 2); + + $leftChild = $this->buildTree($arr, $start, $mid); + $rightChild = $this->buildTree($arr, $mid + 1, $end); + + $node = new SegmentTreeNode($start, $end, $this->callback + ? ($this->callback)($leftChild->value, $rightChild->value) + : $leftChild->value + $rightChild->value); // falls back to sum if no callback is given + + $node->left = $leftChild; + $node->right = $rightChild; + + return $node; + } +} From ee1c6f61dd6da6606e985d7abf417d6d005f3af4 Mon Sep 17 00:00:00 2001 From: Ramy-Badr-Ahmed Date: Sun, 22 Sep 2024 03:35:14 +0200 Subject: [PATCH 21/38] Implementing SegmentTree with updateTree --- DataStructures/SegmentTree/SegmentTree.php | 70 ++++++++++++++++++++-- 1 file changed, 64 insertions(+), 6 deletions(-) diff --git a/DataStructures/SegmentTree/SegmentTree.php b/DataStructures/SegmentTree/SegmentTree.php index 186b23f8..8f68335d 100644 --- a/DataStructures/SegmentTree/SegmentTree.php +++ b/DataStructures/SegmentTree/SegmentTree.php @@ -3,16 +3,18 @@ namespace DataStructures\SegmentTree; use InvalidArgumentException; +use OutOfBoundsException; class SegmentTree { - private SegmentTreeNode $root; - private array $currentArray; // holds the original array - private int $arraySize; + private SegmentTreeNode $root; // Root node of the segment tree + private array $currentArray; // holds the original array and updates reflections + private int $arraySize; // Size of the original array private $callback; // Callback function for aggregation /** * Initializes the segment tree with the provided array and optional callback for aggregation. + * Default aggregation is Sum * * Example usage: * $segmentTree = new SegmentTree($array, fn($a, $b) => max($a, $b)); @@ -57,8 +59,6 @@ private function isAssociative(): bool } /** - * Gets the root node of the segment tree. - * * @return SegmentTreeNode The root node of the segment tree */ public function getRoot(): SegmentTreeNode @@ -66,6 +66,14 @@ public function getRoot(): SegmentTreeNode return $this->root; } + /** + * @return array The original or the current array (after any update) stored in the segment tree. + */ + public function getCurrentArray(): array + { + return $this->currentArray; + } + /** * Builds the segment tree recursively. * @@ -76,6 +84,7 @@ public function getRoot(): SegmentTreeNode */ private function buildTree(array $arr, int $start, int $end): SegmentTreeNode { + // Leaf node if ($start == $end) { return new SegmentTreeNode($start, $end, $arr[$start]); } @@ -87,11 +96,60 @@ private function buildTree(array $arr, int $start, int $end): SegmentTreeNode $node = new SegmentTreeNode($start, $end, $this->callback ? ($this->callback)($leftChild->value, $rightChild->value) - : $leftChild->value + $rightChild->value); // falls back to sum if no callback is given + : $leftChild->value + $rightChild->value); + // Link the children to the parent node $node->left = $leftChild; $node->right = $rightChild; return $node; } + + /** + * Updates the value at a specified index in the segment tree. + * + * @param int $index The index to update + * @param int|float $value The new value to set + * @throws OutOfBoundsException if the index is out of bounds + */ + public function update(int $index, int $value): void + { + if ($index < 0 || $index >= $this->arraySize) { + throw new OutOfBoundsException("Index out of bounds: $index. Must be between 0 and " + . ($this->arraySize - 1)); + } + + $this->updateTree($this->root, $index, $value); + $this->currentArray[$index] = $value; // Reflect the update in the original array + } + + /** + * Recursively updates the segment tree. + * + * @param SegmentTreeNode $node The current node + * @param int $index The index to update + * @param int|float $value The new value + */ + private function updateTree(SegmentTreeNode $node, int $index, $value): void + { + // Leaf node + if ($node->start == $node->end) { + $node->value = $value; + return; + } + + $mid = $node->start + (int)(($node->end - $node->start) / 2); + + // Decide whether to go to the left or right child + if ($index <= $mid) { + $this->updateTree($node->left, $index, $value); + } else { + $this->updateTree($node->right, $index, $value); + } + + // Recompute the value of the current node after the update + $node->value = $this->callback + ? ($this->callback)($node->left->value, $node->right->value) + : $node->left->value + $node->right->value; + } } From 6912710e828252649d866ed42e65f6c9d1102af6 Mon Sep 17 00:00:00 2001 From: Ramy-Badr-Ahmed Date: Sun, 22 Sep 2024 03:45:19 +0200 Subject: [PATCH 22/38] Implementing SegmentTree with rangeUpdateTree --- DataStructures/SegmentTree/SegmentTree.php | 54 ++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/DataStructures/SegmentTree/SegmentTree.php b/DataStructures/SegmentTree/SegmentTree.php index 8f68335d..699427fc 100644 --- a/DataStructures/SegmentTree/SegmentTree.php +++ b/DataStructures/SegmentTree/SegmentTree.php @@ -152,4 +152,58 @@ private function updateTree(SegmentTreeNode $node, int $index, $value): void ? ($this->callback)($node->left->value, $node->right->value) : $node->left->value + $node->right->value; } + + /** + * Performs a range update on a specified segment. + * + * @param int $start The starting index of the range + * @param int $end The ending index of the range + * @param int|float $value The value to set for the range + * @throws OutOfBoundsException if the range is invalid + */ + public function rangeUpdate(int $start, int $end, $value): void + { + if ($start < 0 || $end >= $this->arraySize || $start > $end) { + throw new OutOfBoundsException("Invalid range: start = $start, end = $end."); + } + $this->rangeUpdateTree($this->root, $start, $end, $value); + + // Update the original array to reflect the range update + $this->currentArray = array_replace($this->currentArray, array_fill_keys(range($start, $end), $value)); + } + + /** + * Recursively performs a range update in the segment tree. + * + * @param SegmentTreeNode $node The current node + * @param int $start The starting index of the range + * @param int $end The ending index of the range + * @param int|float $value The new value for the range + */ + private function rangeUpdateTree(SegmentTreeNode $node, int $start, int $end, $value): void + { + // Leaf node + if ($node->start == $node->end) { + $node->value = $value; + return; + } + + $mid = $node->start + (int)(($node->end - $node->start) / 2); + + // Determine which segment of the tree to update (Left, Right, Split respectively) + if ($end <= $mid) { + $this->rangeUpdateTree($node->left, $start, $end, $value); // Entire range is in the left child + } elseif ($start > $mid) { + $this->rangeUpdateTree($node->right, $start, $end, $value); // Entire range is in the right child + } else { + // Range is split between left and right children + $this->rangeUpdateTree($node->left, $start, $mid, $value); + $this->rangeUpdateTree($node->right, $mid + 1, $end, $value); + } + + // Recompute the value of the current node after the update + $node->value = $this->callback + ? ($this->callback)($node->left->value, $node->right->value) + : $node->left->value + $node->right->value; + } } From 4d951d1ac2fb5f5bf3fe8de78ebec03cabe3f307 Mon Sep 17 00:00:00 2001 From: Ramy-Badr-Ahmed Date: Sun, 22 Sep 2024 03:55:47 +0200 Subject: [PATCH 23/38] Implementing SegmentTree with query and queryTree --- DataStructures/SegmentTree/SegmentTree.php | 50 ++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/DataStructures/SegmentTree/SegmentTree.php b/DataStructures/SegmentTree/SegmentTree.php index 699427fc..499e5911 100644 --- a/DataStructures/SegmentTree/SegmentTree.php +++ b/DataStructures/SegmentTree/SegmentTree.php @@ -91,6 +91,7 @@ private function buildTree(array $arr, int $start, int $end): SegmentTreeNode $mid = $start + (int)(($end - $start) / 2); + // Recursively build left and right children $leftChild = $this->buildTree($arr, $start, $mid); $rightChild = $this->buildTree($arr, $mid + 1, $end); @@ -105,6 +106,55 @@ private function buildTree(array $arr, int $start, int $end): SegmentTreeNode return $node; } + /** + * Queries the aggregated value over a specified range. + * + * @param int $start The starting index of the range + * @param int $end The ending index of the range + * @return int|float The aggregated value for the range + * @throws OutOfBoundsException if the range is invalid + */ + public function query(int $start, int $end) + { + if ($start > $end || $start < 0 || $end > ($this->root->end)) { + throw new OutOfBoundsException("Index out of bounds: start = $start, end = $end. + Must be between 0 and " . ($this->arraySize - 1)); + } + return $this->queryTree($this->root, $start, $end); + } + + /** + * Recursively queries the segment tree for a specific range. + * + * @param SegmentTreeNode $node The current node + * @param int $start The starting index of the query range + * @param int $end The ending index of the query range + * @return int|float The aggregated value for the range + */ + private function queryTree(SegmentTreeNode $node, int $start, int $end) + { + if ($node->start == $start && $node->end == $end) { + return $node->value; + } + + $mid = $node->start + (int)(($node->end - $node->start) / 2); + + // Determine which segment of the tree to query + if ($end <= $mid) { + return $this->queryTree($node->left, $start, $end); // Query left child + } elseif ($start > $mid) { + return $this->queryTree($node->right, $start, $end); // Query right child + } else { + // Split query between left and right children + $leftResult = $this->queryTree($node->left, $start, $mid); + $rightResult = $this->queryTree($node->right, $mid + 1, $end); + + return $this->callback + ? ($this->callback)($leftResult, $rightResult) + : $leftResult + $rightResult; // Default sum if no callback + } + } + /** * Updates the value at a specified index in the segment tree. * From f7413013eea055fd86d5fdd8cbb2fee4f09aa24b Mon Sep 17 00:00:00 2001 From: Ramy-Badr-Ahmed Date: Sun, 22 Sep 2024 04:05:41 +0200 Subject: [PATCH 24/38] Added serializing and deserializing of the SegmentTree --- DataStructures/SegmentTree/SegmentTree.php | 65 ++++++++++++++++++++++ composer.json | 3 +- 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/DataStructures/SegmentTree/SegmentTree.php b/DataStructures/SegmentTree/SegmentTree.php index 499e5911..87e5a738 100644 --- a/DataStructures/SegmentTree/SegmentTree.php +++ b/DataStructures/SegmentTree/SegmentTree.php @@ -256,4 +256,69 @@ private function rangeUpdateTree(SegmentTreeNode $node, int $start, int $end, $v ? ($this->callback)($node->left->value, $node->right->value) : $node->left->value + $node->right->value; } + + /** + * Serializes the segment tree into a JSON string. + * + * @return string The serialized segment tree as a JSON string + */ + public function serialize(): string + { + return json_encode($this->serializeTree($this->root)); + } + + /** + * Recursively serializes the segment tree. + * + * @param SegmentTreeNode|null $node The current node + * @return array The serialized representation of the node + */ + private function serializeTree(?SegmentTreeNode $node): array + { + if ($node === null) { + return []; + } + return [ + 'start' => $node->start, + 'end' => $node->end, + 'value' => $node->value, + 'left' => $this->serializeTree($node->left), + 'right' => $this->serializeTree($node->right), + ]; + } + + /** + * Deserializes a JSON string into a SegmentTree object. + * + * @param string $data The JSON string to deserialize + * @return SegmentTree The deserialized segment tree + */ + public static function deserialize(string $data): self + { + $array = json_decode($data, true); + + $initialiseArray = array_fill(0, $array['end'] + 1, 0); + $segmentTree = new self($initialiseArray); + + $segmentTree->root = $segmentTree->deserializeTree($array); + return $segmentTree; + } + + /** + * Recursively deserializes a segment tree from an array representation. + * + * @param array $data The serialized data for the node + * @return SegmentTreeNode|null The deserialized node + */ + private function deserializeTree(array $data): ?SegmentTreeNode + { + if (empty($data)) { + return null; + } + $node = new SegmentTreeNode($data['start'], $data['end'], $data['value']); + + $node->left = $this->deserializeTree($data['left']); + $node->right = $this->deserializeTree($data['right']); + return $node; + } } diff --git a/composer.json b/composer.json index 534b9823..89db6716 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,8 @@ "license": "MIT", "require": { "php": "7.4", - "phan/phan": "^2.7" + "phan/phan": "^2.7", + "ext-json": "*" }, "require-dev": { "phpunit/phpunit": "^9", From 4cef497d407fb8745f181cc141b560490756aebe Mon Sep 17 00:00:00 2001 From: Ramy-Badr-Ahmed Date: Sun, 22 Sep 2024 04:13:02 +0200 Subject: [PATCH 25/38] Adding unit tests SegmentTree implementation --- tests/DataStructures/SegmentTreeTest.php | 97 ++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 tests/DataStructures/SegmentTreeTest.php diff --git a/tests/DataStructures/SegmentTreeTest.php b/tests/DataStructures/SegmentTreeTest.php new file mode 100644 index 00000000..eab39ad8 --- /dev/null +++ b/tests/DataStructures/SegmentTreeTest.php @@ -0,0 +1,97 @@ +testArray = [1, 3, 5, 7, 9, 11, 13, 14, 15, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26]; + } + + public static function sumQueryProvider(): array + { + return [ + // Format: [expectedResult, startIndex, endIndex] + [24, 1, 4], + [107, 5, 11], + [91, 2, 9], + [23, 15, 15], + ]; + } + + /** + * Test sum queries using data provider. + * @dataProvider sumQueryProvider + */ + public function testSegmentTreeSumQuery(int $expected, int $startIndex, int $endIndex): void + { + // Test the default case: sum query + $segmentTree = new SegmentTree($this->testArray); + $this->assertEquals( + $expected, + $segmentTree->query($startIndex, $endIndex), + "Query sum between index $startIndex and $endIndex should return $expected." + ); + } + + public static function maxQueryProvider(): array + { + return [ + [26, 0, 18], + [13, 2, 6], + [22, 8, 14], + [11, 5, 5], + ]; + } + + /** + * Test max queries using data provider. + * @dataProvider maxQueryProvider + */ + public function testSegmentTreeMaxQuery(int $expected, int $startIndex, int $endIndex): void + { + $segmentTree = new SegmentTree($this->testArray, fn($a, $b) => max($a, $b)); + $this->assertEquals( + $expected, + $segmentTree->query($startIndex, $endIndex), + "Max query between index $startIndex and $endIndex should return $expected." + ); + } + + public static function minQueryProvider(): array + { + return [ + [1, 0, 18], + [5, 2, 7], + [18, 10, 17], + [17, 9, 9], + ]; + } + + /** + * Test min queries using data provider. + * @dataProvider minQueryProvider + */ + public function testSegmentTreeMinQuery(int $expected, int $startIndex, int $endIndex): void + { + $segmentTree = new SegmentTree($this->testArray, function ($a, $b) { + return min($a, $b); + }); + $this->assertEquals( + $expected, + $segmentTree->query($startIndex, $endIndex), + "Query min between index $startIndex and $endIndex should return $expected." + ); + } +} From be4154091f0ff35020e20593c63937155e589225 Mon Sep 17 00:00:00 2001 From: Ramy-Badr-Ahmed Date: Sun, 22 Sep 2024 14:01:33 +0200 Subject: [PATCH 26/38] Added unit tests for SegmentTree updates and range updates --- tests/DataStructures/SegmentTreeTest.php | 38 ++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/DataStructures/SegmentTreeTest.php b/tests/DataStructures/SegmentTreeTest.php index eab39ad8..fcc3be7d 100644 --- a/tests/DataStructures/SegmentTreeTest.php +++ b/tests/DataStructures/SegmentTreeTest.php @@ -94,4 +94,42 @@ public function testSegmentTreeMinQuery(int $expected, int $startIndex, int $end "Query min between index $startIndex and $endIndex should return $expected." ); } + + public function testSegmentTreeUpdate(): void + { + // Test update functionality for different query types + // Sum Query + $segmentTreeSum = new SegmentTree($this->testArray); + $segmentTreeSum->update(2, 10); // Update index 2 to 10 + $this->assertEquals(29, $segmentTreeSum->query(1, 4), "After update, sum between index 1 and 4 should return 29."); + + // Max Query: with callback + $segmentTreeMax = new SegmentTree($this->testArray, fn($a, $b) => max($a, $b)); + $segmentTreeMax->update(12, -1); // Update index 12 to -1 + $this->assertEquals(19, $segmentTreeMax->query(5, 12), "After update, max between index 5 and 12 should return 19."); + + // Min Query: with callback + $segmentTreeMin = new SegmentTree($this->testArray, fn($a, $b) => min($a, $b)); + $segmentTreeMin->update(9, -5); // Update index 9 to -5 + $this->assertEquals(-5, $segmentTreeMin->query(9, 13), "After update, min between index 9 and 13 should return -5."); + } + + public function testSegmentTreeRangeUpdate(): void + { + // Test range update functionality for different query types + // Sum Query + $segmentTreeSum = new SegmentTree($this->testArray); + $segmentTreeSum->rangeUpdate(3, 7, 0); // Set indices 3 to 7 to 0 + $this->assertEquals(55, $segmentTreeSum->query(2, 10), "After range update, sum between index 2 and 10 should return 55."); + + // Max Query: with callback + $segmentTreeMax = new SegmentTree($this->testArray, fn($a, $b) => max($a, $b)); + $segmentTreeMax->rangeUpdate(3, 7, 0); // Set indices 3 to 7 to 0 + $this->assertEquals(5, $segmentTreeMax->query(2, 7), "After range update, max between index 2 and 7 should return 5."); + + // Min Query: with callback + $segmentTreeMin = new SegmentTree($this->testArray, fn($a, $b) => min($a, $b)); + $segmentTreeMin->rangeUpdate(3, 9, 0); // Set indices 3 to 9 to 0 + $this->assertEquals(0, $segmentTreeMin->query(2, 9), "After range update, min between index 2 and 9 should return 0."); + } } From 0fed0ad08f60350b9222765bcbbb76af9aa57232 Mon Sep 17 00:00:00 2001 From: Ramy-Badr-Ahmed Date: Sun, 22 Sep 2024 14:02:52 +0200 Subject: [PATCH 27/38] considering PHPCS for Added unit tests for SegmentTree updates and range updates --- tests/DataStructures/SegmentTreeTest.php | 36 ++++++++++++++++++++---- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/tests/DataStructures/SegmentTreeTest.php b/tests/DataStructures/SegmentTreeTest.php index fcc3be7d..741d232e 100644 --- a/tests/DataStructures/SegmentTreeTest.php +++ b/tests/DataStructures/SegmentTreeTest.php @@ -101,17 +101,29 @@ public function testSegmentTreeUpdate(): void // Sum Query $segmentTreeSum = new SegmentTree($this->testArray); $segmentTreeSum->update(2, 10); // Update index 2 to 10 - $this->assertEquals(29, $segmentTreeSum->query(1, 4), "After update, sum between index 1 and 4 should return 29."); + $this->assertEquals( + 29, + $segmentTreeSum->query(1, 4), + "After update, sum between index 1 and 4 should return 29." + ); // Max Query: with callback $segmentTreeMax = new SegmentTree($this->testArray, fn($a, $b) => max($a, $b)); $segmentTreeMax->update(12, -1); // Update index 12 to -1 - $this->assertEquals(19, $segmentTreeMax->query(5, 12), "After update, max between index 5 and 12 should return 19."); + $this->assertEquals( + 19, + $segmentTreeMax->query(5, 12), + "After update, max between index 5 and 12 should return 19." + ); // Min Query: with callback $segmentTreeMin = new SegmentTree($this->testArray, fn($a, $b) => min($a, $b)); $segmentTreeMin->update(9, -5); // Update index 9 to -5 - $this->assertEquals(-5, $segmentTreeMin->query(9, 13), "After update, min between index 9 and 13 should return -5."); + $this->assertEquals( + -5, + $segmentTreeMin->query(9, 13), + "After update, min between index 9 and 13 should return -5." + ); } public function testSegmentTreeRangeUpdate(): void @@ -120,16 +132,28 @@ public function testSegmentTreeRangeUpdate(): void // Sum Query $segmentTreeSum = new SegmentTree($this->testArray); $segmentTreeSum->rangeUpdate(3, 7, 0); // Set indices 3 to 7 to 0 - $this->assertEquals(55, $segmentTreeSum->query(2, 10), "After range update, sum between index 2 and 10 should return 55."); + $this->assertEquals( + 55, + $segmentTreeSum->query(2, 10), + "After range update, sum between index 2 and 10 should return 55." + ); // Max Query: with callback $segmentTreeMax = new SegmentTree($this->testArray, fn($a, $b) => max($a, $b)); $segmentTreeMax->rangeUpdate(3, 7, 0); // Set indices 3 to 7 to 0 - $this->assertEquals(5, $segmentTreeMax->query(2, 7), "After range update, max between index 2 and 7 should return 5."); + $this->assertEquals( + 5, + $segmentTreeMax->query(2, 7), + "After range update, max between index 2 and 7 should return 5." + ); // Min Query: with callback $segmentTreeMin = new SegmentTree($this->testArray, fn($a, $b) => min($a, $b)); $segmentTreeMin->rangeUpdate(3, 9, 0); // Set indices 3 to 9 to 0 - $this->assertEquals(0, $segmentTreeMin->query(2, 9), "After range update, min between index 2 and 9 should return 0."); + $this->assertEquals( + 0, + $segmentTreeMin->query(2, 9), + "After range update, min between index 2 and 9 should return 0." + ); } } From 850fc4b2e92b3ea8e3508caefbf189ac00ddacff Mon Sep 17 00:00:00 2001 From: Ramy-Badr-Ahmed Date: Sun, 22 Sep 2024 14:06:05 +0200 Subject: [PATCH 28/38] Added unit tests for SegmentTree serialization/deserialization and array updates reflections --- tests/DataStructures/SegmentTreeTest.php | 37 ++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/DataStructures/SegmentTreeTest.php b/tests/DataStructures/SegmentTreeTest.php index 741d232e..b57e2c4d 100644 --- a/tests/DataStructures/SegmentTreeTest.php +++ b/tests/DataStructures/SegmentTreeTest.php @@ -156,4 +156,41 @@ public function testSegmentTreeRangeUpdate(): void "After range update, min between index 2 and 9 should return 0." ); } + + public function testGetCurrentArray(): void + { + $segmentTree = new SegmentTree($this->testArray); + + // Ensure the initial array matches the input array + $this->assertEquals( + $this->testArray, + $segmentTree->getCurrentArray(), + "getCurrentArray() should return the initial array." + ); + + // Perform an update and test again + $segmentTree->update(2, 10); // Update index 2 to 10 + $updatedArray = $this->testArray; + $updatedArray[2] = 10; + + $this->assertEquals( + $updatedArray, + $segmentTree->getCurrentArray(), + "getCurrentArray() should return the updated array." + ); + } + + public function testSegmentTreeSerialization(): void + { + // Test serialization and deserialization + $segmentTree = new SegmentTree($this->testArray); + $serialized = $segmentTree->serialize(); + + $deserializedTree = SegmentTree::deserialize($serialized); + $this->assertEquals( + $segmentTree->query(1, 4), + $deserializedTree->query(1, 4), + "Serialized and deserialized trees should have the same query results." + ); + } } From eca97092e71f058ec8e4c2362ab9752ac18646a8 Mon Sep 17 00:00:00 2001 From: Ramy-Badr-Ahmed Date: Sun, 22 Sep 2024 14:08:45 +0200 Subject: [PATCH 29/38] Added unit tests for SegmentTree Edge Cases --- tests/DataStructures/SegmentTreeTest.php | 59 ++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/tests/DataStructures/SegmentTreeTest.php b/tests/DataStructures/SegmentTreeTest.php index b57e2c4d..f9beb27a 100644 --- a/tests/DataStructures/SegmentTreeTest.php +++ b/tests/DataStructures/SegmentTreeTest.php @@ -193,4 +193,63 @@ public function testSegmentTreeSerialization(): void "Serialized and deserialized trees should have the same query results." ); } + + public function testEdgeCases(): void + { + $segmentTree = new SegmentTree($this->testArray); + $firstIndex = 0; + $lastIndex = count($this->testArray) - 1; + + // Test querying the first and last indices + $this->assertEquals( + $this->testArray[$firstIndex], + $segmentTree->query($firstIndex, $firstIndex), + "Query at the first index should return {$this->testArray[$firstIndex]}." + ); + $this->assertEquals( + $this->testArray[$lastIndex], + $segmentTree->query($lastIndex, $lastIndex), + "Query at the last index should return {$this->testArray[$lastIndex]}." + ); + + + // Test updating the first index + $segmentTree->update($firstIndex, 100); // Update first index to 100 + $this->assertEquals( + 100, + $segmentTree->query($firstIndex, $firstIndex), + "After update, query at the first index should return {$this->testArray[$firstIndex]}." + ); + + // Test updating the last index + $segmentTree->update($lastIndex, 200); // Update last index to 200 + $this->assertEquals( + 200, + $segmentTree->query($lastIndex, $lastIndex), + "After update, query at the last index should return {$this->testArray[$lastIndex]}." + ); + + // Test range update that includes the first index + $segmentTree->rangeUpdate($firstIndex, 2, 50); // Set indices 0 to 2 to 50 + $this->assertEquals( + 50, + $segmentTree->query($firstIndex, $firstIndex), + "After range update, query at index $firstIndex should return 50." + ); + $this->assertEquals(50, $segmentTree->query(1, 1), "After range update, query at index 1 should return 50."); + $this->assertEquals(50, $segmentTree->query(2, 2), "After range update, query at index 2 should return 50."); + + // Test range update that includes the last index + $segmentTree->rangeUpdate($lastIndex - 3, $lastIndex, 10); // Set indices to 10 + $this->assertEquals( + 10, + $segmentTree->query($lastIndex, $lastIndex), + "After range update, query at the last index should return 10." + ); + $this->assertEquals( + 10, + $segmentTree->query(count($this->testArray) - 2, count($this->testArray) - 2), + "After range update, query at the second last index should return 10." + ); + } } From 628fc887825225ff4036d2a1cd4f5cbe65cb6de0 Mon Sep 17 00:00:00 2001 From: Ramy-Badr-Ahmed Date: Sun, 22 Sep 2024 14:19:03 +0200 Subject: [PATCH 30/38] Added unit tests for SegmentTree Exceptions (OutOfBoundsException, InvalidArgumentException) --- tests/DataStructures/SegmentTreeTest.php | 61 ++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/tests/DataStructures/SegmentTreeTest.php b/tests/DataStructures/SegmentTreeTest.php index f9beb27a..2fb717c8 100644 --- a/tests/DataStructures/SegmentTreeTest.php +++ b/tests/DataStructures/SegmentTreeTest.php @@ -252,4 +252,65 @@ public function testEdgeCases(): void "After range update, query at the second last index should return 10." ); } + public function testUnsupportedOrEmptyArrayInitialization(): void + { + // Test empty array + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage("Array must not be empty, must contain numeric values + and must be non-associative."); + + $segmentTreeEmpty = new SegmentTree([]); // expecting an exception + + // Test unsupported array (e.g., with non-numeric values) + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage("Array must not be empty, must contain numeric values + and must be non-associative."); + + $segmentTreeUnsupported = new SegmentTree([1, "two", 3]); // Mix of numeric and non-numeric + } + + public function testInvalidUpdateIndex(): void + { + // Test exception for invalid update index + $segmentTree = new SegmentTree($this->testArray); + + $index = count($this->testArray) + 5; + + // Expect an exception for range update with invalid indices + $this->expectException(OutOfBoundsException::class); + $this->expectExceptionMessage("Index out of bounds: $index. Must be between 0 and " + . (count($this->testArray) - 1)); + + $segmentTree->update($index, 100); // non-existing index, should trigger exception + } + + public function testOutOfBoundsQuery(): void + { + // Test exception for out-of-bounds query + $segmentTree = new SegmentTree($this->testArray); + + $start = 0; + $end = count($this->testArray); + + $this->expectException(OutOfBoundsException::class); + $this->expectExceptionMessage("Index out of bounds: start = $start, end = $end. + Must be between 0 and " . (count($this->testArray) - 1)); + + $segmentTree->query(0, count($this->testArray)); // expecting an exception + } + + public function testInvalidRangeUpdate(): void + { + // Test exception for invalid range update + $segmentTree = new SegmentTree($this->testArray); + + $start = -1; + $end = 5; + + // Expect an exception for range update with invalid indices + $this->expectException(OutOfBoundsException::class); + $this->expectExceptionMessage("Invalid range: start = $start, end = $end."); + + $segmentTree->rangeUpdate(-1, 5, 0); // Negative index, should trigger exception + } } From 7d0c07dfda8e98f2217c2438502db4c212acdc88 Mon Sep 17 00:00:00 2001 From: Ramy-Badr-Ahmed Date: Sun, 22 Sep 2024 14:23:28 +0200 Subject: [PATCH 31/38] Added SegmentTree to DIRECTORY.md --- DIRECTORY.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/DIRECTORY.md b/DIRECTORY.md index c1ece342..4783ea2e 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -27,6 +27,10 @@ * [Doublylinkedlist](./DataStructures/DoublyLinkedList.php) * [Node](./DataStructures/Node.php) * [Queue](./DataStructures/Queue.php) + * SegmentTree + * [SegmentTree](./DataStructures/SegmentTree/SegmentTree.php) + * [SegmentTreeNode](./DataStructures/SegmentTree/SegmentTreeNode.php) + * [Singlylinkedlist](./DataStructures/SinglyLinkedList.php) * [Stack](./DataStructures/Stack.php) * Trie @@ -125,6 +129,7 @@ * [Disjointsettest](./tests/DataStructures/DisjointSetTest.php) * [Doublylinkedlisttest](./tests/DataStructures/DoublyLinkedListTest.php) * [Queuetest](./tests/DataStructures/QueueTest.php) + * [SegmentTreeTest](./tests/DataStructures/SegmentTreeTest.php) * [Singlylinkedlisttest](./tests/DataStructures/SinglyLinkedListTest.php) * [Stacktest](./tests/DataStructures/StackTest.php) * [Trietest](./tests/DataStructures/TrieTest.php) From d61d0236531680bc7dd251c32de7f83770000482 Mon Sep 17 00:00:00 2001 From: Ramy-Badr-Ahmed Date: Sun, 22 Sep 2024 14:36:51 +0200 Subject: [PATCH 32/38] Implemented Segment Tree Data Structure --- DIRECTORY.md | 1 - DataStructures/SegmentTree/SegmentTree.php | 100 +++++++++--------- .../SegmentTree/SegmentTreeNode.php | 6 +- tests/DataStructures/SegmentTreeTest.php | 35 ++++-- 4 files changed, 82 insertions(+), 60 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 4783ea2e..34964991 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -30,7 +30,6 @@ * SegmentTree * [SegmentTree](./DataStructures/SegmentTree/SegmentTree.php) * [SegmentTreeNode](./DataStructures/SegmentTree/SegmentTreeNode.php) - * [Singlylinkedlist](./DataStructures/SinglyLinkedList.php) * [Stack](./DataStructures/Stack.php) * Trie diff --git a/DataStructures/SegmentTree/SegmentTree.php b/DataStructures/SegmentTree/SegmentTree.php index 87e5a738..e01e8e9b 100644 --- a/DataStructures/SegmentTree/SegmentTree.php +++ b/DataStructures/SegmentTree/SegmentTree.php @@ -14,14 +14,14 @@ class SegmentTree /** * Initializes the segment tree with the provided array and optional callback for aggregation. - * Default aggregation is Sum + * Default aggregation is Sum. * * Example usage: * $segmentTree = new SegmentTree($array, fn($a, $b) => max($a, $b)); * * @param array $arr The input array for the segment tree - * @param callable|null $callback Optional callback function for custom aggregation logic - * @throws InvalidArgumentException if the array is empty, contains non-numeric values, or is associative + * @param callable|null $callback Optional callback function for custom aggregation logic. + * @throws InvalidArgumentException if the array is empty, contains non-numeric values, or is associative. */ public function __construct(array $arr, callable $callback = null) { @@ -43,7 +43,7 @@ private function isUnsupportedArray(): bool } /** - * @return bool True if any element is non-numeric, false otherwise + * @return bool True if any element is non-numeric, false otherwise. */ private function isNonNumeric(): bool { @@ -51,7 +51,7 @@ private function isNonNumeric(): bool } /** - * @return bool True if the array is associative, false otherwise + * @return bool True if the array is associative, false otherwise. */ private function isAssociative(): bool { @@ -59,7 +59,7 @@ private function isAssociative(): bool } /** - * @return SegmentTreeNode The root node of the segment tree + * @return SegmentTreeNode The root node of the segment tree. */ public function getRoot(): SegmentTreeNode { @@ -77,21 +77,21 @@ public function getCurrentArray(): array /** * Builds the segment tree recursively. * - * @param array $arr The input array - * @param int $start The starting index of the segment - * @param int $end The ending index of the segment - * @return SegmentTreeNode The root node of the constructed segment + * @param array $arr The input array. + * @param int $start The starting index of the segment. + * @param int $end The ending index of the segment. + * @return SegmentTreeNode The root node of the constructed segment. */ private function buildTree(array $arr, int $start, int $end): SegmentTreeNode { - // Leaf node + // Leaf node if ($start == $end) { return new SegmentTreeNode($start, $end, $arr[$start]); } $mid = $start + (int)(($end - $start) / 2); - // Recursively build left and right children + // Recursively build left and right children $leftChild = $this->buildTree($arr, $start, $mid); $rightChild = $this->buildTree($arr, $mid + 1, $end); @@ -99,7 +99,7 @@ private function buildTree(array $arr, int $start, int $end): SegmentTreeNode ? ($this->callback)($leftChild->value, $rightChild->value) : $leftChild->value + $rightChild->value); - // Link the children to the parent node + // Link the children to the parent node $node->left = $leftChild; $node->right = $rightChild; @@ -109,10 +109,10 @@ private function buildTree(array $arr, int $start, int $end): SegmentTreeNode /** * Queries the aggregated value over a specified range. * - * @param int $start The starting index of the range - * @param int $end The ending index of the range - * @return int|float The aggregated value for the range - * @throws OutOfBoundsException if the range is invalid + * @param int $start The starting index of the range. + * @param int $end The ending index of the range. + * @return int|float The aggregated value for the range. + * @throws OutOfBoundsException if the range is invalid. */ public function query(int $start, int $end) { @@ -126,10 +126,10 @@ public function query(int $start, int $end) /** * Recursively queries the segment tree for a specific range. * - * @param SegmentTreeNode $node The current node - * @param int $start The starting index of the query range - * @param int $end The ending index of the query range - * @return int|float The aggregated value for the range + * @param SegmentTreeNode $node The current node. + * @param int $start The starting index of the query range. + * @param int $end The ending index of the query range. + * @return int|float The aggregated value for the range. */ private function queryTree(SegmentTreeNode $node, int $start, int $end) { @@ -139,7 +139,7 @@ private function queryTree(SegmentTreeNode $node, int $start, int $end) $mid = $node->start + (int)(($node->end - $node->start) / 2); - // Determine which segment of the tree to query + // Determine which segment of the tree to query if ($end <= $mid) { return $this->queryTree($node->left, $start, $end); // Query left child } elseif ($start > $mid) { @@ -158,9 +158,9 @@ private function queryTree(SegmentTreeNode $node, int $start, int $end) /** * Updates the value at a specified index in the segment tree. * - * @param int $index The index to update - * @param int|float $value The new value to set - * @throws OutOfBoundsException if the index is out of bounds + * @param int $index The index to update. + * @param int|float $value The new value to set. + * @throws OutOfBoundsException if the index is out of bounds. */ public function update(int $index, int $value): void { @@ -176,13 +176,13 @@ public function update(int $index, int $value): void /** * Recursively updates the segment tree. * - * @param SegmentTreeNode $node The current node - * @param int $index The index to update - * @param int|float $value The new value + * @param SegmentTreeNode $node The current node. + * @param int $index The index to update. + * @param int|float $value The new value. */ private function updateTree(SegmentTreeNode $node, int $index, $value): void { - // Leaf node + // Leaf node if ($node->start == $node->end) { $node->value = $value; return; @@ -190,14 +190,14 @@ private function updateTree(SegmentTreeNode $node, int $index, $value): void $mid = $node->start + (int)(($node->end - $node->start) / 2); - // Decide whether to go to the left or right child + // Decide whether to go to the left or right child if ($index <= $mid) { $this->updateTree($node->left, $index, $value); } else { $this->updateTree($node->right, $index, $value); } - // Recompute the value of the current node after the update + // Recompute the value of the current node after the update $node->value = $this->callback ? ($this->callback)($node->left->value, $node->right->value) : $node->left->value + $node->right->value; @@ -206,10 +206,10 @@ private function updateTree(SegmentTreeNode $node, int $index, $value): void /** * Performs a range update on a specified segment. * - * @param int $start The starting index of the range - * @param int $end The ending index of the range - * @param int|float $value The value to set for the range - * @throws OutOfBoundsException if the range is invalid + * @param int $start The starting index of the range. + * @param int $end The ending index of the range. + * @param int|float $value The value to set for the range. + * @throws OutOfBoundsException if the range is invalid. */ public function rangeUpdate(int $start, int $end, $value): void { @@ -218,21 +218,21 @@ public function rangeUpdate(int $start, int $end, $value): void } $this->rangeUpdateTree($this->root, $start, $end, $value); - // Update the original array to reflect the range update + // Update the original array to reflect the range update $this->currentArray = array_replace($this->currentArray, array_fill_keys(range($start, $end), $value)); } /** * Recursively performs a range update in the segment tree. * - * @param SegmentTreeNode $node The current node - * @param int $start The starting index of the range - * @param int $end The ending index of the range - * @param int|float $value The new value for the range + * @param SegmentTreeNode $node The current node. + * @param int $start The starting index of the range. + * @param int $end The ending index of the range. + * @param int|float $value The new value for the range. */ private function rangeUpdateTree(SegmentTreeNode $node, int $start, int $end, $value): void { - // Leaf node + // Leaf node if ($node->start == $node->end) { $node->value = $value; return; @@ -240,7 +240,7 @@ private function rangeUpdateTree(SegmentTreeNode $node, int $start, int $end, $v $mid = $node->start + (int)(($node->end - $node->start) / 2); - // Determine which segment of the tree to update (Left, Right, Split respectively) + // Determine which segment of the tree to update (Left, Right, Split respectively) if ($end <= $mid) { $this->rangeUpdateTree($node->left, $start, $end, $value); // Entire range is in the left child } elseif ($start > $mid) { @@ -251,7 +251,7 @@ private function rangeUpdateTree(SegmentTreeNode $node, int $start, int $end, $v $this->rangeUpdateTree($node->right, $mid + 1, $end, $value); } - // Recompute the value of the current node after the update + // Recompute the value of the current node after the update $node->value = $this->callback ? ($this->callback)($node->left->value, $node->right->value) : $node->left->value + $node->right->value; @@ -260,7 +260,7 @@ private function rangeUpdateTree(SegmentTreeNode $node, int $start, int $end, $v /** * Serializes the segment tree into a JSON string. * - * @return string The serialized segment tree as a JSON string + * @return string The serialized segment tree as a JSON string. */ public function serialize(): string { @@ -270,8 +270,8 @@ public function serialize(): string /** * Recursively serializes the segment tree. * - * @param SegmentTreeNode|null $node The current node - * @return array The serialized representation of the node + * @param SegmentTreeNode|null $node The current node. + * @return array The serialized representation of the node. */ private function serializeTree(?SegmentTreeNode $node): array { @@ -290,8 +290,8 @@ private function serializeTree(?SegmentTreeNode $node): array /** * Deserializes a JSON string into a SegmentTree object. * - * @param string $data The JSON string to deserialize - * @return SegmentTree The deserialized segment tree + * @param string $data The JSON string to deserialize. + * @return SegmentTree The deserialized segment tree. */ public static function deserialize(string $data): self { @@ -307,8 +307,8 @@ public static function deserialize(string $data): self /** * Recursively deserializes a segment tree from an array representation. * - * @param array $data The serialized data for the node - * @return SegmentTreeNode|null The deserialized node + * @param array $data The serialized data for the node. + * @return SegmentTreeNode|null The deserialized node. */ private function deserializeTree(array $data): ?SegmentTreeNode { diff --git a/DataStructures/SegmentTree/SegmentTreeNode.php b/DataStructures/SegmentTree/SegmentTreeNode.php index 8a6152f3..01af57b0 100644 --- a/DataStructures/SegmentTree/SegmentTreeNode.php +++ b/DataStructures/SegmentTree/SegmentTreeNode.php @@ -14,9 +14,9 @@ class SegmentTreeNode public ?SegmentTreeNode $right; /** - * @param int $start The starting index of the range - * @param int $end The ending index of the range - * @param int|float $value The initial aggregated value for this range (e.g. sum, min, or max) + * @param int $start The starting index of the range. + * @param int $end The ending index of the range. + * @param int|float $value The initial aggregated value for this range (e.g. sum, min, or max). * calculated using a callback. Defaults to sum. */ public function __construct(int $start, int $end, $value) diff --git a/tests/DataStructures/SegmentTreeTest.php b/tests/DataStructures/SegmentTreeTest.php index 2fb717c8..fe94723e 100644 --- a/tests/DataStructures/SegmentTreeTest.php +++ b/tests/DataStructures/SegmentTreeTest.php @@ -95,9 +95,11 @@ public function testSegmentTreeMinQuery(int $expected, int $startIndex, int $end ); } + /** + * Test update functionality for different query types. + */ public function testSegmentTreeUpdate(): void { - // Test update functionality for different query types // Sum Query $segmentTreeSum = new SegmentTree($this->testArray); $segmentTreeSum->update(2, 10); // Update index 2 to 10 @@ -126,9 +128,11 @@ public function testSegmentTreeUpdate(): void ); } + /** + * Test range update functionality for different query types. + */ public function testSegmentTreeRangeUpdate(): void { - // Test range update functionality for different query types // Sum Query $segmentTreeSum = new SegmentTree($this->testArray); $segmentTreeSum->rangeUpdate(3, 7, 0); // Set indices 3 to 7 to 0 @@ -157,6 +161,9 @@ public function testSegmentTreeRangeUpdate(): void ); } + /** + * Test array updates reflections. + */ public function testGetCurrentArray(): void { $segmentTree = new SegmentTree($this->testArray); @@ -180,9 +187,11 @@ public function testGetCurrentArray(): void ); } + /** + * Test serialization and deserialization of the segment tree. + */ public function testSegmentTreeSerialization(): void { - // Test serialization and deserialization $segmentTree = new SegmentTree($this->testArray); $serialized = $segmentTree->serialize(); @@ -194,6 +203,9 @@ public function testSegmentTreeSerialization(): void ); } + /** + * Testing EdgeCases: first and last indices functionality on the Segment Tree + */ public function testEdgeCases(): void { $segmentTree = new SegmentTree($this->testArray); @@ -252,6 +264,10 @@ public function testEdgeCases(): void "After range update, query at the second last index should return 10." ); } + + /** + * Test empty or unsupported arrays. + */ public function testUnsupportedOrEmptyArrayInitialization(): void { // Test empty array @@ -269,9 +285,12 @@ public function testUnsupportedOrEmptyArrayInitialization(): void $segmentTreeUnsupported = new SegmentTree([1, "two", 3]); // Mix of numeric and non-numeric } + + /** + * Test exception for invalid update index. + */ public function testInvalidUpdateIndex(): void { - // Test exception for invalid update index $segmentTree = new SegmentTree($this->testArray); $index = count($this->testArray) + 5; @@ -284,9 +303,11 @@ public function testInvalidUpdateIndex(): void $segmentTree->update($index, 100); // non-existing index, should trigger exception } + /** + * Test exception for invalid update index. + */ public function testOutOfBoundsQuery(): void { - // Test exception for out-of-bounds query $segmentTree = new SegmentTree($this->testArray); $start = 0; @@ -299,9 +320,11 @@ public function testOutOfBoundsQuery(): void $segmentTree->query(0, count($this->testArray)); // expecting an exception } + /** + * Test exception for invalid range update. + */ public function testInvalidRangeUpdate(): void { - // Test exception for invalid range update $segmentTree = new SegmentTree($this->testArray); $start = -1; From eaa6bb7047a58514aba2b6cc09192d82e63523a3 Mon Sep 17 00:00:00 2001 From: Ramy-Badr-Ahmed Date: Sun, 22 Sep 2024 12:37:56 +0000 Subject: [PATCH 33/38] updating DIRECTORY.md --- DIRECTORY.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 34964991..8e2c1b5b 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -27,9 +27,9 @@ * [Doublylinkedlist](./DataStructures/DoublyLinkedList.php) * [Node](./DataStructures/Node.php) * [Queue](./DataStructures/Queue.php) - * SegmentTree - * [SegmentTree](./DataStructures/SegmentTree/SegmentTree.php) - * [SegmentTreeNode](./DataStructures/SegmentTree/SegmentTreeNode.php) + * Segmenttree + * [Segmenttree](./DataStructures/SegmentTree/SegmentTree.php) + * [Segmenttreenode](./DataStructures/SegmentTree/SegmentTreeNode.php) * [Singlylinkedlist](./DataStructures/SinglyLinkedList.php) * [Stack](./DataStructures/Stack.php) * Trie @@ -128,7 +128,7 @@ * [Disjointsettest](./tests/DataStructures/DisjointSetTest.php) * [Doublylinkedlisttest](./tests/DataStructures/DoublyLinkedListTest.php) * [Queuetest](./tests/DataStructures/QueueTest.php) - * [SegmentTreeTest](./tests/DataStructures/SegmentTreeTest.php) + * [Segmenttreetest](./tests/DataStructures/SegmentTreeTest.php) * [Singlylinkedlisttest](./tests/DataStructures/SinglyLinkedListTest.php) * [Stacktest](./tests/DataStructures/StackTest.php) * [Trietest](./tests/DataStructures/TrieTest.php) From f0acc062488b8b174aea6b074e5ee5d300be245c Mon Sep 17 00:00:00 2001 From: Ramy-Badr-Ahmed Date: Sat, 28 Sep 2024 14:30:30 +0200 Subject: [PATCH 34/38] Added some comments to my files in: #160, #162, #163, #166. Implemented Segment Tree Data Structure. --- DataStructures/AVLTree/AVLTree.php | 8 ++++++++ DataStructures/AVLTree/AVLTreeNode.php | 8 ++++++++ DataStructures/DisjointSets/DisjointSet.php | 8 ++++++++ DataStructures/DisjointSets/DisjointSetNode.php | 8 ++++++++ DataStructures/SegmentTree/SegmentTree.php | 8 ++++++++ DataStructures/SegmentTree/SegmentTreeNode.php | 8 ++++++++ DataStructures/Trie/Trie.php | 8 ++++++++ DataStructures/Trie/TrieNode.php | 8 ++++++++ tests/DataStructures/AVLTreeTest.php | 8 ++++++++ tests/DataStructures/DisjointSetTest.php | 8 ++++++++ tests/DataStructures/SegmentTreeTest.php | 8 ++++++++ tests/DataStructures/TrieTest.php | 8 ++++++++ 12 files changed, 96 insertions(+) diff --git a/DataStructures/AVLTree/AVLTree.php b/DataStructures/AVLTree/AVLTree.php index a93b5f32..ff05ff86 100644 --- a/DataStructures/AVLTree/AVLTree.php +++ b/DataStructures/AVLTree/AVLTree.php @@ -1,5 +1,13 @@ Date: Sat, 28 Sep 2024 14:35:28 +0200 Subject: [PATCH 35/38] Added some comments to my files in: #160, #162, #163, #166. Implemented Segment Tree Data Structure. --- DataStructures/AVLTree/TreeTraversal.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/DataStructures/AVLTree/TreeTraversal.php b/DataStructures/AVLTree/TreeTraversal.php index 803a856a..b6812683 100644 --- a/DataStructures/AVLTree/TreeTraversal.php +++ b/DataStructures/AVLTree/TreeTraversal.php @@ -1,5 +1,13 @@ Date: Mon, 30 Sep 2024 09:48:01 +0200 Subject: [PATCH 36/38] Added comments time complexity for query(), update() and buildTree() --- DataStructures/SegmentTree/SegmentTree.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DataStructures/SegmentTree/SegmentTree.php b/DataStructures/SegmentTree/SegmentTree.php index 030231aa..fbc39a1f 100644 --- a/DataStructures/SegmentTree/SegmentTree.php +++ b/DataStructures/SegmentTree/SegmentTree.php @@ -83,7 +83,7 @@ public function getCurrentArray(): array } /** - * Builds the segment tree recursively. + * Builds the segment tree recursively. Takes O(n log n) in total. * * @param array $arr The input array. * @param int $start The starting index of the segment. @@ -115,7 +115,7 @@ private function buildTree(array $arr, int $start, int $end): SegmentTreeNode } /** - * Queries the aggregated value over a specified range. + * Queries the aggregated value over a specified range. Takes O(log n). * * @param int $start The starting index of the range. * @param int $end The ending index of the range. @@ -164,7 +164,7 @@ private function queryTree(SegmentTreeNode $node, int $start, int $end) } /** - * Updates the value at a specified index in the segment tree. + * Updates the value at a specified index in the segment tree. Takes O(log n). * * @param int $index The index to update. * @param int|float $value The new value to set. From 65c4849f32064d0554ed7e4d938d3d4b652fe540 Mon Sep 17 00:00:00 2001 From: Ramy-Badr-Ahmed Date: Thu, 3 Oct 2024 14:29:16 +0200 Subject: [PATCH 37/38] Implemented Splay Tree Data Structure --- DIRECTORY.md | 5 + DataStructures/SplayTree/SplayTree.php | 446 +++++++++++++ DataStructures/SplayTree/SplayTreeNode.php | 51 ++ .../SplayTree/SplayTreeRotations.php | 174 ++++++ tests/DataStructures/SplayTreeTest.php | 588 ++++++++++++++++++ 5 files changed, 1264 insertions(+) create mode 100644 DataStructures/SplayTree/SplayTree.php create mode 100644 DataStructures/SplayTree/SplayTreeNode.php create mode 100644 DataStructures/SplayTree/SplayTreeRotations.php create mode 100644 tests/DataStructures/SplayTreeTest.php diff --git a/DIRECTORY.md b/DIRECTORY.md index 9b673e8d..42b09322 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -31,6 +31,10 @@ * [Segmenttree](./DataStructures/SegmentTree/SegmentTree.php) * [Segmenttreenode](./DataStructures/SegmentTree/SegmentTreeNode.php) * [Singlylinkedlist](./DataStructures/SinglyLinkedList.php) + * Splaytree + * [Splaytree](./DataStructures/SplayTree/SplayTree.php) + * [Splaytreenode](./DataStructures/SplayTree/SplayTreeNode.php) + * [Splaytreerotations](./DataStructures/SplayTree/SplayTreeRotations.php) * [Stack](./DataStructures/Stack.php) * Trie * [Trie](./DataStructures/Trie/Trie.php) @@ -132,6 +136,7 @@ * [Queuetest](./tests/DataStructures/QueueTest.php) * [Segmenttreetest](./tests/DataStructures/SegmentTreeTest.php) * [Singlylinkedlisttest](./tests/DataStructures/SinglyLinkedListTest.php) + * [Splaytreetest](./tests/DataStructures/SplayTreeTest.php) * [Stacktest](./tests/DataStructures/StackTest.php) * [Trietest](./tests/DataStructures/TrieTest.php) * Graphs diff --git a/DataStructures/SplayTree/SplayTree.php b/DataStructures/SplayTree/SplayTree.php new file mode 100644 index 00000000..ac8e80b4 --- /dev/null +++ b/DataStructures/SplayTree/SplayTree.php @@ -0,0 +1,446 @@ + $value) { + $this->insert($key, $value); + } + } + + /** + * @return SplayTreeNode|NULL The root node of the Splay Tree. + */ + public function getRoot(): ?SplayTreeNode + { + return $this->root; + } + + /** + * Set the root node of the Splay Tree. + * @param SplayTreeNode $node + */ + public function setRoot(SplayTreeNode $node): void + { + $this->root = $node; + } + + /** + * Get the number of nodes in the Splay Tree. + */ + public function size(): int + { + return $this->counter; + } + + /** + * Check if current splay tree is empty + */ + public function isEmpty(): bool + { + return $this->root === null; + } + + /** + * Splay the given node to the root of the tree. + * Keep rotating until the node becomes the root. + * + * Time complexity: Amortized O(log n) + * + * @param SplayTreeNode|NULL $node The current node being splayed + * @param int $key The node's key being searched for and splayed + * @return SplayTreeNode|NULL Returns the new root after splaying + */ + protected function splay(?SplayTreeNode $node, int $key): ?SplayTreeNode + { + if ($node === null || $node->key === $key) { + return $node; + } + + return $node->key > $key + ? $this->splayLeft($node, $key) // Key is in the Left subtree + : $this->splayRight($node, $key); // Key is in the Right subtree + } + + /** + * Handles the splay operation when the key is located in the left subtree. + * This includes Zig-Zig (Left-Left), Zig-Zag (Left-Right), and Zig (Left) cases. + * + * Time complexity: Amortized O(log n). + * + * @param SplayTreeNode|null $node The root node of the subtree to splay + * @param int $key The key to splay to the root + * @return SplayTreeNode|null Returns the new root after splaying + */ + private function splayLeft(?SplayTreeNode $node, int $key): ?SplayTreeNode + { + if ($node->left === null) { + return $node; // Key not found in the left subtree + } + + if ($node->left->key > $key) { // Zig-Zig (Left-Left case) + $node->left->left = $this->splay($node->left->left, $key); + return $this->zigZig($node); + } elseif ($node->left->key < $key) { // Zig-Zag (Left-Right case) + $node->left->right = $this->splay($node->left->right, $key); + + if ($node->left->right !== null) { + return $this->zigZag($node); + } + } + // Zig (Left case) + return $node->left === null + ? $node + : $this->zig($node); + } + + /** + * Handles the splay operation when the key is located in the right subtree. + * This includes Zag-Zag (Right-Right), Zag-Zig (Right-Left), and Zag (Right) cases. + * + * Time complexity: Amortized O(log n). + * + * @param SplayTreeNode|null $node The root node of the subtree to splay + * @param int $key The key to splay to the root + * @return SplayTreeNode|null Returns the new root after splaying + */ + private function splayRight(?SplayTreeNode $node, int $key): ?SplayTreeNode + { + if ($node->right === null) { + return $node; + } + + if ($node->right->key < $key) { // Zag-Zag (Right-Right case) + $node->right->right = $this->splay($node->right->right, $key); + + return $this->zagZag($node); + } elseif ($node->right->key > $key) { // Zag-Zig (Right-Left case) + $node->right->left = $this->splay($node->right->left, $key); + + if ($node->right->left !== null) { + return $this->zagZig($node); + } + } + + // Zag (Right case) + return $node->right === null + ? $node + : $this->zag($node); + } + + /** + * Insert a new element into the splay tree following binary search tree insertion. + * Then, apply rotations to bring the newly inserted node to the root of the tree. + * + * Time complexity: O(log n) for the splay operation. + * + * @param int $key The node's key to insert + * @param mixed $value The value associated with the key + * @return SplayTreeNode|null Returns the new root after insertion and splaying + * @throws LogicException If the key already exists + */ + public function insert(int $key, $value): ?SplayTreeNode + { + $this->root = $this->insertNode($this->root, $key, $value); + $this->counter++; + + return $this->splay($this->root, $key); + } + + /** + * Insert a key-value pair into the Splay Tree. + * Recursively inserts a node in the BST fashion and update parent references. + * + * Time complexity: O(log n) for binary search tree insertion. + * + * @param SplayTreeNode|null $node The (sub)tree's root node to start inserting after + * @param int $key The node's key to insert + * @param mixed $value The value associated with the key + * @return SplayTreeNode|null Returns the new root after insertion + * @throws LogicException If the key already exists + */ + private function insertNode(?SplayTreeNode $node, int $key, $value): SplayTreeNode + { + if ($node === null) { + return new SplayTreeNode($key, $value); + } + + if ($key < $node->key) { + $node->left = $this->insertNode($node->left, $key, $value); + $node->left->parent = $node; + } elseif ($key > $node->key) { + $node->right = $this->insertNode($node->right, $key, $value); + $node->right->parent = $node; + } else { + throw new LogicException("Duplicate key: " . $key); + } + + return $node; + } + + /** + * Search for an element in the tree by performing a binary search tree search. + * If the node is found, apply rotations to bring it to the root of the tree. + * Otherwise, apply rotations to the last node visited in the search. + * + * Time complexity: O(log n) for the splay operation. + * + * @param int $key The node's key being searched + * @return SplayTreeNode|null Returns the new root of either the found node or the last visited + */ + public function search(int $key): ?SplayTreeNode + { + if ($this->isEmpty()) { + return null; + } + + // + $lastVisited = null; + + $node = $this->searchNode($this->root, $key, $lastVisited); + + $this->root = $node !== null + ? $this->splay($this->root, $key) + : $this->splay($this->root, $lastVisited->key); + + return $this->root; + } + + /** + * Recursively searches for a node in the BST fashion. + * + * Time complexity: O(log n) for binary search tree search. + * + * @param SplayTreeNode|null $node The (sub)tree's root node to start searching from + * @param int $key The node's key being searched + * @param SplayTreeNode|null $lastVisited Keep track of the last visited node + * @return SplayTreeNode|null Returns the new root of the found node or Null + */ + private function searchNode(?SplayTreeNode $node, int $key, ?SplayTreeNode &$lastVisited): ?SplayTreeNode + { + if ($node === null) { + return null; + } + + $lastVisited = $node; + + if ($key < $node->key) { + return $this->searchNode($node->left, $key, $lastVisited); + } elseif ($key > $node->key) { + return $this->searchNode($node->right, $key, $lastVisited); + } else { + return $node; + } + } + + /** + * Check if a node with the given key exists in the tree. + * Apply rotations to the searched node or to the last visited node to the root of the tree. + * + * Time complexity: O(log n) for the splay operation. + * + * @param int $key The key of the node being searched + * @return bool True if the node exists, false otherwise + */ + public function isFound(int $key): bool + { + $foundNode = $this->search($key); + return $foundNode && $foundNode->key === $key; + } + + /** + * Updates the value of the node with the given key. + * If the node is found, update its value and apply rotations to bring it to the root of the tree. + * Otherwise, apply rotations to the last node visited in the search. + * + * Time complexity: O(log n) for the splay operation. + * + * @param int $key The key of the node to update + * @param mixed $value The new value to set + * @return SplayTreeNode|null Returns the root of the tree after the update or the last visited + */ + public function update(int $key, $value): ?SplayTreeNode + { + if ($this->isFound($key)) { + $this->root->value = $value; + } + return $this->root; + } + + /** + * Deletes the node with the given key from the tree. + * Splays the node to be deleted to the root, then isolates it and restructures the tree. + * If the key doesn't exist, apply rotations to bring the closest node to the root. + * + * Time complexity: O(log n) for the splay operation and O(log n) for restructuring the tree. + * + * @param int $key The key of the node to delete + * @return SplayTreeNode|null The new root after the deletion + * @throws LogicException If the key is not found in the tree + */ + public function delete(int $key): ?SplayTreeNode + { + if (!$this->isFound($key)) { + throw new LogicException("Key: " . $key . " not found in tree. Splayed the last visited node."); + } + + $leftSubtree = $this->root->left; + $rightSubtree = $this->root->right; + + $this->isolateRoot(); + $this->counter--; + return $this->restructureAfterDeletion($leftSubtree, $rightSubtree); + } + + /** + * Isolates the root node by breaking its connections. + */ + private function isolateRoot(): void + { + if ($this->root->left !== null) { + $this->root->left->parent = null; + } + if ($this->root->right !== null) { + $this->root->right->parent = null; + } + + $this->root->left = null; + $this->root->right = null; + } + + /** + * Restructures the tree after the root node has been isolated for deletion. + * + * @return SplayTreeNode|null The new root after restructuring + */ + private function restructureAfterDeletion(?SplayTreeNode $leftSubtree, ?SplayTreeNode $rightSubtree): ?SplayTreeNode + { + if ($leftSubtree === null) { + return $this->handleEmptyLeftSubtree($rightSubtree); + } + + return $this->mergeSubtrees($leftSubtree, $rightSubtree); + } + + /** + * Handles the case when the left subtree is empty after deletion. + * + * @param SplayTreeNode|null $rightSubtreeRoot The root of the right subtree + * @return SplayTreeNode|null The new root + */ + private function handleEmptyLeftSubtree(?SplayTreeNode $rightSubtreeRoot): ?SplayTreeNode + { + $this->root = $rightSubtreeRoot; + if ($this->root !== null) { + $this->root->parent = null; + } + return $this->root; + } + + /** + * Merges the left and right subtrees after deletion. + * + * @param SplayTreeNode $leftSubtreeRoot The root of the left subtree + * @param SplayTreeNode|null $rightSubtreeRoot The root of the right subtree + * @return SplayTreeNode The new root after merging + */ + private function mergeSubtrees(SplayTreeNode $leftSubtreeRoot, ?SplayTreeNode $rightSubtreeRoot): SplayTreeNode + { + $maxLeftNode = $this->maxNode($leftSubtreeRoot); + $this->root = $maxLeftNode; + + $this->detachMaxNodeFromLeftSubtree($maxLeftNode, $leftSubtreeRoot); + $this->attachRightSubtree($rightSubtreeRoot); + + return $this->root; + } + + /** + * Detaches the max node from the left subtree and reattaches the left subtree to the new root. + * + * @param SplayTreeNode $maxLeftNode The max node of the left subtree + * @param SplayTreeNode $leftSubtreeRoot The root of the left subtree + */ + private function detachMaxNodeFromLeftSubtree(SplayTreeNode $maxLeftNode, SplayTreeNode $leftSubtreeRoot): void + { + $maxLeftNodeParent = $maxLeftNode->parent; + if ($maxLeftNodeParent !== null) { + $maxLeftNodeParent->right = null; + $this->root->left = $leftSubtreeRoot; + $leftSubtreeRoot->parent = $this->root; + } + $maxLeftNode->parent = null; + } + + /** + * Attaches the right subtree to the new root. + * + * @param SplayTreeNode|null $rightSubtreeRoot The root of the right subtree + */ + private function attachRightSubtree(?SplayTreeNode $rightSubtreeRoot): void + { + $this->root->right = $rightSubtreeRoot; + if ($rightSubtreeRoot !== null) { + $rightSubtreeRoot->parent = $this->root; + } + } + + /** + * Finds the node with the maximum key in the given subtree. + * + * Time complexity: O(log n) for finding the maximum node in the subtree. + * + * @param SplayTreeNode|null $node The subtree to search for the maximum node + * @return SplayTreeNode|null The node with the maximum key, or null if the subtree is empty + */ + public function maxNode(?SplayTreeNode $node): ?SplayTreeNode + { + if ($node === null) { + return null; + } + return $node->right === null + ? $node + : $this->maxNode($node->right); + } + + /** + * Perform an in-order traversal of the Splay Tree. + * + * @param SplayTreeNode|null $node The root node to start traversing from + * @return array Representation of the traversed nodes + */ + public function inOrderTraversal(?SplayTreeNode $node): array + { + $result = []; + if ($node !== null) { + $result = array_merge($result, $this->inOrderTraversal($node->left)); + $result[] = [$node->key => $node->value]; + $result = array_merge($result, $this->inOrderTraversal($node->right)); + } + return $result; + } +} diff --git a/DataStructures/SplayTree/SplayTreeNode.php b/DataStructures/SplayTree/SplayTreeNode.php new file mode 100644 index 00000000..a11df33d --- /dev/null +++ b/DataStructures/SplayTree/SplayTreeNode.php @@ -0,0 +1,51 @@ +key = $key; + $this->value = $value; + + // Set all node pointers to null initially + $this->left = null; + $this->right = null; + $this->parent = null; + } + + public function isLeaf(): bool + { + return $this->left === null && $this->right === null; + } + + public function isRoot(): bool + { + return $this->parent === null; + } +} diff --git a/DataStructures/SplayTree/SplayTreeRotations.php b/DataStructures/SplayTree/SplayTreeRotations.php new file mode 100644 index 00000000..ec4ad36b --- /dev/null +++ b/DataStructures/SplayTree/SplayTreeRotations.php @@ -0,0 +1,174 @@ +rotateRight($node); + } + + /** + * Zag rotation (single left rotation). + * Performs a left rotation on the given node. + * A case where the node is directly a right child of its parent. + * + * @param SplayTreeNode $node The node to be rotated. + * @return SplayTreeNode The new root of the subtree after rotation. + */ + protected function zag(SplayTreeNode $node): SplayTreeNode + { + return $this->rotateLeft($node); + } + + /** + * Zig-Zig rotation (double right rotation). + * Performs two consecutive right rotations on the given node. The first right rotation is applied to + * the node’s parent, and the second one to the node’s new parent (the previous grandparent). + * + * @param SplayTreeNode $node The node to be rotated. + * @return SplayTreeNode The new root of the subtree after the rotations. + */ + protected function zigZig(SplayTreeNode $node): SplayTreeNode + { + $node = $this->rotateRight($node); + return $this->rotateRight($node); + } + + /** + * Zag-Zag rotation (double left rotation). + * Performs two consecutive left rotations on the given node. The first left rotation is applied to + * the node’s parent, and the second one to the node’s new parent (the previous grandparent). + * + * @param SplayTreeNode $node The node to be rotated. + * @return SplayTreeNode The new root of the subtree after the rotations. + */ + protected function zagZag(SplayTreeNode $node): SplayTreeNode + { + $node = $this->rotateLeft($node); + return $this->rotateLeft($node); + } + + /** + * Zig-Zag rotation (left-right rotation). + * Performs a left rotation on the left child followed by a right rotation on the node itself. + * + * A case when the target key is in the right subtree of the left child. + * + * @param SplayTreeNode $node The node to be rotated. + * @return SplayTreeNode The new root of the subtree after the rotations. + */ + protected function zigZag(SplayTreeNode $node): SplayTreeNode + { + $node->left = $this->rotateLeft($node->left); + return $this->rotateRight($node); + } + + /** + * Zag-Zig rotation (right-left rotation). + * Performs a right rotation on the right child followed by a left rotation on the node itself. + * + * A case when the target key is in the left subtree of the right child. + * + * @param SplayTreeNode $node The node to be rotated. + * @return SplayTreeNode The new root of the subtree after the rotations. + */ + protected function zagZig(SplayTreeNode $node): SplayTreeNode + { + $node->right = $this->rotateRight($node->right); + return $this->rotateLeft($node); + } + + /** + * Rotates the given node to the left, bringing its right child up to take its place. + * The left subtree of the node's right child will become the new right subtree of the node. + * + * @param SplayTreeNode $node The node to be rotated. + * @return SplayTreeNode The new root of the subtree after the rotation (the former right child). + */ + private function rotateLeft(SplayTreeNode $node): SplayTreeNode + { + $rightChild = $node->right; + + if ($rightChild === null) { + return $node; // No rotation possible + } + + $node->right = $rightChild->left; + + if ($rightChild->left !== null) { + $rightChild->left->parent = $node; + } + + $rightChild->parent = $node->parent; + + if ($node->parent === null) { + static::setRoot($rightChild); + } elseif ($node === $node->parent->left) { + $node->parent->left = $rightChild; + } else { + $node->parent->right = $rightChild; + } + + $rightChild->left = $node; + $node->parent = $rightChild; + + return $rightChild; + } + + /** + * Rotates the given node to the right, bringing its left child up to take its place. + * The right subtree of the node's left child will become the new left subtree of the node. + * + * @param SplayTreeNode $node The node to be rotated. + * @return SplayTreeNode The new root of the subtree after the rotation (the former left child). + */ + private function rotateRight(SplayTreeNode $node): SplayTreeNode + { + $leftChild = $node->left; + + if ($leftChild === null) { + return $node; // No rotation possible + } + + $node->left = $leftChild->right; + + if ($leftChild->right !== null) { + $leftChild->right->parent = $node; + } + + $leftChild->parent = $node->parent; + + if ($node->parent === null) { + static::setRoot($leftChild); + } elseif ($node === $node->parent->right) { + $node->parent->right = $leftChild; + } else { + $node->parent->left = $leftChild; + } + + $leftChild->right = $node; + $node->parent = $leftChild; + + return $leftChild; + } +} diff --git a/tests/DataStructures/SplayTreeTest.php b/tests/DataStructures/SplayTreeTest.php new file mode 100644 index 00000000..7709cf2e --- /dev/null +++ b/tests/DataStructures/SplayTreeTest.php @@ -0,0 +1,588 @@ +tree = new SplayTree(); + } + + private function populateTree(): void + { + $this->tree->insert(20, "Value 20"); + $this->tree->insert(15, "Value 15"); + $this->tree->insert(17, "Value 17"); + $this->tree->insert(25, "Value 25"); + $this->tree->insert(30, "Value 30"); + $this->tree->insert(36, "Value 36"); + $this->tree->insert(23, "Value 23"); + $this->tree->insert(24, "Value 24"); + $this->tree->insert(22, "Value 22"); + $this->tree->insert(5, "Value 5"); + } + + public function testTreeInitialization() + { + $tree = new SplayTree(); + + $this->assertNull($tree->getRoot(), "Tree root should be null upon initialization."); + $this->assertEquals(0, $tree->size(), "Tree size should be 0 upon initialization."); + $this->assertTrue($tree->isEmpty(), "Tree should be empty upon initialization."); + } + + /** + * Checks if the root node is correctly set after one insertion. + */ + public function testInsertSingleElement() + { + $this->tree->insert(10, "Value 10"); + $root = $this->tree->getRoot(); + + $this->assertFalse($this->tree->isEmpty(), "Tree cannot be empty. Insertion failed."); + $this->assertNotNull($root, "Tree has one node and its root cannot be Null"); + + $this->assertEquals(10, $root->key, "The key must match the key of the inserted node"); + $this->assertEquals("Value 10", $root->value, "The value must match the value of the inserted node"); + + $this->assertTrue($root->isRoot(), "Tree root must not have a parent"); + $this->assertTrue($root->isLeaf(), "Root node has no children yet"); + } + + /** + * Inserts multiple nodes and checks if the last inserted node is splayed to the root. + */ + public function testInsertMultiple() + { + $this->populateTree(); + + $root = $this->tree->getRoot(); + + $this->assertFalse($this->tree->isEmpty(), "Tree was not populated correctly"); + $this->assertSame(10, $this->tree->size(), "Failed to insert all 10 nodes"); + + $this->assertEquals(5, $root->key, "After splay, the last inserted node should be the new root."); + $this->assertEquals("Value 5", $root->value, "The value of the new root must match the last inserted node"); + + $this->assertTrue($root->isRoot(), "The last inserted node has no longer a parent. Failed to splay correctly."); + $this->assertFalse($root->isLeaf(), "The last inserted node is no longer a leaf. Failed to splay correctly."); + } + + /** + * Inserts multiple nodes from an associative array and checks if the last inserted node is splayed to the root. + */ + public function testInsertMultipleFromArray() + { + $arrayData = [200 => "Value 200", 150 => "Value 150", 170 => "Value 170", + 250 => "Value 250", 300 => "Value 300", 360 => "Value 360", 230 => "Value 230", + 240 => "Value 240", 220 => "Value 220", 50 => "Value 50" + ]; + + $splayTree = new SplayTree($arrayData); + $root = $splayTree->getRoot(); + + $this->assertFalse($splayTree->isEmpty(), "Tree was not populated correctly"); + $this->assertSame( + count($arrayData), + $splayTree->size(), + "Failed to insert all " . count($arrayData) . " nodes" + ); + + $this->assertEquals(50, $root->key, "After splay, the new root should be the last inserted node"); + $this->assertEquals("Value 50", $root->value, "The value of the new root must match the last inserted node"); + + $this->assertTrue($root->isRoot(), "The last inserted node has no longer a parent. Failed to splay correctly."); + $this->assertFalse($root->isLeaf(), "The last inserted node is no longer a leaf. Failed to splay correctly."); + } + + /** + * Checks the empty state of the tree before and after insertions. + */ + public function testIsEmpty() + { + $this->assertTrue($this->tree->isEmpty(), "Tree should be empty."); + $this->tree->insert(120, "Value 120"); + $this->assertFalse($this->tree->isEmpty(), "Tree should not be empty."); + } + + /** + * Data provider for splay insertion and inOrder traversal test. + * Provides different sets of insertions and expected results. + * Format: [nodesInserted, InOrderNodeKeys, rootNodeKey] + */ + public static function splayInsertionDataProvider(): array + { + return [ + // Test case 1: Insert 20 + [ + 'insertions' => [20 => "Value 20"], + 'expectedInOrderKeys' => [20], + 'expectedRootKey' => 20, + ], + // Test case 2: Insert 20, 15 + [ + 'insertions' => [20 => "Value 20", 15 => "Value 15"], + 'expectedInOrderKeys' => [15, 20], + 'expectedRootKey' => 15, + ], + // Test case 3: Insert 20, 15, 17 + [ + 'insertions' => [20 => "Value 20", 15 => "Value 15", 17 => "Value 17"], + 'expectedInOrderKeys' => [15, 17, 20], + 'expectedRootKey' => 17, + ], + // Test case 25: Insert 20, 15, 17, 25 + [ + 'insertions' => [20 => "Value 20", 15 => "Value 15", 17 => "Value 17", 25 => "Value 25"], + 'expectedInOrderKeys' => [15, 17, 20, 25], + 'expectedRootKey' => 25, + ], + // Test case 30: Insert 20, 15, 17, 25, 30 + [ + 'insertions' => [20 => "Value 20", 15 => "Value 15", 17 => "Value 17", 25 => "Value 25", + 30 => "Value 30"], + 'expectedInOrderKeys' => [15, 17, 20, 25, 30], + 'expectedRootKey' => 30, + ], + // Test case 36: Insert 20, 15, 17, 25, 30, 36 + [ + 'insertions' => [20 => "Value 20", 15 => "Value 15", 17 => "Value 17", 25 => "Value 25", + 30 => "Value 30", 36 => "Value 36"], + 'expectedInOrderKeys' => [15, 17, 20, 25, 30, 36], + 'expectedRootKey' => 36, + ], + // Test case 23: Insert 20, 15, 17, 25, 30, 36, 23 + [ + 'insertions' => [20 => "Value 20", 15 => "Value 15", 17 => "Value 17", 25 => "Value 25", + 30 => "Value 30", 36 => "Value 36", 23 => "Value 23"], + 'expectedInOrderKeys' => [15, 17, 20, 23, 25, 30, 36], + 'expectedRootKey' => 23, + ], + // Test case 24: Insert 20, 15, 17, 25, 30, 36, 23, 24 + [ + 'insertions' => [20 => "Value 20", 15 => "Value 15", 17 => "Value 17", 25 => "Value 25", + 30 => "Value 30", 36 => "Value 36", 23 => "Value 23", 24 => "Value 24"], + 'expectedInOrderKeys' => [15, 17, 20, 23, 24, 25, 30, 36], + 'expectedRootKey' => 24, + ], + // Test case 22: Insert 20, 15, 17, 25, 30, 36, 23, 24, 22 + [ + 'insertions' => [20 => "Value 20", 15 => "Value 15", 17 => "Value 17", 25 => "Value 25", + 30 => "Value 30", 36 => "Value 36", 23 => "Value 23", 24 => "Value 24", 22 => "Value 22"], + 'expectedInOrderKeys' => [15, 17, 20, 22, 23, 24, 25, 30, 36], + 'expectedRootKey' => 22, + ], + // Test case 5: Insert 20, 15, 17, 25, 30, 36, 23, 24, 22, 5 + [ + 'insertions' => [20 => "Value 20", 15 => "Value 15", 17 => "Value 17", 25 => "Value 25", 30 => + "Value 30", 36 => "Value 36", 23 => "Value 23", 24 => "Value 24", 22 => "Value 22", 5 => "Value 5"], + 'expectedInOrderKeys' => [5, 15, 17, 20, 22, 23, 24, 25, 30, 36], + 'expectedRootKey' => 5, + ], + ]; + } + + /** + * Test tree structure with inOrder Traversal after insertion and splaying nodes. + * @dataProvider splayInsertionDataProvider + */ + public function testSplayWithInOderTraversal($insertions, $expectedInOrderKeys, $expectedRootKey): void + { + $tree = new SplayTree(); + + // Insert nodes and splay + foreach ($insertions as $key => $value) { + $tree->insert($key, $value); + } + + // Traverse the tree structure wit inOrder Traversal + $inOrderArray = $tree->inOrderTraversal($tree->getRoot()); + $inOrderArrayKeys = $this->getInOrderKeys($inOrderArray); + + // Assert the in-order traversal keys match the expected keys for every dataProvider case + $this->assertEquals( + $expectedInOrderKeys, + $inOrderArrayKeys, + 'Tree structure after splay is not correct. The in-order traversal is not correct.' + ); + + // Assert the root key matches the expected root after the last insertion for every dataProvider case + $this->assertTrue( + $tree->getRoot()->key === $inOrderArrayKeys[array_search($expectedRootKey, $expectedInOrderKeys)], + "Node was not splayed to root successfully" + ); + // Assert the new root is correctly set + $this->assertTrue($tree->getRoot()->isRoot(), "Node with key $expectedRootKey should be the new tree root"); + } + + /** + * Helper function to extract keys from the in-order traversal array. + */ + private function getInOrderKeys(array $inOrderArray): array + { + $inOrderArrayKeys = []; + foreach ($inOrderArray as $node) { + $inOrderArrayKeys = array_merge($inOrderArrayKeys, array_keys($node)); + } + return $inOrderArrayKeys; + } + + // ------------- Test Operations on Splay Tree ------------- + + /** + * Verifies that searching for an existing key returns the correct node + * and ensures that it is splayed to the root. + */ + public function testSearchExistingKey() + { + $this->populateTree(); + + $node = $this->tree->search(22); + + $this->assertNotNull($node, "Returned node cannot be Null."); + $this->assertNull($node->parent, "The searched node must have become the new root with has no parent"); + $this->assertTrue( + $node->isRoot(), + "The searched node must have become the new root. Failed to splay it correctly." + ); + + $this->assertEquals(22, $node->key, "Node with key 22 should be returned. Got a non-expected key: $node->key"); + $this->assertEquals( + "Value 22", + $node->value, + "Value of Node with key 22 does not match. Got a non-expected value: $node->value" + ); + } + + /** + * Verifies that checking for an existing key returns true + * and ensures that its node is splayed to the root. + */ + public function testIsFoundExistingKey() + { + $this->populateTree(); + + $isFound = $this->tree->isFound(25); + $node = $this->tree->getRoot(); + + $this->assertTrue($isFound, "Node with key 25 exists."); + $this->assertEquals(25, $node->key, "Node with key 25 should be returned. Got a non-expected key: $node->key"); + + $this->assertTrue( + $node->isRoot(), + "The searched node must have become the new root. Failed to splay it correctly." + ); + } + + /** + * Ensures that searching for a non-existing key returns the last visit node + * and ensures that it is splayed to the root. + */ + public function testSearchNonExistingKey() + { + $this->populateTree(); + + $node = $this->tree->search(250); // Search for a non-existing key + + $this->assertNotNull($node, "Returned node cannot be Null."); + $this->assertEquals( + 36, + $node->key, + "Node key: 36 should be returned. Got a Non-expected key: $node->key + . Failed to splay the last visited node." + ); + + $this->assertEquals( + "Value 36", + $node->value, + "Value of node 36 does not match. Got a Non-expected value: $node->value" + ); + + $this->assertNull( + $node->parent, + "The last visited node must have become the new root with has no parent. Failed to splay correctly." + ); + } + + /** + * Verifies that checking for a non-existing key returns false + * and ensures that the last visited node is splayed to the root. + */ + public function testIsFoundNonExistingKey() + { + $this->populateTree(); + + $isFound = $this->tree->isFound(18); + $node = $this->tree->getRoot(); + + $this->assertFalse($isFound, "Node with key 18 does not exist."); + $this->assertEquals( + 17, + $node->key, + "Node key: 17 should be returned. Got a Non-expected key: $node->key + . Failed to splay the last visited node." + ); + $this->assertTrue( + $node->isRoot(), + "The last visited node must have become the new root. Failed to splay it correctly." + ); + } + + /** + * Tests the update functionality on an existing key and ensures its node is splayed to the root. + */ + public function testUpdateExistingKey() + { + $this->populateTree(); + + $this->tree->update(36, 360); + + $node = $this->tree->search(36); + + $this->assertNotNull($node, "Node with key 36 should exist after update."); + $this->assertEquals(360, $node->value, "Node with key 36 should have the updated value."); + $this->assertEquals(36, $node->key, "Node with key 36 should be returned. Got a non-expected key: $node->key"); + $this->assertTrue( + $node->isRoot(), + "The updated node must have become the new root. Failed to splay it correctly." + ); + } + + /** + * Checks that updating a non-existing key splays the last visited node only. + */ + public function testUpdateNonExistingKey() + { + $this->populateTree(); + + $node = $this->tree->update(60, "Value 60"); // Update a non-existing key + + $this->assertNotNull($node, "Returned node cannot be Null"); + $this->assertEquals( + 36, + $node->key, + "Node key: 36 should be returned. Got a Non-expected key: $node->key + . Failed to splay the last visited node." + ); + $this->assertEquals( + "Value 36", + $node->value, + "Value of node 36 does not match. Got a Non-expected value: $node->value" + ); + $this->assertNull( + $node->parent, + "The last visited node must have become the new root with has no parent. Failed to splay correctly." + ); + } + + /** + * Tests deletion of a node and checks if the correct new root is set after merging the two subtrees. + */ + public function testDeleteExistingKey() + { + $this->populateTree(); + + $node = $this->tree->delete(22); + $isFound = $this->tree->isFound(22); + + $this->assertFalse($isFound, "Node with key 22 was not deleted."); + $this->assertEquals( + 20, + $node->key, + "After deleting 22, the new root should be the node with key 20." + ); + } + + /** + * Tests correct subtree merging after deletion of a splayed node to the root. + */ + public function testMergeAfterDeleteExistingKey() + { + $this->populateTree(); + + $node = $this->tree->delete(20); + + $inOrderTraversalNodes = $this->tree->inOrderTraversal($this->tree->getRoot()); + $inOrderArrayKeys = $this->getInOrderKeys($inOrderTraversalNodes); + + $expectedInOrderKeys = [5, 15, 17, 22, 23, 24, 25, 30, 36]; + + $this->assertEquals( + 17, + $node->key === $inOrderArrayKeys[array_search($node->key, $expectedInOrderKeys)], + "After deleting 20, the new root should be the node with key 17." + ); + + // Assert the in-order traversal keys match the expected keys + $this->assertEquals( + $expectedInOrderKeys, + $inOrderArrayKeys, + 'Tree structure after splay is not correct. + The in-order traversal is not correct. Failed to merge subtrees.' + ); + } + + /** + * Ensures that attempting to delete a non-existing key throws an exception and keeps the tree intact. + */ + public function testDeleteNonExistingKey() + { + $this->populateTree(); + + $root = $this->tree->getRoot(); + + $this->expectException(LogicException::class); + $this->expectExceptionMessage("Key: 90 not found in tree. Splayed the last visited node."); + + $this->tree->delete(90); // Delete a non-existing key + $this->assertEquals(5, $root->key, "The tree root should not have been changed."); + } + + /** + * Tests update, search, size, isFound and delete operations on an empty tree. + */ + public function testOperationsOnEmptyTree() + { + $this->assertEquals(0, $this->tree->size(), "Tree should be empty."); + + $rootNode1 = $this->tree->search(100); + $this->assertNull($rootNode1, "Searching for a key in an empty tree should return null."); + + $rootNode2 = $this->tree->isFound(200); + $this->assertFalse($rootNode2, "Searching for a key in an empty tree should return null."); + + $rootNode3 = $this->tree->update(100, "Value 100"); + $this->assertNull($rootNode3, "Updating a key in an empty tree should return null."); + + $this->expectException(LogicException::class); + $rootNode4 = $this->tree->delete(100); + $this->assertNull($rootNode4, "Deleting a key in an empty tree should return null."); + } + + + // ------------- Test 6 Rotation types of the Splay Tree ------------- + + /** + * Verify the structure after the Zig rotation + */ + public function testZigRotation(): void + { + $tree = new SplayTree(); + $this->populateTree(); + + $tree->insert(20, 'A'); + $tree->insert(10, 'B'); // Trigger a Zig rotation when 10 is splayed + + $root = $tree->getRoot(); + $this->assertSame(10, $root->key, 'Root should be 10 after Zig rotation'); + $this->assertNull($root->parent, "Root parent is Null after Zig rotation"); + $this->assertSame(20, $root->right->key, '20 should be the right child of 10 after Zig rotation'); + } + + /** + * Verify the structure after the Zag rotation + */ + public function testZagRotation(): void + { + $tree = new SplayTree(); + + $tree->insert(10, 'A'); + $tree->insert(20, 'B'); // Trigger a Zag rotation when 20 is splayed + + $root = $tree->getRoot(); + $this->assertSame(20, $root->key, 'Root should be 20 after Zag rotation'); + $this->assertNull($root->parent, "Root parent is Null after Zig rotation"); + $this->assertSame(10, $root->left->key, '10 should be the left child of 20 after Zag rotation'); + } + + /** + * Verify the structure after the Zig-Zig rotation + */ + public function testZigZigRotation(): void + { + $tree = new SplayTree(); + + $tree->insert(30, 'A'); + $tree->insert(20, 'B'); + $tree->insert(10, 'C'); // Trigger a Zig-Zig rotation when 10 is splayed + + $root = $tree->getRoot(); + $this->assertSame(10, $root->key, 'Root should be 10 after Zig-Zig rotation'); + $this->assertTrue($root->isRoot(), "Root parent should be Null after Zig-Zig rotation"); + $this->assertSame(20, $root->right->key, '20 should be the right child of 10 after Zig-Zig rotation'); + $this->assertSame(30, $root->right->right->key, '30 should be the right child of 20 after Zig-Zig rotation'); + } + + /** + * Verify the structure after the Zag-Zag rotation + */ + public function testZagZagRotation(): void + { + $tree = new SplayTree(); + + $tree->insert(10, 'A'); + $tree->insert(20, 'B'); + $tree->insert(30, 'C'); // Trigger a Zag-Zag rotation when 30 is splayed + + $root = $tree->getRoot(); + $this->assertSame(30, $root->key, 'Root should be 30 after Zag-Zag rotation'); + $this->assertTrue($root->isRoot(), "Root parent should be Null after Zag-Zag rotation"); + $this->assertSame(20, $root->left->key, '20 should be the left child of 30 after Zag-Zag rotation'); + $this->assertSame(10, $root->left->left->key, '10 should be the left child of 20 after Zag-Zag rotation'); + } + + /** + * Verify the structure after the Zig-Zag rotation + */ + public function testZigZagRotation(): void + { + $tree = new SplayTree(); + + $tree->insert(30, 'A'); + $tree->insert(10, 'B'); + $tree->insert(20, 'C'); // Trigger Zig-Zag rotation when 20 is splayed + + $root = $tree->getRoot(); + $this->assertSame(20, $root->key, 'Root should be 20 after Zig-Zag rotation'); + $this->assertTrue($root->isRoot(), "Root parent should be Null after Zig-Zag rotation"); + $this->assertSame(10, $root->left->key, '10 should be the left child of 20 after Zig-Zag rotation'); + $this->assertSame(30, $root->right->key, '30 should be the right child of 20 after Zig-Zag rotation'); + } + + /** + * Verify the structure after the Zag-Zig rotation + */ + public function testZagZigRotation(): void + { + $tree = new SplayTree(); + + $tree->insert(10, 'A'); + $tree->insert(30, 'B'); + $tree->insert(20, 'C'); // Trigger a Zag-Zig rotation when 20 is splayed + + $root = $tree->getRoot(); + $this->assertSame(20, $root->key, 'Root should be 20 after Zag-Zig rotation'); + $this->assertTrue($root->isRoot(), "Root parent should be Null after Zag-Zag rotation"); + $this->assertSame(10, $root->left->key, '10 should be the left child of 20 after Zag-Zig rotation'); + $this->assertSame(30, $root->right->key, '30 should be the right child of 20 after Zag-Zig rotation'); + } +} From 5bb6777fa8ed9c90418782ce6a52185da7e1f38d Mon Sep 17 00:00:00 2001 From: Ramy Date: Thu, 3 Oct 2024 23:37:47 +0200 Subject: [PATCH 38/38] Update tests/DataStructures/SplayTreeTest.php Co-authored-by: Brandon Johnson --- tests/DataStructures/SplayTreeTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/DataStructures/SplayTreeTest.php b/tests/DataStructures/SplayTreeTest.php index 7709cf2e..9c33ffa7 100644 --- a/tests/DataStructures/SplayTreeTest.php +++ b/tests/DataStructures/SplayTreeTest.php @@ -259,7 +259,7 @@ public function testSearchExistingKey() $node = $this->tree->search(22); $this->assertNotNull($node, "Returned node cannot be Null."); - $this->assertNull($node->parent, "The searched node must have become the new root with has no parent"); + $this->assertNull($node->parent, "The searched node must have become the new root which has no parent"); $this->assertTrue( $node->isRoot(), "The searched node must have become the new root. Failed to splay it correctly."