mirror of
https://github.com/TheAlgorithms/PHP.git
synced 2025-07-20 00:11:14 +02:00
Implemented Segment Tree Data Structure (#166)
* Added Disjoint Sets Data structure * Moved DisjointSetTest.php to tests/DataStructures * Update DataStructures/DisjointSets/DisjointSet.php Co-authored-by: Brandon Johnson <bbj1979@gmail.com> * Update DataStructures/DisjointSets/DisjointSetNode.php Co-authored-by: Brandon Johnson <bbj1979@gmail.com> * Update DataStructures/DisjointSets/DisjointSetNode.php Co-authored-by: Brandon Johnson <bbj1979@gmail.com> * Update tests/DataStructures/DisjointSetTest.php Co-authored-by: Brandon Johnson <bbj1979@gmail.com> * Update tests/DataStructures/DisjointSetTest.php Co-authored-by: Brandon Johnson <bbj1979@gmail.com> * Update tests/DataStructures/DisjointSetTest.php Co-authored-by: Brandon Johnson <bbj1979@gmail.com> * Considered PHPCS remarks. Unit Testing is now working. * Remove data type mixed. Considered annotations for php7.4. * Remove data type mixed. Considered annotations for php7.4. * updating DIRECTORY.md * Implemented Trie DataStructure * Added Trie to DIRECTORY.md * updating DIRECTORY.md * Implemented AVLTree DataStructure * updating DIRECTORY.md * Implemented AVLTree DataStructure * Implemented SegmentTreeNode.php * Implementing SegmentTree * Implementing SegmentTree with updateTree * Implementing SegmentTree with rangeUpdateTree * Implementing SegmentTree with query and queryTree * Added serializing and deserializing of the SegmentTree * Adding unit tests SegmentTree implementation * Added unit tests for SegmentTree updates and range updates * considering PHPCS for Added unit tests for SegmentTree updates and range updates * Added unit tests for SegmentTree serialization/deserialization and array updates reflections * Added unit tests for SegmentTree Edge Cases * Added unit tests for SegmentTree Exceptions (OutOfBoundsException, InvalidArgumentException) * Added SegmentTree to DIRECTORY.md * Implemented Segment Tree Data Structure * Added some comments to my files in: #160, #162, #163, #166. Implemented Segment Tree Data Structure. * Added some comments to my files in: #160, #162, #163, #166. Implemented Segment Tree Data Structure. * Added comments time complexity for query(), update() and buildTree() --------- Co-authored-by: Brandon Johnson <bbj1979@gmail.com> Co-authored-by: Ramy-Badr-Ahmed <Ramy-Badr-Ahmed@users.noreply.github.com>
This commit is contained in:
@@ -27,6 +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)
|
||||
* [Singlylinkedlist](./DataStructures/SinglyLinkedList.php)
|
||||
* [Stack](./DataStructures/Stack.php)
|
||||
* Trie
|
||||
@@ -125,6 +128,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)
|
||||
|
@@ -1,5 +1,13 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Created by: Ramy-Badr-Ahmed (https://github.com/Ramy-Badr-Ahmed) in Pull Request: #163
|
||||
* https://github.com/TheAlgorithms/PHP/pull/163
|
||||
*
|
||||
* Please mention me (@Ramy-Badr-Ahmed) in any issue or pull request addressing bugs/corrections to this file.
|
||||
* Thank you!
|
||||
*/
|
||||
|
||||
namespace DataStructures\AVLTree;
|
||||
|
||||
/**
|
||||
|
@@ -1,5 +1,13 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Created by: Ramy-Badr-Ahmed (https://github.com/Ramy-Badr-Ahmed) in Pull Request: #163
|
||||
* https://github.com/TheAlgorithms/PHP/pull/163
|
||||
*
|
||||
* Please mention me (@Ramy-Badr-Ahmed) in any issue or pull request addressing bugs/corrections to this file.
|
||||
* Thank you!
|
||||
*/
|
||||
|
||||
namespace DataStructures\AVLTree;
|
||||
|
||||
class AVLTreeNode
|
||||
|
@@ -1,5 +1,13 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Created by: Ramy-Badr-Ahmed (https://github.com/Ramy-Badr-Ahmed) in Pull Request: #163
|
||||
* https://github.com/TheAlgorithms/PHP/pull/163
|
||||
*
|
||||
* Please mention me (@Ramy-Badr-Ahmed) in any issue or pull request addressing bugs/corrections to this file.
|
||||
* Thank you!
|
||||
*/
|
||||
|
||||
namespace DataStructures\AVLTree;
|
||||
|
||||
abstract class TreeTraversal
|
||||
|
@@ -1,5 +1,13 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Created by: Ramy-Badr-Ahmed (https://github.com/Ramy-Badr-Ahmed) in Pull Request: #160
|
||||
* https://github.com/TheAlgorithms/PHP/pull/160
|
||||
*
|
||||
* Please mention me (@Ramy-Badr-Ahmed) in any issue or pull request addressing bugs/corrections to this file.
|
||||
* Thank you!
|
||||
*/
|
||||
|
||||
namespace DataStructures\DisjointSets;
|
||||
|
||||
class DisjointSet
|
||||
|
@@ -1,5 +1,13 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Created by: Ramy-Badr-Ahmed (https://github.com/Ramy-Badr-Ahmed) in Pull Request: #160
|
||||
* https://github.com/TheAlgorithms/PHP/pull/160
|
||||
*
|
||||
* Please mention me (@Ramy-Badr-Ahmed) in any issue or pull request addressing bugs/corrections to this file.
|
||||
* Thank you!
|
||||
*/
|
||||
|
||||
namespace DataStructures\DisjointSets;
|
||||
|
||||
class DisjointSetNode
|
||||
|
332
DataStructures/SegmentTree/SegmentTree.php
Normal file
332
DataStructures/SegmentTree/SegmentTree.php
Normal file
@@ -0,0 +1,332 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Created by: Ramy-Badr-Ahmed (https://github.com/Ramy-Badr-Ahmed) in Pull Request #166
|
||||
* https://github.com/TheAlgorithms/PHP/pull/166
|
||||
*
|
||||
* Please mention me (@Ramy-Badr-Ahmed) in any issue or pull request addressing bugs/corrections to this file.
|
||||
* Thank you!
|
||||
*/
|
||||
|
||||
namespace DataStructures\SegmentTree;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use OutOfBoundsException;
|
||||
|
||||
class SegmentTree
|
||||
{
|
||||
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));
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SegmentTreeNode The root node of the segment tree.
|
||||
*/
|
||||
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. Takes O(n log n) in total.
|
||||
*
|
||||
* @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
|
||||
if ($start == $end) {
|
||||
return new SegmentTreeNode($start, $end, $arr[$start]);
|
||||
}
|
||||
|
||||
$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);
|
||||
|
||||
$node = new SegmentTreeNode($start, $end, $this->callback
|
||||
? ($this->callback)($leftChild->value, $rightChild->value)
|
||||
: $leftChild->value + $rightChild->value);
|
||||
|
||||
// Link the children to the parent node
|
||||
$node->left = $leftChild;
|
||||
$node->right = $rightChild;
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @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. Takes O(log n).
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
38
DataStructures/SegmentTree/SegmentTreeNode.php
Normal file
38
DataStructures/SegmentTree/SegmentTreeNode.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Created by: Ramy-Badr-Ahmed (https://github.com/Ramy-Badr-Ahmed) in Pull Request #166
|
||||
* https://github.com/TheAlgorithms/PHP/pull/166
|
||||
*
|
||||
* Please mention me (@Ramy-Badr-Ahmed) in any issue or pull request addressing bugs/corrections to this file.
|
||||
* Thank you!
|
||||
*/
|
||||
|
||||
namespace DataStructures\SegmentTree;
|
||||
|
||||
class SegmentTreeNode
|
||||
{
|
||||
public int $start;
|
||||
public int $end;
|
||||
/**
|
||||
* @var int|float
|
||||
*/
|
||||
public $value;
|
||||
public ?SegmentTreeNode $left;
|
||||
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).
|
||||
* calculated using a callback. Defaults to sum.
|
||||
*/
|
||||
public function __construct(int $start, int $end, $value)
|
||||
{
|
||||
$this->start = $start;
|
||||
$this->end = $end;
|
||||
$this->value = $value;
|
||||
$this->left = null;
|
||||
$this->right = null;
|
||||
}
|
||||
}
|
@@ -1,5 +1,13 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Created by: Ramy-Badr-Ahmed (https://github.com/Ramy-Badr-Ahmed) in Pull Request: #162
|
||||
* https://github.com/TheAlgorithms/PHP/pull/162
|
||||
*
|
||||
* Please mention me (@Ramy-Badr-Ahmed) in any issue or pull request addressing bugs/corrections to this file.
|
||||
* Thank you!
|
||||
*/
|
||||
|
||||
namespace DataStructures\Trie;
|
||||
|
||||
class Trie
|
||||
|
@@ -1,5 +1,13 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Created by: Ramy-Badr-Ahmed (https://github.com/Ramy-Badr-Ahmed) in Pull Request: #162
|
||||
* https://github.com/TheAlgorithms/PHP/pull/162
|
||||
*
|
||||
* Please mention me (@Ramy-Badr-Ahmed) in any issue or pull request addressing bugs/corrections to this file.
|
||||
* Thank you!
|
||||
*/
|
||||
|
||||
namespace DataStructures\Trie;
|
||||
|
||||
class TrieNode
|
||||
|
@@ -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",
|
||||
|
@@ -1,5 +1,13 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Created by: Ramy-Badr-Ahmed (https://github.com/Ramy-Badr-Ahmed) in Pull Request: #163
|
||||
* https://github.com/TheAlgorithms/PHP/pull/163
|
||||
*
|
||||
* Please mention me (@Ramy-Badr-Ahmed) in any issue or pull request addressing bugs/corrections to this file.
|
||||
* Thank you!
|
||||
*/
|
||||
|
||||
namespace DataStructures;
|
||||
|
||||
require_once __DIR__ . '/../../DataStructures/AVLTree/AVLTree.php';
|
||||
|
@@ -1,5 +1,13 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Created by: Ramy-Badr-Ahmed (https://github.com/Ramy-Badr-Ahmed) in Pull Request: #160
|
||||
* https://github.com/TheAlgorithms/PHP/pull/160
|
||||
*
|
||||
* Please mention me (@Ramy-Badr-Ahmed) in any issue or pull request addressing bugs/corrections to this file.
|
||||
* Thank you!
|
||||
*/
|
||||
|
||||
namespace DataStructures;
|
||||
|
||||
require_once __DIR__ . '/../../DataStructures/DisjointSets/DisjointSet.php';
|
||||
|
347
tests/DataStructures/SegmentTreeTest.php
Normal file
347
tests/DataStructures/SegmentTreeTest.php
Normal file
@@ -0,0 +1,347 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Created by: Ramy-Badr-Ahmed (https://github.com/Ramy-Badr-Ahmed) in Pull Request #166
|
||||
* https://github.com/TheAlgorithms/PHP/pull/166
|
||||
*
|
||||
* Please mention me (@Ramy-Badr-Ahmed) in any issue or pull request addressing bugs/corrections to this file.
|
||||
* Thank you!
|
||||
*/
|
||||
|
||||
namespace DataStructures;
|
||||
|
||||
require_once __DIR__ . '/../../DataStructures/SegmentTree/SegmentTree.php';
|
||||
require_once __DIR__ . '/../../DataStructures/SegmentTree/SegmentTreeNode.php';
|
||||
|
||||
use DataStructures\SegmentTree\SegmentTree;
|
||||
use InvalidArgumentException;
|
||||
use OutOfBoundsException;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class SegmentTreeTest extends TestCase
|
||||
{
|
||||
private array $testArray;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->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."
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test update functionality for different query types.
|
||||
*/
|
||||
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."
|
||||
);
|
||||
|
||||
// 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."
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test range update functionality for different query types.
|
||||
*/
|
||||
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."
|
||||
);
|
||||
|
||||
// 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."
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test array updates reflections.
|
||||
*/
|
||||
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."
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test serialization and deserialization of the segment tree.
|
||||
*/
|
||||
public function testSegmentTreeSerialization(): void
|
||||
{
|
||||
$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."
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Testing EdgeCases: first and last indices functionality on the Segment Tree
|
||||
*/
|
||||
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."
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test empty or unsupported arrays.
|
||||
*/
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test exception for invalid update index.
|
||||
*/
|
||||
public function testInvalidUpdateIndex(): void
|
||||
{
|
||||
$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
|
||||
}
|
||||
|
||||
/**
|
||||
* Test exception for invalid update index.
|
||||
*/
|
||||
public function testOutOfBoundsQuery(): void
|
||||
{
|
||||
$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
|
||||
}
|
||||
|
||||
/**
|
||||
* Test exception for invalid range update.
|
||||
*/
|
||||
public function testInvalidRangeUpdate(): void
|
||||
{
|
||||
$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
|
||||
}
|
||||
}
|
@@ -1,5 +1,13 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Created by: Ramy-Badr-Ahmed (https://github.com/Ramy-Badr-Ahmed) in Pull Request: #162
|
||||
* https://github.com/TheAlgorithms/PHP/pull/162
|
||||
*
|
||||
* Please mention me (@Ramy-Badr-Ahmed) in any issue or pull request addressing bugs/corrections to this file.
|
||||
* Thank you!
|
||||
*/
|
||||
|
||||
namespace DataStructures;
|
||||
|
||||
require_once __DIR__ . '/../../DataStructures/Trie/Trie.php';
|
||||
|
Reference in New Issue
Block a user