From d61d0236531680bc7dd251c32de7f83770000482 Mon Sep 17 00:00:00 2001 From: Ramy-Badr-Ahmed Date: Sun, 22 Sep 2024 14:36:51 +0200 Subject: [PATCH] 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 4783ea2..3496499 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 87e5a73..e01e8e9 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 8a6152f..01af57b 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 2fb717c..fe94723 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;