diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 014f7539..35560791 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,23 +12,29 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - - name: Validate composer.json and composer.lock - run: composer validate - - - name: Cache Composer packages - id: composer-cache - uses: actions/cache@v2 - with: - path: vendor - key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} - restore-keys: | - ${{ runner.os }}-php- - - - name: Install dependencies - if: steps.composer-cache.outputs.cache-hit != 'true' - run: composer install --prefer-dist --no-progress --no-suggest - - - name: Run PHPUnit - run: composer run-script test + - uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '7.4' + ini-values: xdebug.max_nesting_level=512 + + - name: Validate composer.json and composer.lock + run: composer validate + + - name: Cache Composer packages + id: composer-cache + uses: actions/cache@v3 + with: + path: vendor + key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-php- + + - name: Install dependencies + if: steps.composer-cache.outputs.cache-hit != 'true' + run: composer install --prefer-dist --no-progress --no-suggest + + - name: Run PHPUnit + run: composer run-script test \ No newline at end of file diff --git a/.github/workflows/code-style.yml b/.github/workflows/code-style.yml new file mode 100644 index 00000000..e852dfc9 --- /dev/null +++ b/.github/workflows/code-style.yml @@ -0,0 +1,25 @@ +name: Code style + +on: [push, pull_request] + +permissions: + contents: read + +jobs: + phpcs: + name: PHPCS + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '7.4' + + - name: Install dependencies + run: composer update --prefer-dist --no-progress --no-suggest + + - name: Run script + run: vendor/bin/phpcs -n \ No newline at end of file diff --git a/.github/workflows/directory_workflow.yml b/.github/workflows/directory_workflow.yml index 0b9b06d8..a75aeeeb 100644 --- a/.github/workflows/directory_workflow.yml +++ b/.github/workflows/directory_workflow.yml @@ -8,12 +8,12 @@ jobs: name: DIRECTORY.md runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 # v2 is broken for git diff - - uses: actions/setup-python@v2 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 - name: Setup Git Specs run: | - git config --global user.name github-actions - git config --global user.email '${GITHUB_ACTOR}@users.noreply.github.com' + git config --global user.name "$GITHUB_ACTOR" + git config --global user.email "$GITHUB_ACTOR@users.noreply.github.com" git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY - name: Update DIRECTORY.md shell: python diff --git a/.gitignore b/.gitignore index 96ccab44..8e409f4d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,7 @@ .phan composer.lock +/.phpcs-cache +/phpcs.xml + .phpunit.result.cache \ No newline at end of file diff --git a/.gitpod.Dockerfile b/.gitpod.Dockerfile new file mode 100644 index 00000000..44d236a3 --- /dev/null +++ b/.gitpod.Dockerfile @@ -0,0 +1 @@ +FROM gitpod/workspace-full:2022-05-08-14-31-53 diff --git a/.gitpod.yml b/.gitpod.yml new file mode 100644 index 00000000..94081db9 --- /dev/null +++ b/.gitpod.yml @@ -0,0 +1,6 @@ +image: + file: .gitpod.Dockerfile + +tasks: + - init: | + echo "Welcome to TheAlgorithms/PHP" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8917aa2e..0b0abd8e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -62,9 +62,12 @@ We want your work to be readable by others; therefore, we encourage you to note If you used a Wikipedia article or some other source material to create your algorithm, please add the URL in a docstring or comment to help your reader -- Write tests +- Write proper unit tests (see examples in [tests](https://github.com/TheAlgorithms/PHP/tree/master/tests) and ensure all unit tests are passing (composer run-script test) + - Avoid importing external libraries for basic algorithms. Only use them for complicated algorithms +- Ensure code is linted with phpcs, and passing all linting checks (vendor/bin/phpcs -n) + #### Other Standard While Submitting Your Work - File extension for code should be `.php` diff --git a/Ciphers/AtbashCipher.php b/Ciphers/AtbashCipher.php new file mode 100644 index 00000000..655365c4 --- /dev/null +++ b/Ciphers/AtbashCipher.php @@ -0,0 +1,39 @@ + \ No newline at end of file + $rowIndex) { + $lengths[$rowIndex]++; + } + if ($balance > ($rails + ($rails - $rowIndex) - 2)) { + $lengths[$rowIndex]++; + } + $strings[] = substr($cipherMessage, $totalLengths, $lengths[$rowIndex]); + $totalLengths += $lengths[$rowIndex]; + } + // Convert the rows of characters to plain message + $plainText = ''; + while (strlen($plainText) < $textLength) { + for ($charIndex = 0; $charIndex < $position; $charIndex++) { + if (isset($strings[$charIndex])) { + $index = $charIndex; + } else { + $index = $position - $charIndex; + } + $plainText .= substr($strings[$index], 0, 1); + $strings[$index] = substr($strings[$index], 1); + } + } + return $plainText; +} diff --git a/Ciphers/VignereCipher.php b/Ciphers/VignereCipher.php new file mode 100644 index 00000000..d3bf1bf2 --- /dev/null +++ b/Ciphers/VignereCipher.php @@ -0,0 +1,63 @@ + 1 knot which is equal to 1 nautical mile (1852 km/h) * The conversion is made using kilometers as base * - * @param float $speed - * @param string $unitFrom - * @param string $unitTo - * @return int + * @param float $speed + * @param string $unitFrom + * @param string $unitTo + * @return float + * @throws \Exception */ function convertSpeed(float $speed, string $unitFrom, string $unitTo) { $speedUnitsFrom = [ 'mph' => 1.609344, 'km/h' => 1, - 'm/s'=> 3.6, - 'ft/s'=> 1.097, + 'm/s' => 3.6, + 'ft/s' => 1.097, 'kn' => 1.852, ]; $speedUnitsTo = [ 'mph' => 0.6213712, 'km/h' => 1, - 'm/s'=> 0.277778, - 'ft/s'=> 0.911344, + 'm/s' => 0.277778, + 'ft/s' => 0.911344, 'kn' => 0.539957, ]; $availableUnits = array_keys($speedUnitsFrom); diff --git a/Conversions/TemperatureConversions.php b/Conversions/TemperatureConversions.php new file mode 100644 index 00000000..1be1fb52 --- /dev/null +++ b/Conversions/TemperatureConversions.php @@ -0,0 +1,115 @@ +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; + } + + /** + * Serializes the segment tree into a JSON string. + * + * @return string The serialized AVL Tree as a JSON string. + */ + public function serialize(): string + { + return json_encode($this->serializeTree($this->root)); + } + + /** + * Recursively serializes the AVL Tree. + * + * @param AVLTreeNode|null $node + * @return array + */ + private function serializeTree(?AVLTreeNode $node): array + { + if ($node === null) { + return []; + } + return [ + 'key' => $node->key, + 'value' => $node->value, + 'left' => $this->serializeTree($node->left), + 'right' => $this->serializeTree($node->right), + 'height' => $node->height, + ]; + } + + /** + * Deserializes a JSON string into an AVL Tree object + * + * @param string $data The JSON representation of an AVL Tree to deserialize. + */ + public function deserialize(string $data): void + { + $this->root = $this->deserializeTree(json_decode($data, true)); + $this->counter = 0; + $this->updateNodeCount($this->root); + } + + /** + * Recursively deserializes an AVL Tree from an array representation. + * + * @param array $data The serialized data for the node. + * @return AVLTreeNode|null The root node of the deserialized tree. + */ + private function deserializeTree(array $data): ?AVLTreeNode + { + if (empty($data)) { + return null; + } + + $node = new AVLTreeNode($data['key'], $data['value']); + $node->height = $data['height']; + + $node->left = $this->deserializeTree($data['left']); + $node->right = $this->deserializeTree($data['right']); + + return $node; + } + + /** + * Updates the deserialized tree size. + * + * @param AVLTreeNode|null $node The root node of the deserialized tree. + */ + private function updateNodeCount(?AVLTreeNode $node): void + { + if ($node !== null) { + $this->counter++; + $this->updateNodeCount($node->left); + $this->updateNodeCount($node->right); + } + } +} diff --git a/DataStructures/AVLTree/AVLTreeNode.php b/DataStructures/AVLTree/AVLTreeNode.php new file mode 100644 index 00000000..2d21c6a1 --- /dev/null +++ b/DataStructures/AVLTree/AVLTreeNode.php @@ -0,0 +1,49 @@ +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..b6812683 --- /dev/null +++ b/DataStructures/AVLTree/TreeTraversal.php @@ -0,0 +1,88 @@ +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/DataStructures/BinarySearchTree/BSTNode.php b/DataStructures/BinarySearchTree/BSTNode.php new file mode 100644 index 00000000..1b180718 --- /dev/null +++ b/DataStructures/BinarySearchTree/BSTNode.php @@ -0,0 +1,66 @@ +key = $key; + $this->value = $value; + $this->left = null; + $this->right = null; + $this->parent = null; + } + + public function isRoot(): bool + { + return $this->parent === null; + } + + public function isLeaf(): bool + { + return $this->left === null && $this->right === null; + } + + public function getChildren(): array + { + if ($this->isLeaf()) { + return []; + } + + $children = []; + if ($this->left !== null) { + $children['left'] = $this->left; + } + if ($this->right !== null) { + $children['right'] = $this->right; + } + return $children; + } + public function getChildrenCount(): int + { + return count($this->getChildren()); + } +} diff --git a/DataStructures/BinarySearchTree/BSTree.php b/DataStructures/BinarySearchTree/BSTree.php new file mode 100644 index 00000000..0fded5e7 --- /dev/null +++ b/DataStructures/BinarySearchTree/BSTree.php @@ -0,0 +1,378 @@ + $value) { + $this->insert($key, $value); // Build the tree from an array of key-value pairs + } + parent::setTraversalType($traversalType); + } + /** + * Get the root of the Splay Tree. + * + * @return BSTNode|NULL The root node. + */ + public function getRoot(): ?BSTNode + { + return $this->root; + } + + public function size(): int + { + return $this->counter; + } + + public function isEmpty(): bool + { + return $this->root === null; + } + + /** + * Inserts a new node into binary search tree and updates parent reference. + * + * Time complexity: O(log n) for binary search tree insertion. + */ + public function insert(int $key, $value): ?BSTNode + { + $this->insertNode($this->root, $key, $value); + $this->counter++; + return $this->root; + } + + private function insertNode(?BSTNode &$rootPtr, int $key, $value): void + { + if ($rootPtr === null) { + $rootPtr = new BSTNode($key, $value); + return; + } + + if ($key < $rootPtr->key) { + $this->insertNode($rootPtr->left, $key, $value); + $rootPtr->left->parent = $rootPtr; + } elseif ($key > $rootPtr->key) { + $this->insertNode($rootPtr->right, $key, $value); + $rootPtr->right->parent = $rootPtr; + } else { + throw new DuplicateKeyException($key); + } + } + + /** + * Removes and isolates an existing node from the BST and update structure references. + * + * Time complexity: O(log n) for binary search tree node removal. + */ + public function remove(int $key): ?BSTNode + { + $discardedNode = $this->removeNode($this->root, $key); + if ($discardedNode !== null) { + $this->counter--; + } + return $discardedNode; + } + + private function removeNode(?BSTNode &$rootPtr, int $key): ?BSTNode + { + if ($rootPtr === null) { + return null; + } + + if ($key < $rootPtr->key) { + $discardedNode = $this->removeNode($rootPtr->left, $key); + } elseif ($key > $rootPtr->key) { + $discardedNode = $this->removeNode($rootPtr->right, $key); + } else { + // Key found, proceed to delete + $discardedNode = $rootPtr->getChildrenCount() === 2 + ? $this->handleNodeWithTwoChildren($rootPtr) + : $this->handleNodeWithSingleOrZeroChild($rootPtr); + } + return $discardedNode; + } + + /** + * handle deletion when found node has 2 children. + */ + private function handleNodeWithTwoChildren(BSTNode $rootPtr): ?BSTNode + { + $minRightNode = $this->minNode($rootPtr->right); + + $discarded = clone $rootPtr; + + $rootPtr->key = $minRightNode->key; + $rootPtr->value = $minRightNode->value; + + $this->removeNode($rootPtr->right, $minRightNode->key); + + // Isolate the target node + $discarded->left = null; + $discarded->right = null; + $discarded->parent = null; + + return $discarded; + } + + /** + * Handle deletion when found node has 1 or 0 child. + */ + private function handleNodeWithSingleOrZeroChild(BSTNode &$rootPtr): ?BSTNode + { + $discard = $rootPtr; + + if ($discard->isLeaf()) { // Case 1: Node to be removed is a leaf node + $rootPtr = null; + } elseif ($rootPtr->left != null) { // Case 2: Node has only a left child + $rootPtr = $rootPtr->left; + $discard->left = null; + } else { // Case 3: Node has only a right child + $rootPtr = $rootPtr->right; + $discard->right = null; + } + + // Update the parent reference for the new child node + if ($rootPtr !== null) { + $rootPtr->parent = $discard->parent; + } + + // Unlink the discarded node from its parent + $discard->parent = null; + return $discard; + } + + /** + * Return the minimum node in the BST. + */ + public function minNode(?BSTNode $node): ?BSTNode + { + if ($node === null) { + return null; + } + + return $node->left === null + ? $node + : $this->minNode($node->left); + } + + /** + * Search for a node by its key and return the node, otherwise Null. + * + * Time complexity: O(log n) for the splay operation. + */ + public function search(int $key): ?BSTNode + { + if ($this->isEmpty()) { + return null; + } + + return $this->searchNode($this->root, $key); + } + + private function searchNode(?BSTNode $node, int $key): ?BSTNode + { + if ($node === null) { + return null; + } + + if ($key === $node->key) { + return $node; + } elseif ($key < $node->key) { + return $this->searchNode($node->left, $key); + } else { + return $this->searchNode($node->right, $key); + } + } + + /** + * Check if a node with the given key exists in the BST. + * + * Time complexity: O(log n) for the splay operation. + * + * @param BSTNode|null $node The root of the (sub)tree to start propagating from. + * @param int $key The key of the node being searched. + */ + public function isFound(?BSTNode $node, int $key): bool + { + if ($node === null) { + return false; + } + + if ($key === $node->key) { + return true; + } elseif ($key < $node->key) { + return $this->isFound($node->left, $key); + } else { + return $this->isFound($node->right, $key); + } + } + + /** + * Get the depth of the given node relative to the root of the tree. + * Traverses from the given node up to the root of the tree by recursively following the parent references + * + * Time complexity: O(d_p) where d_p: depth of node p in tree + * + * @param BSTNode $node The node whose depth is to be calculated. + * @return int The depth of the node relative to the root. + */ + public function getDepth(BSTNode $node): int + { + return $node->parent === null + ? 0 + : 1 + $this->getDepth($node->parent); + } + + /** + * Get the height of the given node relative to the farthest leaf node. + * Recursively visits all children of the given node determining the maximum height of its subtrees + * + * Time Complexity: O(n) + * + * @param BSTNode $node The node whose height is to be calculated. + * @return int The height of the node relative to its farthest descendant. + */ + public function getHeight(BSTNode $node): int + { + if ($node->isLeaf()) { + return 0; + } + $height = 0; + $childrenList = $node->getChildren(); + + foreach ($childrenList as $childNode) { + $height = max($height, $this->getHeight($childNode)); + } + return 1 + $height; + } + + /** + * Returns a flat associative array structure for inOrder Traversal + */ + public function inOrderTraversal(): array + { + return $this->inOrder($this->root); + } + + /** + * Returns a flat associative array structure for preOrder Traversal + */ + public function preOrderTraversal(): array + { + return $this->preOrder($this->root); + } + + /** + * Returns a flat associative array structure for postOrder Traversal + */ + public function postOrderTraversal(): array + { + return $this->postOrder($this->root); + } + + /** + * Returns a flat associative array structure for Breadth-first Traversal + */ + public function breadthFirstTraversal(): array + { + return $this->breadthFirst($this->root); + } + + /** + * Serializes the BST into a JSON string. + * + * @return string The BST as a JSON string. + */ + public function serialize(): string + { + return json_encode($this->serializeTree($this->root)); + } + + /** + * Recursively serializes the Binary Search Tree. + * + * @param BSTNode|null $node The current node to serialize from. + * @return array The serialized representation of the node. + */ + private function serializeTree(?BSTNode $node): array + { + if ($node === null) { + return []; + } + return [ + 'key' => $node->key, + 'value' => $node->value, + 'left' => $this->serializeTree($node->left), + 'right' => $this->serializeTree($node->right), + ]; + } + + /** + * Deserializes a JSON string into a BST object. + * + * @param string $data The JSON string to deserialize. + * @return BSTree The deserialized Binary Search Tree. + */ + public function deserialize(string $data): BSTree + { + $arrayRepresentation = json_decode($data, true); + + $binarySearchTree = new self(); + $binarySearchTree->root = $binarySearchTree->deserializeTree($arrayRepresentation, null); + + $binarySearchTree->updateNodeCount($binarySearchTree->root); + return $binarySearchTree; + } + + /** + * Recursively deserializes a BST from an array representation. + * + * @param array $data The serialized data for the node. + * @return BSTNode|null The deserialized node. + */ + private function deserializeTree(array $data, ?BSTNode $parent): ?BSTNode + { + if (empty($data)) { + return null; + } + + $node = new BSTNode($data['key'], $data['value']); + $node->parent = $parent; + + $node->left = $this->deserializeTree($data['left'], $node); + $node->right = $this->deserializeTree($data['right'], $node); + + return $node; + } + /** + * Recursively updates the BST size after deserialization. + */ + private function updateNodeCount(?BSTNode $node): void + { + if ($node !== null) { + $this->counter++; + $this->updateNodeCount($node->left); + $this->updateNodeCount($node->right); + } + } +} diff --git a/DataStructures/BinarySearchTree/BinaryTreeTraversal.php b/DataStructures/BinarySearchTree/BinaryTreeTraversal.php new file mode 100644 index 00000000..a312dacc --- /dev/null +++ b/DataStructures/BinarySearchTree/BinaryTreeTraversal.php @@ -0,0 +1,207 @@ +traversalType = $traversalType; + + // Reset iterator with new traversal + $this->rewind(); + } + + /** + * Helper to populate iteration nodes per traversal ype + */ + private function loadTraversedNodes(): void + { + switch ($this->traversalType) { + case self::PRE_ORDER: + $this->preOrderIterator(static::getRoot()); + break; + + case self::POST_ORDER: + $this->postOrderIterator(static::getRoot()); + break; + + case self::IN_ORDER: + default: + $this->inOrderIterator(static::getRoot()); + } + } + + /** + * Returns a flat associative array structure for inOrder Traversal + */ + protected function inOrder(?BSTNode $node): array + { + $result = []; + if ($node !== null) { + $result += $this->inOrder($node->left); + $result[$node->key] = $node->value; + $result += $this->inOrder($node->right); + } + return $result; + } + + /** + * Returns a flat associative array structure for preOrder Traversal + */ + protected function preOrder(?BSTNode $node): array + { + $result = []; + if ($node !== null) { + $result[$node->key] = $node->value; + $result += $this->preOrder($node->left); + $result += $this->preOrder($node->right); + } + return $result; + } + + /** + * Returns a flat associative array structure for postOrder Traversal + */ + protected function postOrder(?BSTNode $node): array + { + $result = []; + if ($node !== null) { + $result += $this->postOrder($node->left); + $result += $this->postOrder($node->right); + $result[$node->key] = $node->value; + } + return $result; + } + + /** + * Returns a flat associative array structure for BFT Traversal + */ + protected function breadthFirst(?BSTNode $node): array + { + $result = []; + if (!isset($node)) { + return $result; + } + + $queue = []; + $queue[] = $node; + + 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; + } + + /** + * Rewind the iterator to the first element. + */ + public function rewind(): void + { + $this->iterationNodes = []; + $this->currentPosition = 0; + + // Load nodes based on traversal type + $this->loadTraversedNodes(); + } + + /** + * Return the current element if exists + */ + public function current(): ?BSTNode + { + return $this->iterationNodes[$this->currentPosition] ?? null; + } + + /** + * Return the key of the current element. + */ + public function key(): int + { + return $this->currentPosition; + } + + /** + * Move forward to the next element. + */ + public function next(): void + { + $this->currentPosition++; + } + + /** + * Check if the current position is valid. + */ + public function valid(): bool + { + return isset($this->iterationNodes[$this->currentPosition]); + } + + /** + * Helper function to traverse the tree in-order and fill the $inOrderNodes array. + */ + private function inOrderIterator(?BSTNode $node): void + { + if ($node !== null) { + $this->inOrderIterator($node->left); + $this->iterationNodes[] = $node; + $this->inOrderIterator($node->right); + } + } + /** + * Helper function to traverse the tree in-order and fill the $preOrderNodes array. + */ + private function preOrderIterator(?BSTNode $node): void + { + if ($node !== null) { + $this->iterationNodes[] = $node; + $this->preOrderIterator($node->left); + $this->preOrderIterator($node->right); + } + } + /** + * Helper function to traverse the tree in-order and fill the $postOrderNodes array. + */ + private function postOrderIterator(?BSTNode $node): void + { + if ($node !== null) { + $this->postOrderIterator($node->left); + $this->postOrderIterator($node->right); + $this->iterationNodes[] = $node; + } + } +} diff --git a/DataStructures/BinarySearchTree/DuplicateKeyException.php b/DataStructures/BinarySearchTree/DuplicateKeyException.php new file mode 100644 index 00000000..37cf1669 --- /dev/null +++ b/DataStructures/BinarySearchTree/DuplicateKeyException.php @@ -0,0 +1,14 @@ +value = $value; + $this->left = $left; + $this->right = $right; + } + + public $value; + public ?BinaryTreeNode $left; + public ?BinaryTreeNode $right; +} diff --git a/DataStructures/CompareBinaryTree/CompareBinaryTree.php b/DataStructures/CompareBinaryTree/CompareBinaryTree.php new file mode 100644 index 00000000..d4def6e4 --- /dev/null +++ b/DataStructures/CompareBinaryTree/CompareBinaryTree.php @@ -0,0 +1,35 @@ +value !== $b->value) { + return false; + } + return $this->areTreesEqual($a->left, $b->left) + && $this->areTreesEqual($a->right, $b->right); + } +} diff --git a/DataStructures/DisjointSets/DisjointSet.php b/DataStructures/DisjointSets/DisjointSet.php new file mode 100644 index 00000000..8046ae55 --- /dev/null +++ b/DataStructures/DisjointSets/DisjointSet.php @@ -0,0 +1,49 @@ +parent) { + // Path compression: make the parent point directly to the root + $node->parent = $this->findSet($node->parent); + } + 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; + } + } + } +} diff --git a/DataStructures/DisjointSets/DisjointSetNode.php b/DataStructures/DisjointSets/DisjointSetNode.php new file mode 100644 index 00000000..c8b885e1 --- /dev/null +++ b/DataStructures/DisjointSets/DisjointSetNode.php @@ -0,0 +1,29 @@ +data = $data; + $this->rank = 0; + $this->parent = $this; // Initialize parent to itself + } +} diff --git a/DataStructures/DoublyLinkedList.php b/DataStructures/DoublyLinkedList.php new file mode 100644 index 00000000..42a423b3 --- /dev/null +++ b/DataStructures/DoublyLinkedList.php @@ -0,0 +1,379 @@ +head = null; + $this->tail = null; + } + + // Destructor + public function __destruct() + { + $this->head = null; + $this->tail = null; + } + + // Append to the end of the list + public function append($data): void + { + $newNode = new Node($data); + + // If the list is empty, set the head and tail to the new node + if ($this->head === null) { + $this->head = $newNode; + $this->tail = $newNode; + return; + } + + // Otherwise, set the tail's next node to the new node + $this->tail->next = $newNode; + + // Set the new node's previous node to the tail + $newNode->prev = $this->tail; + + // Set the tail to the new node + $this->tail = $newNode; + } + + // Insert a node after a given position + public function insert($data, $position): void + { + $newNode = new Node($data); + + // If the list is empty, set the head and tail to the new node + if ($this->head === null) { + $this->head = $newNode; + $this->tail = $newNode; + return; + } + + // If the position is 0, set the new node's next node to the head + // Set the head's previous node to the new node + // Set the head to the new node + if ($position === 0) { + $newNode->next = $this->head; + $this->head->prev = $newNode; + $this->head = $newNode; + return; + } + + // Otherwise, set the current node to the head + $current = $this->head; + + // Loop through the list until we reach the position + for ($i = 0; $i < $position; $i++) { + // If the current node is null, we've reached the end of the list + // Set the tail's next node to the new node + // Set the new node's previous node to the tail + // Set the tail to the new node + if ($current === null) { + $this->tail->next = $newNode; + $newNode->prev = $this->tail; + $this->tail = $newNode; + return; + } + + // Otherwise, set the current node to the next node + $current = $current->next; + } + + // Set the new node's next node to the current node + // Set the new node's previous node to the current node's previous node + // Set the current node's previous node's next node to the new node + // Set the current node's previous node to the new node + $newNode->next = $current; + $newNode->prev = $current->prev; + $current->prev->next = $newNode; + $current->prev = $newNode; + } + + // Delete a node from the list + public function delete($data): void + { + // If the list is empty, return + if ($this->head === null) { + return; + } + + // If the head's data is the data we're looking for + // Set the head to the head's next node + // Set the head's previous node to null + if ($this->head->data === $data) { + $this->head = $this->head->next; + $this->head->prev = null; + return; + } + + // Otherwise, set the current node to the head + $current = $this->head; + + // Loop through the list until we reach the end of the list + while ($current !== null) { + // If the current node's data is the data we're looking for + // Set the current node's previous node's next node to the current node's next node + // Set the current node's next node's previous node to the current node's previous node + if ($current->data === $data) { + $current->prev->next = $current->next; + $current->next->prev = $current->prev; + return; + } + + // Otherwise, set the current node to the next node + $current = $current->next; + } + } + + // Delete a node from a given position + public function deleteAt($position): void + { + // If the list is empty, return + if ($this->head === null) { + return; + } + + // If the position is 0 + // Set the head to the head's next node + // Set the head's previous node to null + if ($position === 0) { + $this->head = $this->head->next; + $this->head->prev = null; + return; + } + + // Otherwise, set the current node to the head + $current = $this->head; + + // Loop through the list until we reach the position + for ($i = 0; $i < $position; $i++) { + // If the current node is null, we've reached the end of the list + // Set the tail to the current node's previous node + // Set the tail's next node to null + if ($current === null) { + $this->tail = $current->prev; + $this->tail->next = null; + return; + } + + // Otherwise, set the current node to the next node + $current = $current->next; + } + + // Set the current node's previous node's next node to the current node's next node + // Set the current node's next node's previous node to the current node's previous node + $current->prev->next = $current->next; + $current->next->prev = $current->prev; + } + + // Print the list + public function printList(): void + { + // If the list is empty, return + if ($this->head === null) { + return; + } + + // Otherwise, set the current node to the head + $current = $this->head; + + // Loop through the list until we reach the end of the list + while ($current !== null) { + // Print the current node's data + echo $current->data . "\n"; + + // Set the current node to the next node + $current = $current->next; + } + } + + // Print the list in reverse + public function printListReverse(): void + { + // If the list is empty, return + if ($this->head === null) { + return; + } + + // Otherwise, set the current node to the tail + $current = $this->tail; + + // Loop through the list until we reach the beginning of the list + while ($current !== null) { + // Print the current node's data + echo $current->data . "\n"; + + // Set the current node to the previous node + $current = $current->prev; + } + } + + // Reverse the list + public function reverse(): void + { + // If the list is empty, return + if ($this->head === null) { + return; + } + + // Otherwise, set the current node to the head + $current = $this->head; + + // Loop through the list until we reach the end of the list + while ($current !== null) { + // Set the temp node to the current node's next node + $temp = $current->next; + + // Set the current node's next node to the current node's previous node + $current->next = $current->prev; + + // Set the current node's previous node to the temp node + $current->prev = $temp; + + // Set the current node to the temp node + $current = $temp; + } + + // Set the temp node to the head + $temp = $this->head; + + // Set the head to the tail + $this->head = $this->tail; + + // Set the tail to the temp node + $this->tail = $temp; + } + + // Get the length of the list + public function length(): int + { + // If the list is empty, return 0 + if ($this->head === null) { + return 0; + } + + // Otherwise, set the current node to the head + $current = $this->head; + + // Set the length to 0 + $length = 0; + + // Loop through the list until we reach the end of the list + while ($current !== null) { + // Increment the length + $length++; + + // Set the current node to the next node + $current = $current->next; + } + + // Return the length + return $length; + } + + // Search for a node + public function search($data): ?Node + { + // If the list is empty, return null + if ($this->head === null) { + return null; + } + + // Otherwise, set the current node to the head + $current = $this->head; + + // Loop through the list until we reach the end of the list + while ($current !== null) { + // If the current node's data is the data we're looking for, return the current node + if ($current->data === $data) { + return $current; + } + + // Set the current node to the next node + $current = $current->next; + } + + // Return null + return null; + } + + // Is the list empty? + public function isEmpty(): bool + { + // If the head is null, return true + if ($this->head === null) { + return true; + } + + // Otherwise, return false + return false; + } + + // To String + public function __toString(): string + { + // If the list is empty, return an empty string + if ($this->head === null) { + return ''; + } + + // Otherwise, set the current node to the head + $current = $this->head; + + // Set the string to an empty string + $string = ''; + + // Loop through the list until we reach the end of the list + while ($current !== null) { + // Append the current node's data to the string + $string .= $current->data; + + // If the current node's next node is not null, append a comma and a space to the string + if ($current->next !== null) { + $string .= ', '; + } + + // Set the current node to the next node + $current = $current->next; + } + + // Return the string + return $string; + } + + // To Array + public function toArray(): array + { + // If the list is empty, return an empty array + if ($this->head === null) { + return []; + } + + // Otherwise, set the current node to the head + $current = $this->head; + + // Set the array to an empty array + $array = []; + + // Loop through the list until we reach the end of the list + while ($current !== null) { + // Append the current node's data to the array + $array[] = $current->data; + + // Set the current node to the next node + $current = $current->next; + } + + // Return the array + return $array; + } +} diff --git a/DataStructures/InvertBinaryTree/BinaryTree.php b/DataStructures/InvertBinaryTree/BinaryTree.php new file mode 100644 index 00000000..38419351 --- /dev/null +++ b/DataStructures/InvertBinaryTree/BinaryTree.php @@ -0,0 +1,43 @@ +left = $left; + return $this; + } + + public function getLeft(): ?BinaryTree + { + return $this->left; + } + + public function setRight(?BinaryTree $right) + { + $this->right = $right; + return $this; + } + + public function getRight(): ?BinaryTree + { + return $this->right; + } + + public function setValue($value) + { + $this->value = $value; + return $this; + } + + public function getValue() + { + return $this->value; + } +} diff --git a/DataStructures/InvertBinaryTree/InvertBinaryTree.php b/DataStructures/InvertBinaryTree/InvertBinaryTree.php new file mode 100644 index 00000000..346f74c1 --- /dev/null +++ b/DataStructures/InvertBinaryTree/InvertBinaryTree.php @@ -0,0 +1,24 @@ +getLeft(); + $b->setLeft($b->getRight()); + $b->setRight($tmp); + $this->invert($b->getLeft()); + $this->invert($b->getRight()); + } +} diff --git a/DataStructures/Node.php b/DataStructures/Node.php new file mode 100644 index 00000000..e7178d08 --- /dev/null +++ b/DataStructures/Node.php @@ -0,0 +1,17 @@ +data = $data; + } +} diff --git a/DataStructures/Queue.php b/DataStructures/Queue.php new file mode 100644 index 00000000..360e0acc --- /dev/null +++ b/DataStructures/Queue.php @@ -0,0 +1,80 @@ +elements = []; + $this->count = 0; + $this->lowestCount = 0; + } + + public function enqueue($element): void + { + $this->elements[$this->count] = $element; + $this->count++; + } + + public function dequeue() + { + if ($this->isEmpty()) { + return null; + } + + $element = $this->elements[$this->lowestCount]; + + unset($this->elements[$this->lowestCount]); + + $this->lowestCount++; + + return $element; + } + + public function isEmpty(): bool + { + return $this->count - $this->lowestCount === 0; + } + + public function size(): int + { + return $this->count - $this->lowestCount; + } + + public function peek() + { + if ($this->isEmpty()) { + return null; + } + + return $this->elements[$this->lowestCount]; + } + + public function clear(): void + { + $this->elements = []; + $this->count = 0; + $this->lowestCount = 0; + } + + public function toString(string $delimiter = ''): string + { + if ($this->isEmpty()) { + return ''; + } + + $result = "{$this->elements[$this->lowestCount]}"; + + for ($i = $this->lowestCount + 1; $i < $this->count; $i++) { + $result .= "{$delimiter}{$this->elements[$i]}"; + } + + return $result; + } +} diff --git a/DataStructures/ReverseLinkedList/LinkedListItem.php b/DataStructures/ReverseLinkedList/LinkedListItem.php new file mode 100644 index 00000000..1e2ae822 --- /dev/null +++ b/DataStructures/ReverseLinkedList/LinkedListItem.php @@ -0,0 +1,43 @@ +next = $next; + return $this; + } + + public function getNext(): ?LinkedListItem + { + return $this->next; + } + + public function setPrev(?LinkedListItem $prev) + { + $this->prev = $prev; + return $this; + } + + public function getPrev(): ?LinkedListItem + { + return $this->prev; + } + + public function setValue($value) + { + $this->value = $value; + return $this; + } + + public function getValue() + { + return $this->value; + } +} diff --git a/DataStructures/ReverseLinkedList/ReverseLinkedList.php b/DataStructures/ReverseLinkedList/ReverseLinkedList.php new file mode 100644 index 00000000..cde6384b --- /dev/null +++ b/DataStructures/ReverseLinkedList/ReverseLinkedList.php @@ -0,0 +1,28 @@ +getNext(); + $item->setNext(null); + while (true) { + $item->setPrev($next); + if (! $next) { + return $item; + } + $nextNext = $next->getNext(); + $next->setNext($item); + $item = $next; + $next = $nextNext; + } + } +} diff --git a/DataStructures/SegmentTree/SegmentTree.php b/DataStructures/SegmentTree/SegmentTree.php new file mode 100644 index 00000000..fbc39a1f --- /dev/null +++ b/DataStructures/SegmentTree/SegmentTree.php @@ -0,0 +1,332 @@ + 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; + } +} diff --git a/DataStructures/SegmentTree/SegmentTreeNode.php b/DataStructures/SegmentTree/SegmentTreeNode.php new file mode 100644 index 00000000..bb017c67 --- /dev/null +++ b/DataStructures/SegmentTree/SegmentTreeNode.php @@ -0,0 +1,38 @@ +start = $start; + $this->end = $end; + $this->value = $value; + $this->left = null; + $this->right = null; + } +} diff --git a/DataStructures/SinglyLinkedList.php b/DataStructures/SinglyLinkedList.php index 44621d60..b7a97bce 100644 --- a/DataStructures/SinglyLinkedList.php +++ b/DataStructures/SinglyLinkedList.php @@ -1,8 +1,8 @@ next)) { $current = $current->next; } @@ -27,7 +26,6 @@ public function append($data): void public function delete($data): SinglyLinkedList { $current = $this; - if ($current->data == $data) { return $current->next; } @@ -35,7 +33,6 @@ public function delete($data): SinglyLinkedList while ($current instanceof SinglyLinkedList && isset($current->next)) { if ($current->next->data === $data) { $current->next = $current->next->next; - return $this; } diff --git a/DataStructures/SplayTree/SplayTree.php b/DataStructures/SplayTree/SplayTree.php new file mode 100644 index 00000000..c14b64ab --- /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 + */ + protected 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..3375caa1 --- /dev/null +++ b/DataStructures/SplayTree/SplayTreeRotations.php @@ -0,0 +1,175 @@ +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/DataStructures/Stack.php b/DataStructures/Stack.php new file mode 100644 index 00000000..eaeb7c58 --- /dev/null +++ b/DataStructures/Stack.php @@ -0,0 +1,85 @@ +stack = $array; + } + + public function __destruct() + { + unset($this->stack); + } + + + public function push($data): void + { + array_push($this->stack, $data); + } + + public function pop() + { + return array_pop($this->stack); + } + + public function peek() + { + return $this->stack[count($this->stack) - 1]; + } + + public function isEmpty(): bool + { + return empty($this->stack); + } + + public function print(): void + { + echo implode(', ', $this->stack); + } + + public function __toString(): string + { + return implode(', ', $this->stack); + } + + public function length(): int + { + return count($this->stack); + } + + public function clear(): void + { + $this->stack = []; + } + + public function search($data): int + { + return array_search($data, $this->stack); + } + + public function toArray(): array + { + return $this->stack; + } + + public function fromArray(array $array): void + { + $this->stack = $array; + } + + public function reverse(): void + { + $this->stack = array_reverse($this->stack); + } + + public function sort(): void + { + sort($this->stack); + } +} diff --git a/DataStructures/Trie/Trie.php b/DataStructures/Trie/Trie.php new file mode 100644 index 00000000..03b353a0 --- /dev/null +++ b/DataStructures/Trie/Trie.php @@ -0,0 +1,173 @@ +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 + { + $prefix = strtolower($prefix); // Normalize the prefix to lowercase + $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..303da902 --- /dev/null +++ b/DataStructures/Trie/TrieNode.php @@ -0,0 +1,63 @@ + */ + 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 + { + $char = $this->normalizeChar($char); + 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 + { + $char = $this->normalizeChar($char); + return isset($this->children[$char]); + } + + /** + * Get the child node corresponding to a character. + */ + public function getChild(string $char): ?TrieNode + { + $char = $this->normalizeChar($char); + return $this->children[$char] ?? null; + } + + /** + * Normalize the character to lowercase. + */ + private function normalizeChar(string $char): string + { + return strtolower($char); + } +} diff --git a/Graphs/BellmanFord.php b/Graphs/BellmanFord.php new file mode 100644 index 00000000..7173e6ea --- /dev/null +++ b/Graphs/BellmanFord.php @@ -0,0 +1,47 @@ + $minWeight) { + if ($verbose) { + echo "checking vertex $vertice\n"; + } + if ($start === $vertice) { + $vertices[$vertice] = 0; + } + + foreach ($edges[$vertice] as $edge) { + if ($vertices[$edge->end] > $vertices[$vertice] + $edge->weight) { + if ($verbose) { + echo "replace $vertice " . $vertices[$edge->end] . " with " + . ($vertices[$vertice] + $edge->weight) . "\n "; + } + $vertices[$edge->end] = $vertices[$vertice] + $edge->weight; + $change = true; + } + } + } + $round++; + } + return $vertices; +} diff --git a/Graphs/BreadthFirstSearch.php b/Graphs/BreadthFirstSearch.php index 44110114..205e7f14 100644 --- a/Graphs/BreadthFirstSearch.php +++ b/Graphs/BreadthFirstSearch.php @@ -1,23 +1,25 @@ start == $nextVertex) { //consider only nodes connected to current one + $vertices[$edge->end] = min($vertices[$edge->end], $vertices[$nextVertex] + $edge->weight); + } + } + + // find vertex with current lowest value to be starting point in next iteration + $minVertexName = null; + $minVertexWeight = PHP_INT_MAX; + foreach ($vertices as $name => $weight) { + if (in_array($name, $visitedNodes) || $name == $nextVertex) { + continue; + } + if ($weight <= $minVertexWeight) { + $minVertexName = $name; + $minVertexWeight = $weight; + } + } + $visitedNodes[] = $nextVertex; + $nextVertex = $minVertexName; + } + return $vertices; +} diff --git a/Graphs/GraphEdge.php b/Graphs/GraphEdge.php new file mode 100644 index 00000000..df73f082 --- /dev/null +++ b/Graphs/GraphEdge.php @@ -0,0 +1,8 @@ + diff --git a/Maths/BaseX.php b/Maths/BaseX.php new file mode 100644 index 00000000..58483980 --- /dev/null +++ b/Maths/BaseX.php @@ -0,0 +1,29 @@ + diff --git a/Maths/CheckPrime.php b/Maths/CheckPrime.php index 64630435..f9d14522 100644 --- a/Maths/CheckPrime.php +++ b/Maths/CheckPrime.php @@ -1,4 +1,5 @@ > 1); + // Convert the integer back to a float + $y = unpack('f', pack('l', $i))[1]; + // Perform the final calculation + return $y * (1.5 - 0.5 * $x * $y * $y); +} diff --git a/Maths/Fibonacci.php b/Maths/Fibonacci.php index 975db26b..bc6b7062 100644 --- a/Maths/Fibonacci.php +++ b/Maths/Fibonacci.php @@ -3,8 +3,9 @@ /** * Fibonacci recursive * - * @param int $num - * @return int + * @param int $num + * @return array + * @throws \Exception */ function fibonacciRecursive(int $num) { @@ -20,8 +21,9 @@ function fibonacciRecursive(int $num) } /** - * @param int $num + * @param int $num * @return int + * @throws \Exception */ function recursive(int $num) { @@ -36,6 +38,9 @@ function recursive(int $num) } } +/** + * @throws \Exception + */ function fibonacciWithBinetFormula(int $num) { /* @@ -57,7 +62,6 @@ function fibonacciWithBinetFormula(int $num) $seriesNumber = (pow($phi_1, $n) - pow($phi_2, $n)) / $sqrt; array_push($fib_series, (int)$seriesNumber); } - } return $fib_series; diff --git a/Maths/Fibonacci2.php b/Maths/Fibonacci2.php index c7dff110..942ec691 100644 --- a/Maths/Fibonacci2.php +++ b/Maths/Fibonacci2.php @@ -10,8 +10,7 @@ */ function loop($i, Generator $set) { - while($i-- > 0 && $set->valid()) - { + while ($i-- > 0 && $set->valid()) { yield $set->current(); $set->next(); } @@ -25,8 +24,7 @@ function fib() yield $i = 0; yield $j = 1; - while(true) - { + while (true) { yield $k = $i + $j; $i = $j; $j = $k; @@ -36,7 +34,6 @@ function fib() /* * Generate 100 Fibonacci numbers */ -foreach(loop(100, fib()) as $item) -{ - print($item.','); -} \ No newline at end of file +foreach (loop(100, fib()) as $item) { + print($item . ','); +} diff --git a/Maths/GreatestCommonDivisor.php b/Maths/GreatestCommonDivisor.php new file mode 100644 index 00000000..54758359 --- /dev/null +++ b/Maths/GreatestCommonDivisor.php @@ -0,0 +1,16 @@ + 6 + * @param int $a + * @param int $b + * @return int + */ +function gcd(int $a, int $b): int +{ + if ($b == 0) { + return $a; + } + return gcd($b, $a % $b); +} diff --git a/Maths/Mean.php b/Maths/Mean.php index 52f01202..b24b4e01 100644 --- a/Maths/Mean.php +++ b/Maths/Mean.php @@ -1,11 +1,13 @@ diff --git a/Maths/PerfectNumber.php b/Maths/PerfectNumber.php new file mode 100644 index 00000000..5d7112e6 --- /dev/null +++ b/Maths/PerfectNumber.php @@ -0,0 +1,39 @@ + $largest) { + if (strrev((string)$product) == (string)$product && $product > $largest) { $largest = $product; } } diff --git a/Maths/ProjectEuler/Problem6.php b/Maths/ProjectEuler/Problem6.php index 15a91ecb..35fd8e5c 100644 --- a/Maths/ProjectEuler/Problem6.php +++ b/Maths/ProjectEuler/Problem6.php @@ -5,10 +5,10 @@ * * Problem description: * The sum of the squares of the first ten natural numbers is, - * 1 ** 2 + 2 ** 2 + ... + 10 ** 2 = 385 + * 1 ** 2 + 2 ** 2 + ... + 10 ** 2 = 385 * * The square of the sum of the first ten natural numbers is, - * (1 + 2 + ... + 10) ** 2 = 3025 + * (1 + 2 + ... + 10) ** 2 = 3025 * * Hence the difference between the sum of the squares of the * first ten natural numbers and the square of the sum is . diff --git a/Maths/ProjectEuler/Problem8.php b/Maths/ProjectEuler/Problem8.php index 737254f1..aa0a215f 100644 --- a/Maths/ProjectEuler/Problem8.php +++ b/Maths/ProjectEuler/Problem8.php @@ -59,11 +59,11 @@ function problem8(): int $substringSize = 13; for ($i = 0; $i < strlen($theNumber) - $substringSize; $i++) { - $currentSubstring = substr($theNumber,$i,$substringSize); + $currentSubstring = substr($theNumber, $i, $substringSize); $currentProduct = 0; - for ($j = 0; $j < strlen ($currentSubstring); $j++) { - $currentProduct = ($currentProduct == 0 ? (int)$currentSubstring[$j] : $currentProduct * (int)$currentSubstring[$j]); + for ($j = 0; $j < strlen($currentSubstring); $j++) { + $currentProduct = ($currentProduct == 0 ? (int)$currentSubstring[$j] : $currentProduct * (int)$currentSubstring[$j]); } $greatestProduct = ($greatestProduct < $currentProduct ? $currentProduct : $greatestProduct); diff --git a/NeuralNetworks/PerceptronClassifier/NeuralNetworkPerceptronClassifier.php b/NeuralNetworks/PerceptronClassifier/NeuralNetworkPerceptronClassifier.php new file mode 100644 index 00000000..6b94e543 --- /dev/null +++ b/NeuralNetworks/PerceptronClassifier/NeuralNetworkPerceptronClassifier.php @@ -0,0 +1,192 @@ +initParams(count($X)); + + for ($i = 0; $i < $iterations; $i++) { + // Forward propagation + $A = $this->forwardPropagation($X, $W, $b); + + // Compute cost + $cost = $this->computeCost($A, $Y); + + // Backward propagation + [$dW, $db] = $this->backwardPropagation($A, $X, $Y); + + // Update parameters + [$W, $b] = $this->updateParams($W, $b, $dW, $db, $learningRate); + + if ($i % 100 == 0) { + echo "Iteration {$i} - Cost: {$cost}\n"; + } + } + + return [$W, $b]; + } + + /** + * @param array $X + * @param array $W + * @param float $b + * @return array + */ + public function predict(array $X, array $W, float $b): array + { + $A = $this->forwardPropagation($X, $W, $b); + return array_map(fn($a) => $a > 0.5 ? 1 : 0, $A); + } + + /** + * Stage 1. Prepare dataset + * @return array[] + */ + public function generateTrainingSet(): array + { + $m = 50; + + // Generate a 2 x m matrix with binary values (0 or 1) + $X = []; + for ($i = 0; $i < 2; $i++) { + for ($j = 0; $j < $m; $j++) { + $X[$i][$j] = rand(0, 1); + } + } + + // Compute Y: Logical AND condition (X[0] == 1 and X[1] == 0) + $Y = []; + for ($j = 0; $j < $m; $j++) { + $Y[$j] = ($X[0][$j] == 1 && $X[1][$j] == 0) ? 1 : 0; + } + + return [$X, $Y]; + } + + /** + * Stage 2. Initialize model parameters + * @param int $n Number of features + * @return array [$W, $b] Weight and bias arrays + */ + private function initParams(int $n): array + { + $W = []; + for ($i = 0; $i < $n; $i++) { + $W[$i] = mt_rand() / mt_getrandmax(); // Small random values + } + $b = 0.0; // Bias initialized to zero + return [$W, $b]; + } + + /** + * Sigmoid Activation Function + * @param float $z + * @return float + */ + private function sigmoid(float $z): float + { + return 1 / (1 + exp(-$z)); + } + + /** + * Stage 3. Forward Propagation + * @param array $X + * @param array $W + * @param float $b + * @return array + */ + private function forwardPropagation(array $X, array $W, float $b): array + { + $Z = []; + for ($j = 0; $j < count($X[0]); $j++) { + $sum = $b; + for ($i = 0; $i < count($W); $i++) { + $sum += $W[$i] * $X[$i][$j]; + } + $Z[$j] = $this->sigmoid($sum); + } + return $Z; + } + + /** + * Stage 4. Compute Cost Function (Binary Cross-Entropy Loss) + * @param array $A + * @param array $Y + * @return float + */ + private function computeCost(array $A, array $Y): float + { + $m = count($Y); + $cost = 0.0; + for ($i = 0; $i < $m; $i++) { + $cost += -($Y[$i] * log($A[$i]) + (1 - $Y[$i]) * log(1 - $A[$i])); + } + return $cost / $m; + } + + /** + * Stage 5. Backward Propagation + * @param array $A + * @param array $X + * @param array $Y + * @return array + */ + private function backwardPropagation(array $A, array $X, array $Y): array + { + $m = count($Y); + $dW = array_fill(0, count($X), 0.0); + $db = 0.0; + + for ($j = 0; $j < $m; $j++) { + $dZ = $A[$j] - $Y[$j]; + for ($i = 0; $i < count($X); $i++) { + $dW[$i] += $dZ * $X[$i][$j]; + } + $db += $dZ; + } + + // Average gradients + for ($i = 0; $i < count($dW); $i++) { + $dW[$i] /= $m; + } + $db /= $m; + + return [$dW, $db]; + } + + /** + * STage 6. Update Parameters + * @param array $W + * @param float $b + * @param array $dW + * @param float $db + * @param float $learningRate + * @return array + */ + private function updateParams(array $W, float $b, array $dW, float $db, float $learningRate): array + { + for ($i = 0; $i < count($W); $i++) { + $W[$i] -= $learningRate * $dW[$i]; + } + $b -= $learningRate * $db; + + return [$W, $b]; + } +} diff --git a/NeuralNetworks/PerceptronClassifier/README.md b/NeuralNetworks/PerceptronClassifier/README.md new file mode 100644 index 00000000..870c9727 --- /dev/null +++ b/NeuralNetworks/PerceptronClassifier/README.md @@ -0,0 +1,100 @@ +## Maths behind the single Perceptron Neural Network with Activation Function + +This work is based on examples from course https://www.coursera.org/learn/machine-learning-calculus prepared by author Luis Serrano. + +Linear separation refers to data points in binary classification problems that can be separated by a linear decision boundary. +If the data points can be separated by a line, linear function, or flat hyperplane, they are said to be linearly separable. + +If separate points in an n-dimensional space exist, then it is said to be linearly separable + +$$w_1x_1 + w_2x_2 + w_nx_n + b = 0$$ + +For two-dimensional input data, if there is a line, whose equation is $$w_1x_1 + w_2x_2 + b = 0$$ + +that separates all samples of one class from the other class, then the corresponding observation can be derived from the equation of the separating line. +Such classification problems are called "linearly separable", i.e. separating by linear combination. + + + +The input layer contains two nodes $x_1$ and $x_2$. Weight vector $W = \begin{bmatrix} w_1 & w_2\end{bmatrix}$ and bias ($b$) are the parameters to be updated during the model training. + +$$z^{(i)} = w_1x_1^{(i)} + w_2x_2^{(i)} + b = Wx^{(i)} + b.\tag{1}$$ + +To be able to perform classification we need nonlinear approach. This can achieved with sigmoid activation function which roughly replace values with nearly 0 or nearly 1 for most cases and some values between for small range near 0. + +$$\hat{y} = \begin{cases} 1 & \mbox{if } a > 0.5 \\ 0 & \mbox{otherwise } \end{cases}\tag{10}$$ + +Sigmoid activation function is defined as + +$$a = \sigma\left(z\right) = \frac{1}{1+e^{-z}}.\tag{2}$$ + + + +Threshold value of $0.5$ can be used for predictions: $1$ (red) if $a > 0.5$ and $0$ (blue) otherwise. + +The single perceptron neural network with sigmoid activation function can be expressed as: + +\begin{align} +z^{(i)} &= W x^{(i)} + b,\\ +a^{(i)} &= \sigma\left(z^{(i)}\right).\\\tag{3} +\end{align} + + +With $m$ training examples organised in the columns of ($2 \times m$) matrix $X$, you can apply the activation function element-wise. So the model can be written as: + + +\begin {align} +Z &= W X + b,\\ +A &= \sigma\left(Z\right),\\\tag{4} +\end{align} + +When dealing with classification problems, the most commonly used cost function is the **log loss**, which is described by the following equation + +$$\mathcal{L}\left(W, b\right) = \frac{1}{m}\sum_{i=1}^{m} L\left(W, b\right) = \frac{1}{m}\sum_{i=1}^{m} \large\left(\small -y^{(i)}\log\left(a^{(i)}\right) - (1-y^{(i)})\log\left(1- a^{(i)}\right) \large \right) \small,\tag{5}$$ + +where $y^{(i)} \in \{0,1\}$ are the original labels and $a^{(i)}$ are the continuous output values of the forward propagation step (elements of array $A$). + + +We want to minimize the cost function during the training. To implement gradient descent, calculate partial derivatives using chain rule + + +\begin{align} +\frac{\partial \mathcal{L} }{ \partial w_1 } &= +\frac{1}{m}\sum_{i=1}^{m} \left(a^{(i)} - y^{(i)}\right)x_1^{(i)},\\ +\frac{\partial \mathcal{L} }{ \partial w_2 } &= +\frac{1}{m}\sum_{i=1}^{m} \left(a^{(i)} - y^{(i)}\right)x_2^{(i)},\tag{7}\\ +\frac{\partial \mathcal{L} }{ \partial b } &= +\frac{1}{m}\sum_{i=1}^{m} \left(a^{(i)} - y^{(i)}\right). +\end{align} + +Equations above can be rewritten in a matrix form + + +\begin{align} +\frac{\partial \mathcal{L} }{ \partial W } &= +\begin{bmatrix} \frac{\partial \mathcal{L} }{ \partial w_1 } & +\frac{\partial \mathcal{L} }{ \partial w_2 }\end{bmatrix} = \frac{1}{m}\left(A - Y\right)X^T,\\ +\frac{\partial \mathcal{L} }{ \partial b } &= \frac{1}{m}\left(A - Y\right)\mathbf{1}. +\tag{8} +\end{align} + +where $\left(A - Y\right)$ is an array of a shape ($1 \times m$), $X^T$ is an array of a shape ($m \times 2$) and $\mathbf{1}$ is just a ($m \times 1$) vector of ones. + +Then you can update the parameters: + +\begin{align} +W &= W - \alpha \frac{\partial \mathcal{L} }{ \partial W },\\ +b &= b - \alpha \frac{\partial \mathcal{L} }{ \partial b }, +\tag{9}\end{align} + +where $\alpha$ is the learning rate. Repeat the process in a loop until the cost function stops decreasing. + +in last step apply activation +$$\hat{y} = \begin{cases} 1 & \mbox{if } a > 0.5 \\ 0 & \mbox{otherwise } \end{cases}\tag{10}$$ + + +### Dataset + +As a dataset we will generate $m=50$ data points $(x_1, x_2)$, where $x_1, x_2 \in \{0,1\}$ and save them in the `NumPy` array `X` of a shape $(2 \times m)$. The labels ($0$: blue, $1$: red) will be calculated so that $y = 1$ if $x_1 = 1$ and $x_2 = 0$, in the rest of the cases $y=0$. The labels will be saved in the array `Y` of a shape $(1 \times m)$. + + diff --git a/NeuralNetworks/PerceptronClassifier/chart/dataset.png b/NeuralNetworks/PerceptronClassifier/chart/dataset.png new file mode 100644 index 00000000..95a5042a Binary files /dev/null and b/NeuralNetworks/PerceptronClassifier/chart/dataset.png differ diff --git a/NeuralNetworks/PerceptronClassifier/chart/linear-separated.png b/NeuralNetworks/PerceptronClassifier/chart/linear-separated.png new file mode 100644 index 00000000..061be4bc Binary files /dev/null and b/NeuralNetworks/PerceptronClassifier/chart/linear-separated.png differ diff --git a/NeuralNetworks/PerceptronClassifier/chart/sigmoid.png b/NeuralNetworks/PerceptronClassifier/chart/sigmoid.png new file mode 100644 index 00000000..98560311 Binary files /dev/null and b/NeuralNetworks/PerceptronClassifier/chart/sigmoid.png differ diff --git a/README.md b/README.md index 7afa9dc6..f7a4a144 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # The Algorithms - PHP +[![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/TheAlgorithms/PHP) + The Algorithms - PHP, is a library or framework written in the PHP programming language that provides a set of algorithms and data structures for various computational tasks. It aims to simplify the implementation of common algorithms and data structures in PHP, making it easier for developers to leverage these powerful tools in their projects. ### All algorithms implemented in PHP - for education diff --git a/Searches/BinarySearch.php b/Searches/BinarySearch.php index e3b1e310..10789307 100644 --- a/Searches/BinarySearch.php +++ b/Searches/BinarySearch.php @@ -21,7 +21,7 @@ function binarySearchIterative($list, $target) { $first = 0; - $last = count($list)-1; + $last = count($list) - 1; while ($first <= $last) { @@ -70,8 +70,9 @@ function binarySearchByRecursion($list, $target, $start, $end) return $list[0] == $target ? 0 : null; } - if ($start > $end) + if ($start > $end) { return null; + } $mid = ($start + $end) >> 1; @@ -80,9 +81,9 @@ function binarySearchByRecursion($list, $target, $start, $end) if ($list[$mid] == $target) { return $mid; } elseif ($list[$mid] > $target) { - return binarySearchByRecursion($list, $target, $start, $mid-1); + return binarySearchByRecursion($list, $target, $start, $mid - 1); } elseif ($list[$mid] < $target) { - return binarySearchByRecursion($list, $target, $mid+1, $end); + return binarySearchByRecursion($list, $target, $mid + 1, $end); } return null; diff --git a/Searches/ExponentialSearch.php b/Searches/ExponentialSearch.php index 7073053d..1ea8a1c9 100644 --- a/Searches/ExponentialSearch.php +++ b/Searches/ExponentialSearch.php @@ -1,60 +1,66 @@ $ceiling) return -1; - // search the left part of the $array - // If the $middle element is great than the $value - if ($arr[$mid] > $value) { - return binarySearch($arr, $value, $floor, $mid - 1); - } - // search the right part of the $array - // If the $middle element is lower than the $value - else { - return binarySearch($arr, $value, $mid + 1, $ceiling); - } + +/** + * @param Array $arr + * @param int $value + * @param int $floor + * @param int $ceiling + * @return int + **/ +function binarySearch($arr, $value, $floor, $ceiling) +{ + // Get $middle index + $mid = floor(($floor + $ceiling) / 2); + + // Return position if $value is at the $mid position + if ($arr[$mid] === $value) { + return (int) $mid; + } + + //Return -1 is range is wrong + if ($floor > $ceiling) { + return -1; + } + + // search the left part of the $array + // If the $middle element is greater than the $value + if ($arr[$mid] > $value) { + return binarySearch($arr, $value, $floor, $mid - 1); + } else { // search the right part of the $array If the $middle element is lower than the $value + return binarySearch($arr, $value, $mid + 1, $ceiling); + } +} + +/** + * @param Array $arr + * @param int $value + * @return int + */ +function exponentialSearch($arr, $value) +{ + // If $value is the first element of the $array return this position + if ($arr[0] === $value) { + return 0; + } + + // Find range for binary search + $i = 1; + $length = count($arr); + while ($i < $length && $arr[$i] <= $value) { + $i = $i * 2; + } + $floor = $i / 2; + $ceiling = min($i, $length); + + // Call binary search for the range found above + return binarySearch($arr, $value, $floor, $ceiling); } - /** - * @param Array $arr - * @param int $length - * @param int $value - * @return int - **/ -function exponentialSearch ($arr, $value) { - // If $value is the first element of the $array return this position - if ($arr[0] === $value) { - return 0; - } - - // Find range for binary search - $i = 1; - $length = count($arr); - while ($i < $length && $arr[$i] <= $value) { - $i = $i * 2; - } - $floor = $i/2; - $ceiling = min($i, $length); - - // Call binary search for the range found above - return binarySearch($arr, $value, $floor, $ceiling); -} \ No newline at end of file diff --git a/Searches/FibonacciSearch.php b/Searches/FibonacciSearch.php index 7e04a4b9..b103785a 100644 --- a/Searches/FibonacciSearch.php +++ b/Searches/FibonacciSearch.php @@ -11,13 +11,16 @@ */ function fibonacciPosition(int $n, array &$m = []) { - if(isset($m[$n])) return $m[$n]; - if($n < 2) return $n; + if (isset($m[$n])) { + return $m[$n]; + } + if ($n < 2) { + return $n; + } $m[$n] = fibonacciPosition($n - 1, $m) + fibonacciPosition($n - 2, $m); return $m[$n]; - } print fibonacciPosition(59); diff --git a/Searches/InterpolationSearch.php b/Searches/InterpolationSearch.php index 4e27c7dc..fcd3dee8 100644 --- a/Searches/InterpolationSearch.php +++ b/Searches/InterpolationSearch.php @@ -1,12 +1,13 @@ key decrease the high index * repeat the loop */ -function interpolationSearch($arr, $key) { - $length = count($arr) - 1; - $low = 0; - $high = $length; - $position = -1; - //loop, between low & high - while ($low <= $high && $key >= $arr[$low] && $key <= $arr[$high]) { - //GET INDEX - $delta = ($key - $arr[$low]) / ($arr[$high] - $arr[$low]); - $index = $low + floor(($high - $low) * $delta); - //GET VALUE OF INDEX IN ARRAY... - $indexValue = $arr[$index]; - if ($indexValue === $key) { - //index value equals key - //FOUND TARGET - //return index value - $position = $index; - return (int) $position; +function interpolationSearch($arr, $key) +{ + $length = count($arr) - 1; + $low = 0; + $high = $length; + $position = -1; + //loop, between low & high + while ($low <= $high && $key >= $arr[$low] && $key <= $arr[$high]) { + //GET INDEX + $delta = ($key - $arr[$low]) / ($arr[$high] - $arr[$low]); + $index = $low + floor(($high - $low) * $delta); + + //GET VALUE OF INDEX IN ARRAY... + $indexValue = $arr[$index]; + + if ($indexValue === $key) { + //index value equals key + //FOUND TARGET + //return index value + $position = $index; + return (int) $position; + } elseif ($indexValue < $key) { + //index value lower than key + //increase low index + $low = $index + 1; + } elseif ($indexValue > $key) { + //index value higher than key + //decrease high index + $high = $index - 1; + } } - if ($indexValue < $key) { - //index value lower than key - //increase low index - $low = $index + 1; - } - if ($indexValue > $key) { - //index value higher than key - //decrease high index - $high = $index - 1; - } - } - //when key not found in array or array not sorted - return null; -} \ No newline at end of file + + //when key not found in array or array not sorted + return null; +} diff --git a/Searches/JumpSearch.php b/Searches/JumpSearch.php index adda458b..a84daa7e 100644 --- a/Searches/JumpSearch.php +++ b/Searches/JumpSearch.php @@ -1,40 +1,39 @@ = $num) - return -1; - } + /*number of elements in the sorted array*/ + $num = count($list); - /*Performing linear search for $key in block*/ - while ($list[$prev] < $key) - { - $prev++; - if ($prev == min($step, $num)) - return -1; - } - - return $list[$prev] === $key ? $prev : -1; -} + /*block size to be jumped*/ + $step = (int)sqrt($num); + $prev = 0; + while ($list[min($step, $num) - 1] < $key) { + $prev = $step; + $step += (int)sqrt($num); + if ($prev >= $num) { + return -1; + } + } + /*Performing linear search for $key in block*/ + while ($list[$prev] < $key) { + $prev++; + if ($prev == min($step, $num)) { + return -1; + } + } + return $list[$prev] === $key ? $prev : -1; +} diff --git a/Searches/LinearSearch.php b/Searches/LinearSearch.php index 4de38be3..2d8ee41e 100644 --- a/Searches/LinearSearch.php +++ b/Searches/LinearSearch.php @@ -5,10 +5,6 @@ * * Reference: https://www.geeksforgeeks.org/linear-search/ * - * @param Array $list a array of integers to search - * @param integer $target an integer number to search for in the list - * @return integer the index where the target is found (or -1 if not found) - * * Examples: * data = 5, 7, 8, 11, 12, 15, 17, 18, 20 * x = 15 @@ -17,16 +13,15 @@ * x = 1 * Element not found * - * @param Array $list a array of integers to search + * @param Array $list an array of integers to search * @param integer $target an integer number to search for in the list * @return integer the index where the target is found (or -1 if not found) */ function linearSearch($list, $target) { $n = sizeof($list); - for($i = 0; $i < $n; $i++) - { - if($list[$i] == $target) { + for ($i = 0; $i < $n; $i++) { + if ($list[$i] == $target) { return $i + 1; } } diff --git a/Searches/LowerBound.php b/Searches/LowerBound.php index cf465555..ba4ddbb5 100644 --- a/Searches/LowerBound.php +++ b/Searches/LowerBound.php @@ -10,24 +10,25 @@ * [C++ Lower Bound](http://www.cplusplus.com/reference/algorithm/lower_bound/) * * It is assumed that an integer array is provided - * and the second parameter is also a integer + * and the second parameter is also an integer * - * @param array of sorted integers - * @param integer whose lower bound is to be found + * @param array $arr of sorted integers + * @param integer $elem whose lower bound is to be found * - * @return the index of lower bound of the given element + * @return int the index of lower bound of the given element */ -function lowerBound(array $arr, int $elem){ +function lowerBound(array $arr, int $elem) +{ isSortedAscendingInts($arr); $hi = count($arr); $lo = 0; - while($lo < $hi){ - $mid = $lo + floor(($hi - $lo)/2); + while ($lo < $hi) { + $mid = $lo + floor(($hi - $lo) / 2); - if($arr[$mid] < $elem){ - $lo = $mid+1; - }else{ + if ($arr[$mid] < $elem) { + $lo = $mid + 1; + } else { $hi = $mid; } } diff --git a/Searches/SentinelSearch.php b/Searches/SentinelSearch.php new file mode 100644 index 00000000..135f5471 --- /dev/null +++ b/Searches/SentinelSearch.php @@ -0,0 +1,42 @@ + $arr[$mid2]) { - // the $key lies in between $mid2 and $high - return ternarySearchByRecursion($arr, $key, $mid2 + 1, $high); + // Key is in the left section, between $low and $mid1. + return ternarySearchByRecursion($arr, $key, $low, $mid1 - 1); + } elseif ($key > $arr[$mid2]) { + // Key is in the right section, between $mid2 and $high. + return ternarySearchByRecursion($arr, $key, $mid2 + 1, $high); } else { - // the $key lies in between $mid1 and $mid2 - return ternarySearchByRecursion($arr, $key, $mid1 + 1, $mid2 - 1); + // Key is in the middle section, between $mid1 and $mid2. + return ternarySearchByRecursion($arr, $key, $mid1 + 1, $mid2 - 1); } } -function ternarySearchIterative ($arr, $key) { - $low = 0; $high = count($arr) - 1; - while ($high >= $low) { - // find the $mid1 and $mid2 - $mid1 = floor($low + ($high - $low) / 3); - $mid2 = floor($high - ($high - $low) / 3); +function ternarySearchIterative($arr, $key) +{ + // Initialize low and high pointers. + $low = 0; + $high = count($arr) - 1; - // check if $key is found at any $mid - if ($arr[$mid1] === $key) { - // return index of $key if found - return $mid1; - } - if ($arr[$mid2] === $key) { - // return index of $key if found - return $mid2; - } + // Continue searching while the high pointer is greater than or equal to the low pointer. + while ($high >= $low) { + // Calculate the first and second midpoints. + $mid1 = floor($low + ($high - $low) / 3); + $mid2 = floor($high - ($high - $low) / 3); - // since the $key is not found at $mid, - // check in which region it is present - // and repeat the Search operation - // in that region - if ($key < $arr[$mid1]) { - // the $key lies in between $low and $mid1 - $high = $mid1 - 1; - } else if ($key > $arr[$mid2]) { - // the $key lies in between $mid2 and $high - $low = $mid2 + 1; - } else { - // the $key lies in between $mid1 and $mid2 - $low = $mid1 + 1; - $high = $mid2 - 1; + // Check if the key is found at either midpoint. + if ($arr[$mid1] === $key) { + return $mid1; + } + + if ($arr[$mid2] === $key) { + return $mid2; + } + + // Determine the section to continue the search in. + if ($key < $arr[$mid1]) { + // Key is in the left section, update the high pointer. + $high = $mid1 - 1; + } elseif ($key > $arr[$mid2]) { + // Key is in the right section, update the low pointer. + $low = $mid2 + 1; + } else { + // Key is in the middle section, update both pointers. + $low = $mid1 + 1; + $high = $mid2 - 1; + } } - } - // the $key was not found - return null; + + // Key was not found. + return null; } diff --git a/Searches/TwoPointers.php b/Searches/TwoPointers.php new file mode 100644 index 00000000..f05e5daa --- /dev/null +++ b/Searches/TwoPointers.php @@ -0,0 +1,62 @@ + $target) { + // the sum is greater than the target, so we need to decrease the sum + // to decrease the sum we will move our $right pointer to the left + $right--; + } else if ($list[$left] + $list[$right] == $target) { + // if it's true, we have found a pair + $ans++; + // now we will move one of our pointers, otherwise it'll run forever + $left++; # doesn't matter which one we move + } + // The loop will go until the pointers point to the same element + } + return $ans; # returning the number of pairs +} diff --git a/Searches/UpperBound.php b/Searches/UpperBound.php index 6cfbffe3..47d62518 100644 --- a/Searches/UpperBound.php +++ b/Searches/UpperBound.php @@ -10,24 +10,25 @@ * [C++ Lower Bound](http://www.cplusplus.com/reference/algorithm/upper_bound/) * * It is assumed that an integer array is provided - * and the second parameter is also a integer + * and the second parameter is also an integer * - * @param array of sorted integers - * @param integer whose upper bound is to be found + * @param array $arr of sorted integers + * @param integer $elem whose upper bound is to be found * - * @return the index of upper bound of the given element + * @return int the index of upper bound of the given element */ -function upperBound(array $arr, int $elem){ +function upperBound(array $arr, int $elem) +{ isSortedAscendingInts($arr); $hi = count($arr); $lo = 0; - while($lo < $hi){ - $mid = $lo + floor(($hi - $lo)/2); + while ($lo < $hi) { + $mid = $lo + floor(($hi - $lo) / 2); - if($arr[$mid] <= $elem){ + if ($arr[$mid] <= $elem) { $lo = $mid + 1; - }else{ + } else { $hi = $mid; } } diff --git a/Sorting/ArrayKeysSort.php b/Sorting/ArrayKeysSort.php index 034258ec..4c97830d 100644 --- a/Sorting/ArrayKeysSort.php +++ b/Sorting/ArrayKeysSort.php @@ -1,4 +1,5 @@ $key) || !isset($b->$key)) { - $errorMsg = 'The key "' . $key - . '" does not exist in the collection'; - throw new Exception($errorMsg); - } - $item1 = !$isCaseSensitive - ? strtolower($a->$key) : $a->$key; - $item2 = !$isCaseSensitive - ? strtolower($b->$key) : $b->$key; + throw new Exception($errorMsg); } - } while ($item1 === $item2 && !empty($keys[++$pos])); - - if ($item1 === $item2) { - return 0; - } elseif ($order === self::ORDER_ASC) { - return ($item1 < $item2) ? -1 : 1; + $item1 = !$isCaseSensitive + ? strtolower($a[$key]) : $a[$key]; + $item2 = !$isCaseSensitive + ? strtolower($b[$key]) : $b[$key]; } else { - return ($item1 > $item2) ? -1 : 1; + if (!isset($a->$key) || !isset($b->$key)) { + $errorMsg = 'The key "' . $key + . '" does not exist in the collection'; + throw new Exception($errorMsg); + } + $item1 = !$isCaseSensitive + ? strtolower($a->$key) : $a->$key; + $item2 = !$isCaseSensitive + ? strtolower($b->$key) : $b->$key; } + } while ($item1 === $item2 && !empty($keys[++$pos])); + if ($item1 === $item2) { + return 0; + } elseif ($order === self::ORDER_ASC) { + return ($item1 < $item2) ? -1 : 1; + } else { + return ($item1 > $item2) ? -1 : 1; } - ); + }); } catch (Exception $e) { echo $e->getMessage(); die(); diff --git a/Sorting/BubbleSort.php b/Sorting/BubbleSort.php index b46e7a0b..9bc66795 100644 --- a/Sorting/BubbleSort.php +++ b/Sorting/BubbleSort.php @@ -6,13 +6,14 @@ * @param array $array * @return array */ -function bubbleSort($array) { +function bubbleSort($array) +{ $length = count($array); for ($i = $length; $i > 0; $i--) { $swapped = true; - for ($j=0;$j<$i-1;$j++) { + for ($j = 0; $j < $i - 1; $j++) { if ($array[$j] > $array[$j + 1]) { $temp = $array[$j]; $array[$j] = $array[$j + 1]; @@ -21,7 +22,9 @@ function bubbleSort($array) { } } - if ($swapped) break; + if ($swapped) { + break; + } } return $array; diff --git a/Sorting/BubbleSort2.php b/Sorting/BubbleSort2.php index f29b3394..97d0757f 100644 --- a/Sorting/BubbleSort2.php +++ b/Sorting/BubbleSort2.php @@ -1,4 +1,5 @@ 0 ) { + for ($i = $min; $i <= $max; $i++) { + while ($count[$i]-- > 0) { $array[$z++] = $i; } } diff --git a/Sorting/GnomeSort.php b/Sorting/GnomeSort.php index 003e0970..a9a88ce8 100644 --- a/Sorting/GnomeSort.php +++ b/Sorting/GnomeSort.php @@ -4,26 +4,25 @@ * Gnome Sort * References: * https://www.geeksforgeeks.org/gnome-sort-a-stupid-one/ - * + * * The Gnome algorithm works by locating the first instance in which two adjoining elements are arranged incorrectly and swaps with each other. - * + * * @param array $array refers to the array to be sorted * @return array */ - -function gnomeSort($array){ +function gnomeSort($array) +{ $a = 1; $b = 2; - while($a < count($array)){ - - if ($array[$a-1] <= $array[$a]){ + while ($a < count($array)) { + if ($array[$a - 1] <= $array[$a]) { $a = $b; $b++; - }else{ - list($array[$a],$array[$a-1]) = array($array[$a-1],$array[$a]); + } else { + list($array[$a],$array[$a - 1]) = array($array[$a - 1],$array[$a]); $a--; - if($a == 0){ + if ($a == 0) { $a = $b; $b++; } diff --git a/Sorting/HeapSort.php b/Sorting/HeapSort.php new file mode 100644 index 00000000..8cd0c523 --- /dev/null +++ b/Sorting/HeapSort.php @@ -0,0 +1,66 @@ += 0; $i--) { + heapify($arr, $n, $i); + } + + // Extract elements from the max heap and build the sorted array. + for ($i = $n - 1; $i >= 0; $i--) { + // Swap the root(maximum value) of the heap with the last element of the heap. + [$arr[0], $arr[$i]] = [$arr[$i], $arr[0]]; + + // Heapify the reduced heap. + heapify($arr, $i, 0); + } + + // Return the sorted array. + return $arr; +} + +/** + * Ensures that the array satisfies the heap property + * + * @param array $arr + * @param int $n + * @param int $i + */ +function heapify(array &$arr, int $n, int $i): void +{ + $largest = $i; + $left = 2 * $i + 1; + $right = 2 * $i + 2; + if ($left < $n && $arr[$left] > $arr[$largest]) { + $largest = $left; + } + + if ($right < $n && $arr[$right] > $arr[$largest]) { + $largest = $right; + } + + if ($largest !== $i) { + [$arr[$i], $arr[$largest]] = [$arr[$largest], $arr[$i]]; + heapify($arr, $n, $largest); + } +} diff --git a/Sorting/InsertionSort.php b/Sorting/InsertionSort.php index b184e545..fcffaa00 100644 --- a/Sorting/InsertionSort.php +++ b/Sorting/InsertionSort.php @@ -8,13 +8,11 @@ */ function insertionSort(array $array) { - for ($i = 1; $i < count($array); $i++) - { + for ($i = 1; $i < count($array); $i++) { $currentVal = $array[$i]; - for ($j = $i - 1; $j >= 0 && $array[$j] > $currentVal; $j--) - { - $array[$j + 1] = $array[$j]; + for ($j = $i - 1; $j >= 0 && $array[$j] > $currentVal; $j--) { + $array[$j + 1] = $array[$j]; } $array[$j + 1] = $currentVal; @@ -22,5 +20,3 @@ function insertionSort(array $array) return $array; } - - diff --git a/Sorting/MergeSort.php b/Sorting/MergeSort.php index 7923917a..23c64178 100644 --- a/Sorting/MergeSort.php +++ b/Sorting/MergeSort.php @@ -9,12 +9,12 @@ function mergeSort(array $arr) { if (count($arr) <= 1) { - return $arr; + return $arr; } - $mid = floor( count($arr) / 2 ); - $leftArray = mergeSort( array_slice($arr, 0, $mid) ); - $rightArray = mergeSort( array_slice($arr, $mid) ); + $mid = floor(count($arr) / 2); + $leftArray = mergeSort(array_slice($arr, 0, $mid)); + $rightArray = mergeSort(array_slice($arr, $mid)); return merge($leftArray, $rightArray); } @@ -52,6 +52,3 @@ function merge(array $leftArray, array $rightArray) return $result; } - - - diff --git a/Sorting/QuickSort.php b/Sorting/QuickSort.php index 5c00e146..4fc82aad 100644 --- a/Sorting/QuickSort.php +++ b/Sorting/QuickSort.php @@ -1,4 +1,5 @@ = $gap && $array[$j - $gap] > $temp) { + $array[$j] = $array[$j - $gap]; + $j -= $gap; + } + + $array[$j] = $temp; + } + } + + return $array; +} + +/** + * Calculate Knuth's series + * + * @param int $n Size of the array + * @return array + */ +function calculateKnuthSeries(int $n): array +{ + $h = 1; + $series = []; + + while ($h < $n) { + array_unshift($series, $h); + $h = 3 * $h + 1; + } + + return $series; +} diff --git a/Strings/CheckAnagram.php b/Strings/CheckAnagram.php index d36a17dc..93ec37d3 100644 --- a/Strings/CheckAnagram.php +++ b/Strings/CheckAnagram.php @@ -10,20 +10,12 @@ */ function isAnagram(string $originalString, string $testString, bool $caseInsensitive = true) { - if ($caseInsensitive) - { + if ($caseInsensitive) { $originalString = strtolower($originalString); // Converting string to lowercase for case-insensitive check $testString = strtolower($testString); } // count_chars(string, mode = 1) returns key-value pairs with character as key, frequency as value // We can directly compare the arrays in this case - if (count_chars($originalString, 1) == count_chars($testString, 1)) - { - return true; - } - else - { - return false; - } + return count_chars($originalString, 1) === count_chars($testString, 1); } diff --git a/Strings/CheckPalindrome.php b/Strings/CheckPalindrome.php index 8fa7e9fc..be2b19c9 100644 --- a/Strings/CheckPalindrome.php +++ b/Strings/CheckPalindrome.php @@ -11,22 +11,18 @@ function isPalindrome(string $string, bool $caseInsensitive = true) { $string = trim($string); // Removing leading and trailing spaces - if (empty($string)) - { + if (empty($string)) { return false; // Returning false for an Empty String } - if ($caseInsensitive) - { + if ($caseInsensitive) { $string = strtolower($string); // Converting string to lowercase for case-insensitive check } $characters = str_split($string); - for ($i = 0; $i < count($characters); $i++) - { - if ($characters[$i] !== $characters[count($characters) - ($i + 1)]) - { + for ($i = 0; $i < count($characters); $i++) { + if ($characters[$i] !== $characters[count($characters) - ($i + 1)]) { return false; } } diff --git a/Strings/CheckPalindrome2.php b/Strings/CheckPalindrome2.php index 56019256..88655999 100644 --- a/Strings/CheckPalindrome2.php +++ b/Strings/CheckPalindrome2.php @@ -5,16 +5,17 @@ * using php strrev() function * make it simple * - * @param string $string - * @param bool $caseInsensitive + * @param string $string + * @param bool $caseInsensitive + * @return string + * @throws \Exception */ -function checkPalindromeString(string $string, bool $caseInsensitive = true): String +function checkPalindromeString(string $string, bool $caseInsensitive = true): string { //removing spaces $string = trim($string); - if (empty($string)) - { + if (empty($string)) { throw new \Exception('You are given empty string. Please give a non-empty string value'); } @@ -22,13 +23,11 @@ function checkPalindromeString(string $string, bool $caseInsensitive = true): St * for case-insensitive * lowercase string conversion */ - if ($caseInsensitive) - { + if ($caseInsensitive) { $string = strtolower($string); } - if ($string !== strrev($string)) - { + if ($string !== strrev($string)) { return $string . " - not a palindrome string." . PHP_EOL; } diff --git a/Strings/CountConsonants.php b/Strings/CountConsonants.php index d8637926..df53999a 100644 --- a/Strings/CountConsonants.php +++ b/Strings/CountConsonants.php @@ -4,10 +4,11 @@ * Function returns the total number of consonants present in the given * string using a linear search through the string * - * @param string $string + * @param string $string * @return int + * @throws \Exception */ -function countConsonants(string $string): Int +function countConsonants(string $string): int { if (empty($string)) { throw new \Exception('Please pass a non-empty string value'); @@ -18,11 +19,11 @@ function countConsonants(string $string): Int $consonantCount = 0; - for ($i = 0; $i < strlen($string); $i++) - { - if (!in_array($string[$i], $vowels) && - $string[$i] >= 'a' && $string[$i] <= 'z') - { + for ($i = 0; $i < strlen($string); $i++) { + if ( + !in_array($string[$i], $vowels) && + $string[$i] >= 'a' && $string[$i] <= 'z' + ) { $consonantCount++; } } diff --git a/Strings/CountHomogenous.php b/Strings/CountHomogenous.php new file mode 100644 index 00000000..8478a9d6 --- /dev/null +++ b/Strings/CountHomogenous.php @@ -0,0 +1,32 @@ += 0; $i--) - { + for ($i = (count($words) - 1); $i >= 0; $i--) { $reversedWords[] = $words[$i]; } diff --git a/Utils/ArrayHelpers.php b/Utils/ArrayHelpers.php index 9666aa61..37602e99 100644 --- a/Utils/ArrayHelpers.php +++ b/Utils/ArrayHelpers.php @@ -1,24 +1,25 @@ + + + + + + . + + vendor + + + 0 + + diff --git a/tests/Ciphers/AtbashCipherTest.php b/tests/Ciphers/AtbashCipherTest.php new file mode 100644 index 00000000..0db404e6 --- /dev/null +++ b/tests/Ciphers/AtbashCipherTest.php @@ -0,0 +1,25 @@ +assertEquals($plainText, $decryptedText); + } + + public function testWithNonAlphabetCharacters() + { + $plainText = "HELLO, WORLD!"; + $encryptedText = atbash_encrypt($plainText); + $decryptedText = atbash_decrypt($encryptedText); + $this->assertEquals($plainText, $decryptedText); + } +} diff --git a/tests/Ciphers/CiphersTest.php b/tests/Ciphers/CiphersTest.php index d6204811..83e52546 100755 --- a/tests/Ciphers/CiphersTest.php +++ b/tests/Ciphers/CiphersTest.php @@ -1,6 +1,5 @@ assertEquals( $input_str, xorCipher( xorCipher( $input_str , $key) , $key)); - $this->assertNotEquals( $input_str, xorCipher( xorCipher( $input_str , $key) , $invalid_key)); + $this->assertEquals($input_str, xorCipher(xorCipher($input_str, $key), $key)); + $this->assertNotEquals($input_str, xorCipher(xorCipher($input_str, $key), $invalid_key)); } } diff --git a/tests/Ciphers/MonoAlphabeticCipherTest.php b/tests/Ciphers/MonoAlphabeticCipherTest.php index 4509ab4c..b8f99788 100644 --- a/tests/Ciphers/MonoAlphabeticCipherTest.php +++ b/tests/Ciphers/MonoAlphabeticCipherTest.php @@ -1,23 +1,19 @@ - +assertEquals(maEncrypt($key, $alphabet, $text), $encryptedText); + $this->assertEquals(maDecrypt($key, $alphabet, $encryptedText), "I love GitHub"); + } +} diff --git a/tests/Ciphers/MorseCodeTest.php b/tests/Ciphers/MorseCodeTest.php index 7c9b97b7..bdb2b031 100644 --- a/tests/Ciphers/MorseCodeTest.php +++ b/tests/Ciphers/MorseCodeTest.php @@ -1,6 +1,5 @@ assertEquals('TEST 123', decode(encode('TEST 123'))); } } diff --git a/tests/Ciphers/RailfenceCipherTest.php b/tests/Ciphers/RailfenceCipherTest.php new file mode 100644 index 00000000..b37fe81c --- /dev/null +++ b/tests/Ciphers/RailfenceCipherTest.php @@ -0,0 +1,26 @@ +assertEquals($plainMessage, $decodedMessage); + } + public function testRailfenceCipherCase2() + { + $plainMessage = "THIS IS RAILFENCE"; + $rails = 3; + $cipherMessage = Railencode($plainMessage, $rails); + $decodedMessage = Raildecode($cipherMessage, $rails); + $this->assertEquals($plainMessage, $decodedMessage); + } +} diff --git a/tests/Ciphers/VignereCipherTest.php b/tests/Ciphers/VignereCipherTest.php new file mode 100644 index 00000000..dc39626f --- /dev/null +++ b/tests/Ciphers/VignereCipherTest.php @@ -0,0 +1,19 @@ +assertEquals($plaintext, $decryptedText); + } +} diff --git a/tests/Conversions/ConversionsTest.php b/tests/Conversions/ConversionsTest.php index 026f94fe..93893b4e 100644 --- a/tests/Conversions/ConversionsTest.php +++ b/tests/Conversions/ConversionsTest.php @@ -1,6 +1,5 @@ assertEquals(7, binaryToDecimal(111)); + $this->assertEquals(5, binaryToDecimal(101)); $this->expectException(\Exception::class); $this->expectExceptionMessage('Please pass a valid Binary Number for Converting it to a Decimal Number.'); binaryToDecimal("this is a string"); @@ -24,8 +24,8 @@ public function testBinaryToDecimal() public function testDecimalToBinary() { - assertEquals(decimalToBinary(7), 111); - assertEquals(decimalToBinary(5), 101); + $this->assertEquals(111, decimalToBinary(7)); + $this->assertEquals(101, decimalToBinary(5)); $this->expectException(\Exception::class); $this->expectExceptionMessage('Please pass a valid Decimal Number for Converting it to a Binary Number.'); decimalToBinary("this is a string"); @@ -33,9 +33,9 @@ public function testDecimalToBinary() public function testOctalToDecimal() { - assertEquals(octalToDecimal(10), 8); - assertEquals(octalToDecimal(11), 9); - assertEquals(octalToDecimal(1115), 589); + $this->assertEquals(8, octalToDecimal(10)); + $this->assertEquals(9, octalToDecimal(11)); + $this->assertEquals(589, octalToDecimal(1115)); $this->expectException(\Exception::class); $this->expectExceptionMessage('Please pass a valid Octal Number for Converting it to a Decimal Number.'); octalToDecimal("this is a string"); @@ -43,9 +43,9 @@ public function testOctalToDecimal() public function testDecimalToOctal() { - assertEquals(decimalToOctal(8), 10); - assertEquals(decimalToOctal(9), 11); - assertEquals(decimalToOctal(589), 1115); + $this->assertEquals(10, decimalToOctal(8)); + $this->assertEquals(11, decimalToOctal(9)); + $this->assertEquals(1115, decimalToOctal(589)); $this->expectException(\Exception::class); $this->expectExceptionMessage('Please pass a valid Decimal Number for Converting it to an Octal Number.'); decimalToOctal("this is a string"); @@ -53,9 +53,9 @@ public function testDecimalToOctal() public function testDecimalToHex() { - assertEquals(decimalToHex(10), 'A'); - assertEquals(decimalToHex(489201875), '1D28A0D3'); - assertEquals(decimalToHex(171), 'AB'); + $this->assertEquals('A', decimalToHex(10)); + $this->assertEquals('1D28A0D3', decimalToHex(489201875)); + $this->assertEquals('AB', decimalToHex(171)); $this->expectException(\Exception::class); $this->expectExceptionMessage('Please pass a valid Decimal Number for Converting it to a Hexadecimal Number.'); decimalToHex("this is a string"); @@ -63,9 +63,9 @@ public function testDecimalToHex() public function testHexToDecimal() { - assertEquals(hexToDecimal('A'), 10); - assertEquals(hexToDecimal('1D28A0D3'), 489201875); - assertEquals(hexToDecimal('AB'), 171); + $this->assertEquals(10, hexToDecimal('A')); + $this->assertEquals(489201875, hexToDecimal('1D28A0D3')); + $this->assertEquals(171, hexToDecimal('AB')); $this->expectException(\Exception::class); $this->expectExceptionMessage('Please pass a valid Hexadecimal Number for Converting it to a Decimal Number.'); hexToDecimal("this is a string"); @@ -73,12 +73,12 @@ public function testHexToDecimal() public function testSpeedConversion() { - assertEquals(convertSpeed(5, 'm/s', 'mph'), 11.18); - assertEquals(convertSpeed(5, 'ft/s', 'km/h'), 5.49); - assertEquals(convertSpeed(3, 'km/h', 'km/h'), 3); - assertEquals(convertSpeed(7, 'kn', 'km/h'), 12.96); - assertEquals(convertSpeed(12, 'mph', 'km/h'), 19.31); - assertEquals(convertSpeed(0.514, 'm/s', 'kn'), 1); + $this->assertEquals(11.18, convertSpeed(5, 'm/s', 'mph')); + $this->assertEquals(5.49, convertSpeed(5, 'ft/s', 'km/h')); + $this->assertEquals(3, convertSpeed(3, 'km/h', 'km/h')); + $this->assertEquals(12.96, convertSpeed(7, 'kn', 'km/h')); + $this->assertEquals(19.31, convertSpeed(12, 'mph', 'km/h')); + $this->assertEquals(1, convertSpeed(0.514, 'm/s', 'kn')); $this->expectException(\Exception::class); convertSpeed('1', 'km/h', 'mph'); @@ -86,4 +86,60 @@ public function testSpeedConversion() $this->expectException(\Exception::class); convertSpeed(1, 'km/h', 'miles'); } + + public function testCelsiusToFahrenheit() + { + $this->assertEquals(32.0, CelsiusToFahrenheit(0)); + $this->assertEquals(212.0, CelsiusToFahrenheit(100)); + $this->assertEquals(98.6, CelsiusToFahrenheit(37)); + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Temperature (Celsius) must be a number'); + CelsiusToFahrenheit("non-numeric"); + } + + public function testFahrenheitToCelsius() + { + $this->assertEquals(0.0, FahrenheitToCelsius(32)); + $this->assertEquals(100.0, FahrenheitToCelsius(212)); + $this->assertEquals(37.0, FahrenheitToCelsius(98.6)); + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Temperature (Fahrenheit) must be a number'); + FahrenheitToCelsius("non-numeric"); + } + + public function testCelsiusToKelvin() + { + $this->assertEquals(273.15, CelsiusToKelvin(0)); + $this->assertEquals(373.15, CelsiusToKelvin(100)); + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Temperature (Celsius) must be a number'); + CelsiusToKelvin("non-numeric"); + } + + public function testKelvinToCelsius() + { + $this->assertEquals(0.0, KelvinToCelsius(273.15)); + $this->assertEquals(100.0, KelvinToCelsius(373.15)); + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Temperature (Kelvin) must be a number'); + KelvinToCelsius("non-numeric"); + } + + public function testKelvinToFahrenheit() + { + $this->assertEquals(32.0, KelvinToFahrenheit(273.15)); + $this->assertEquals(212.0, KelvinToFahrenheit(373.15)); + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Temperature (Kelvin) must be a number'); + KelvinToFahrenheit("non-numeric"); + } + + public function testFahrenheitToKelvin() + { + $this->assertEquals(273.15, FahrenheitToKelvin(32)); + $this->assertEquals(373.15, FahrenheitToKelvin(212)); + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Temperature (Fahrenheit) must be a number'); + FahrenheitToKelvin("non-numeric"); + } } diff --git a/tests/DataStructures/AVLTreeTest.php b/tests/DataStructures/AVLTreeTest.php new file mode 100644 index 00000000..26729115 --- /dev/null +++ b/tests/DataStructures/AVLTreeTest.php @@ -0,0 +1,366 @@ +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'); + } + + /** + * Tests the insert and search operations in the AVLTree. + */ + 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'); + } + + /** + * Tests the deletion of nodes and ensures the AVLTree maintains + * its integrity after deletions. + */ + 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' + ); + } + + /** + * Tests the insertion and deletion of a large number of nodes. + */ + 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"); + } + } + + /** + * Tests whether the AVLTree remains balanced after insertions. + */ + 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'); + $method->setAccessible(true); + + $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'); + } + + /** + * Test serialization and deserialization + */ + public function testAVLTreeSerialization(): void + { + $avlTree = new AVLTree(); + + $avlTree->insert(100, 'Value 100'); + $avlTree->insert(200, 'Value 200'); + $avlTree->insert(50, 'Value 50'); + $avlTree->insert(150, 'Value 150'); + $avlTree->insert(350, 'Value 350'); + $avlTree->insert(40, 'Value 40'); + $avlTree->insert(90, 'Value 90'); + + $avlTreeRoot = $avlTree->getRoot(); + $serializedAVLTree = $avlTree->serialize(); + + $deserializedTree = new AVLTree(); + $deserializedTree->deserialize($serializedAVLTree); + + $deserializedTreeRoot = $deserializedTree->getRoot(); + + $this->assertEquals($deserializedTreeRoot->key, $avlTreeRoot->key, 'The two roots key should match'); + $this->assertEquals($deserializedTreeRoot->value, $avlTreeRoot->value, 'The two roots value should match'); + $this->assertEquals( + $deserializedTreeRoot->left->key, + $avlTreeRoot->left->key, + 'Left child of the two roots should match' + ); + $this->assertEquals( + $deserializedTreeRoot->right->key, + $avlTreeRoot->right->key, + 'Left child of the two roots should match' + ); + $this->assertEquals( + $deserializedTreeRoot->height, + $avlTreeRoot->height, + 'The two trees should match in height' + ); + $this->assertEquals($deserializedTree->size(), $avlTree->size(), 'The two trees should match in size'); + + $this->assertSame( + $deserializedTree->inOrderTraversal(), + $avlTree->inOrderTraversal(), + 'Tree structure was not retained' + ); + } +} diff --git a/tests/DataStructures/BSTreeTest.php b/tests/DataStructures/BSTreeTest.php new file mode 100644 index 00000000..633cac98 --- /dev/null +++ b/tests/DataStructures/BSTreeTest.php @@ -0,0 +1,598 @@ +tree = new BSTree(); + } + + public function testTreeInitialization() + { + $this->assertNull($this->tree->getRoot(), "Tree root should be null upon initialization."); + $this->assertEquals(0, $this->tree->size(), "Tree size should be 0 upon initialization."); + $this->assertTrue($this->tree->isEmpty(), "Tree should be empty upon initialization."); + } + + /** + * Test: Insert a single node + */ + public function testInsertSingleNode(): void + { + $this->tree->insert(10, 'value10'); + $this->assertNotNull($this->tree->getRoot(), "Tree root should not be null after inserting."); + $this->assertEquals(10, $this->tree->getRoot()->key, "Node key does not match the key inserted in node"); + $this->assertEquals( + 'value10', + $this->tree->getRoot()->value, + "Node value does not match the value inserted in node" + ); + } + + /** + * Test: Insert multiple nodes and validate small structure + */ + public function testInsertMultipleNodes(): void + { + $this->tree->insert(20, 'value20'); + $this->tree->insert(10, 'value10'); + $this->tree->insert(30, 'value30'); + + $this->assertEquals(20, $this->tree->getRoot()->key, "Root node was not set properly."); + $this->assertEquals(10, $this->tree->getRoot()->left->key, "Left node was not inserted properly"); + $this->assertEquals(30, $this->tree->getRoot()->right->key, "Right node was not inserted properly"); + } + + /** + * Test: Insert multiple nodes from array and validate structure + * + */ + public function testInsertMultipleNodesFromArray() + { + $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", 28 => "Value 28", 164 => "Value 164", + 321 => "Value 321", 40 => "Value 40", 9 => "Value 9", 32 => "Value 32", 64 => "Value 64", + ]; + + $bsTree = new BSTree($arrayData); + $root = $bsTree->getRoot(); + + $this->assertFalse($bsTree->isEmpty(), "Tree was not populated correctly"); + $this->assertSame(count($arrayData), $bsTree->size(), "Failed to insert all " . count($arrayData) . " nodes"); + + $this->assertEquals(200, $root->key, "The root should be the first inserted node"); + $this->assertEquals("Value 200", $root->value, "The value of the new root must match the first inserted node"); + } + + /** + * Test: Duplicate key insertion should throw an exception + */ + public function testInsertDuplicateKey(): void + { + $this->tree->insert(15, 'value15'); + $this->expectException(DuplicateKeyException::class); + $this->tree->insert(15, 'valueNew'); + } + + /** + * 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."); + } + + /** + * Helper to populate the initialized tree for further tests + * + * The structure of the Binary Search Tree (BST) after insertion: + * * + * * 200 + * * / \ + * * 150 250 + * * / \ / \ + * * 140 170 220 300 + * * / / \ / \ \ + * * 130 160 180 215 230 360 + * * / \ / \ + * * 110 185 225 240 + * * / \ + * * 50 115 + * * / + * * 70 + */ + private function populateTree(): void + { + $this->tree->insert(200, "Value 200"); + $this->tree->insert(150, "Value 150"); + $this->tree->insert(250, "Value 250"); + $this->tree->insert(170, "Value 170"); + $this->tree->insert(140, "Value 140"); + $this->tree->insert(130, "Value 130"); + $this->tree->insert(110, "Value 110"); + $this->tree->insert(115, "Value 115"); + $this->tree->insert(160, "Value 160"); + $this->tree->insert(180, "Value 180"); + $this->tree->insert(185, "Value 185"); + $this->tree->insert(220, "Value 220"); + $this->tree->insert(230, "Value 230"); + $this->tree->insert(300, "Value 300"); + $this->tree->insert(360, "Value 360"); + $this->tree->insert(215, "Value 215"); + $this->tree->insert(240, "Value 240"); + $this->tree->insert(225, "Value 225"); + $this->tree->insert(50, "Value 50"); + $this->tree->insert(70, "Value 70"); + } + + /** + * Test: Search for an existing node + */ + public function testSearchNodeExists(): void + { + $this->populateTree(); + $node = $this->tree->search(115); + $isFound = $this->tree->isFound($this->tree->getRoot(), 230); + + $this->assertNotNull($node, "The node with key 115 exists. Should not be null."); + $this->assertEquals(115, $node->key, "The node key does not match"); + $this->assertEquals('Value 115', $node->value, "The node value does not match"); + $this->assertTrue($isFound, "Node with key 230 exists."); + } + + /** + * Test: Search for a non-existing node + */ + public function testSearchNodeNotExists(): void + { + $this->populateTree(); + + $node = $this->tree->search(444); + $isFound = $this->tree->isFound($this->tree->getRoot(), 1500); + + $this->assertNull($node, "Node with key 444 does not exist"); + $this->assertFalse($isFound, "Node with key 1500 does not exist."); + } + + /** + * Test: Remove a leaf node + */ + public function testRemoveLeafNode(): void + { + $this->populateTree(); + + $this->assertTrue($this->tree->search(360)->isLeaf(), "The node with key 360 is leaf."); + $parentNode = $this->tree->search(360)->parent->key; // 300 + + $removedNode = $this->tree->remove(360); + $this->assertNull($this->tree->search(360), "Node with key 360 should be gone"); + $this->assertNotNull( + $this->tree->search($parentNode), + "Parent node with key 300 should still exist. Tree was not merged correctly." + ); + + $this->assertEquals(360, $removedNode->key, "The key of the removed node does not match"); + $this->assertEquals('Value 360', $removedNode->value, "The value of the removed node does not match"); + $this->assertNull( + $removedNode->left, + "The left pointer was not broken from tree correctly. Node isolation failed." + ); + $this->assertNull( + $removedNode->right, + "The right pointer was not broken from tree correctly. Node isolation failed." + ); + $this->assertNull( + $removedNode->parent, + "The parent pointer was not broken from tree correctly. Node isolation failed." + ); + } + + /** + * Test: Remove a node with one child + */ + public function testRemoveNodeWithOneChild(): void + { + $this->populateTree(); + + $this->assertEquals(1, $this->tree->search(140)->getChildrenCount(), "The node with key 140 has one child."); + $parentNode = $this->tree->search(140)->parent->key; // 150 + $leftNode = $this->tree->search(140)->left->key; // 130 + + $removedNode = $this->tree->remove(140); + $this->assertNull($this->tree->search(140), "Node with key 140 should be gone"); + $this->assertNotNull( + $this->tree->search($parentNode), + "Parent node with key 150 should still exist. Tree was not merged correctly." + ); + $this->assertNotNull( + $this->tree->search($leftNode), + "Left Node with key 130 should still exist. Tree was not merged correctly." + ); + + $this->assertEquals(140, $removedNode->key, "The key of the removed node does not match"); + $this->assertEquals('Value 140', $removedNode->value, "The value of the removed node does not match"); + $this->assertNull( + $removedNode->left, + "The left pointer was not broken from tree correctly. Node isolation failed." + ); + $this->assertNull( + $removedNode->right, + "The right pointer was not broken from tree correctly. Node isolation failed." + ); + $this->assertNull( + $removedNode->parent, + "The parent pointer was not broken from tree correctly. Node isolation failed." + ); + } + + /** + * Test: Remove a node with two children + */ + public function testRemoveNodeWithTwoChildren(): void + { + $this->populateTree(); + + $this->assertEquals( + 2, + $this->tree->search(230)->getChildrenCount(), + "The node with key 230 has two children." + ); + + $parentNode = $this->tree->search(230)->parent->key; // 220 + $leftNode = $this->tree->search(230)->left->key; // 225 + $rightNode = $this->tree->search(230)->right->key; // 240 + + $removedNode = $this->tree->remove(230); + $this->assertNull($this->tree->search(230), "Node with key 230 should be gone"); + $this->assertNotNull( + $this->tree->search($parentNode), + "Parent Node with key 220 should still exist. Tree was not merged correctly." + ); + $this->assertNotNull( + $this->tree->search($leftNode), + "Left Node with key 225 should still exist. Tree was not merged correctly." + ); + $this->assertNotNull( + $this->tree->search($rightNode), + "Parent Node with key 240 should still exist. Tree was not merged correctly." + ); + + $this->assertEquals(230, $removedNode->key, "The key of the removed node does not match"); + $this->assertEquals('Value 230', $removedNode->value, "The value of the removed node does not match"); + $this->assertNull( + $removedNode->left, + "The left pointer was not broken from tree correctly. Node isolation failed." + ); + $this->assertNull( + $removedNode->right, + "The right pointer was not broken from tree correctly. Node isolation failed." + ); + $this->assertNull( + $removedNode->parent, + "The parent pointer was not broken from tree correctly. Node isolation failed." + ); + } + + public function testRemoveNonExistingNode(): void + { + $this->populateTree(); + $removedNode = $this->tree->remove(3333); + $this->assertNull($removedNode, "Node not found, Null should be returned."); + } + + /** + * Test: Verify all operations on a large tree. + */ + public function testOperationsOnLargeTree(): void + { + for ($i = 1; $i <= 1000; $i++) { + $this->tree->insert($i, "Value $i"); + } + + for ($i = 1; $i <= 1000; $i++) { + $this->assertEquals("Value $i", $this->tree->search($i)->value, "Value for key $i should be 'Value $i'"); + } + + for ($i = 1; $i <= 1000; $i++) { + $this->assertTrue($this->tree->isFound($this->tree->getRoot(), $i), "Node with key $i should exist"); + } + + for ($i = 1; $i <= 5; $i++) { + $this->tree->remove($i); + $this->assertFalse( + $this->tree->isFound($this->tree->getRoot(), $i), + "Value for key $i should be not exist after deletion" + ); + } + } + + /** + * Test: Check tree size + */ + public function testTreeSize(): void + { + $this->assertEquals(0, $this->tree->size()); + + $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", 28 => "Value 28", + 164 => "Value 164", 321 => "Value 321", 40 => "Value 40", 9 => "Value 9", + 32 => "Value 32", 64 => "Value 64", 116 => "Value 116" + ]; + + $bsTree = new BSTree($arrayData); + $this->assertEquals( + count($arrayData), + $bsTree->size(), + "Tree size should be size of array. Failed to insert all nodes." + ); + } + + /** + * Test depth for various nodes + */ + public function testGetDepth(): void + { + $this->populateTree(); + + $root = $this->tree->getRoot(); + + $node150 = $this->tree->search(150); + $node110 = $this->tree->search(110); + $node70 = $this->tree->search(70); + + $this->assertEquals(0, $this->tree->getdepth($root), "The root node should have a depth of 0."); + $this->assertEquals(1, $this->tree->getdepth($node150), "Node 150 should have a depth of 1."); + $this->assertEquals(4, $this->tree->getdepth($node110), "Node 110 should have a depth of 4."); + $this->assertEquals(6, $this->tree->getdepth($node70), "Node 70 should have a depth of 6."); + } + + /** + * Test height for various nodes + */ + public function testGetHeight(): void + { + $this->populateTree(); + + $root = $this->tree->getRoot(); + + $node150 = $this->tree->search(150); + $node110 = $this->tree->search(110); + $node70 = $this->tree->search(70); + $node360 = $this->tree->search(360); + + $this->assertEquals(6, $this->tree->getheight($root), "The root node should have a height of 6."); + $this->assertEquals(5, $this->tree->getheight($node150), "Node 150 should have a height of 5."); + $this->assertEquals(0, $this->tree->getheight($node70), "Node 70 should have a height of 0 (it's a leaf)."); + $this->assertEquals(0, $this->tree->getheight($node360), "Node 360 should have a height of 0 (it's a leaf)."); + $this->assertEquals(2, $this->tree->getheight($node110), "Node 110 should have a height of 2."); + } + + /** + * Test: In-order traversal + */ + public function testInOrderTraversal(): void + { + $this->assertSame( + $this->getExpectedInOrder(), + $this->tree->inOrderTraversal(), + "Did not match the expected inOrder nodes. Failed inOrder traversal." + ); + } + + private function getExpectedInOrder(): array + { + $this->populateTree(); + + return [ + 50 => 'Value 50', 70 => 'Value 70', 110 => 'Value 110', + 115 => 'Value 115', 130 => 'Value 130', 140 => 'Value 140', + 150 => 'Value 150', 160 => 'Value 160', 170 => 'Value 170', + 180 => 'Value 180', 185 => 'Value 185', 200 => 'Value 200', 215 => 'Value 215', + 220 => 'Value 220', 225 => 'Value 225', 230 => 'Value 230', 240 => 'Value 240', + 250 => 'Value 250', 300 => 'Value 300', 360 => 'Value 360' + ]; + } + + /** + * Test: Pre-order traversal + */ + public function testPreOrderTraversal(): void + { + $this->assertSame( + $this->getExpectedPreOrder(), + $this->tree->preOrderTraversal(), + "Did not match the expected preOrder nodes. Failed preOrder traversal." + ); + } + + private function getExpectedPreOrder(): array + { + $this->populateTree(); + + return [ + 200 => 'Value 200', 150 => 'Value 150', 140 => 'Value 140', + 130 => 'Value 130', 110 => 'Value 110', 50 => 'Value 50', + 70 => 'Value 70', 115 => 'Value 115', 170 => 'Value 170', + 160 => 'Value 160', 180 => 'Value 180', 185 => 'Value 185', 250 => 'Value 250', + 220 => 'Value 220', 215 => 'Value 215', 230 => 'Value 230', 225 => 'Value 225', + 240 => 'Value 240', 300 => 'Value 300', 360 => 'Value 360' + ]; + } + + /** + * Test: Post-order traversal + */ + public function testPostOrderTraversal(): void + { + $this->assertSame( + $this->getExpectedPostOrder(), + $this->tree->postOrderTraversal(), + "Did not match the expected postOrder nodes. Failed postOrder traversal." + ); + } + + private function getExpectedPostOrder(): array + { + $this->populateTree(); + + return [ + 70 => 'Value 70', 50 => 'Value 50', 115 => 'Value 115', + 110 => 'Value 110', 130 => 'Value 130', 140 => 'Value 140', + 160 => 'Value 160', 185 => 'Value 185', 180 => 'Value 180', + 170 => 'Value 170', 150 => 'Value 150', 215 => 'Value 215', 225 => 'Value 225', + 240 => 'Value 240', 230 => 'Value 230', 220 => 'Value 220', 360 => 'Value 360', + 300 => 'Value 300', 250 => 'Value 250', 200 => 'Value 200' + ]; + } + + /** + * Test: Breadth-first traversal + */ + public function testBreadthFirstTraversal(): void + { + $this->assertSame( + $this->getExpectedBFT(), + $this->tree->breadthFirstTraversal(), + "Did not match the expected breadth-first nodes. Failed BFT traversal." + ); + } + + private function getExpectedBFT(): array + { + $this->populateTree(); + + return [ + 200 => 'Value 200', 150 => 'Value 150', 250 => 'Value 250', + 140 => 'Value 140', 170 => 'Value 170', 220 => 'Value 220', + 300 => 'Value 300', 130 => 'Value 130', 160 => 'Value 160', + 180 => 'Value 180', 215 => 'Value 215', 230 => 'Value 230', 360 => 'Value 360', + 110 => 'Value 110', 185 => 'Value 185', 225 => 'Value 225', 240 => 'Value 240', + 50 => 'Value 50', 115 => 'Value 115', 70 => 'Value 70' + ]; + } + + /** + * Test: Serialize and deserialize + */ + public function testSerializationAndDeserialization(): void + { + $this->populateTree(); + + $serializedData = $this->tree->serialize(); + $deserializedTree = $this->tree->deserialize($serializedData); + + $this->assertEquals( + $this->tree->inOrderTraversal(), + $deserializedTree->inOrderTraversal(), + "Deserialized tree should match the original tree." + ); + $this->assertEquals( + $this->tree->size(), + $deserializedTree->size(), + "Deserialized tree size was not updated correctly." + ); + } + + /** + * Provides traversal types and expected results for the iterator test. + */ + public static function traversalProvider(): array + { + return [ + // Test case for In-Order traversal + 'InOrder' => [ + 'traversalType' => 'inOrder', + 'expected' => [ + 50 => 'Value 50', 70 => 'Value 70', 110 => 'Value 110', + 115 => 'Value 115', 130 => 'Value 130', 140 => 'Value 140', + 150 => 'Value 150', 160 => 'Value 160', 170 => 'Value 170', + 180 => 'Value 180', 185 => 'Value 185', 200 => 'Value 200', 215 => 'Value 215', + 220 => 'Value 220', 225 => 'Value 225', 230 => 'Value 230', 240 => 'Value 240', + 250 => 'Value 250', 300 => 'Value 300', 360 => 'Value 360', + ], + ], + // Test case for Pre-Order traversal + 'PreOrder' => [ + 'traversalType' => 'preOrder', + 'expected' => [ + 200 => 'Value 200', 150 => 'Value 150', 140 => 'Value 140', + 130 => 'Value 130', 110 => 'Value 110', 50 => 'Value 50', + 70 => 'Value 70', 115 => 'Value 115', 170 => 'Value 170', + 160 => 'Value 160', 180 => 'Value 180', 185 => 'Value 185', 250 => 'Value 250', + 220 => 'Value 220', 215 => 'Value 215', 230 => 'Value 230', 225 => 'Value 225', + 240 => 'Value 240', 300 => 'Value 300', 360 => 'Value 360', + ], + ], + // Test case for Post-Order traversal + 'PostOrder' => [ + 'traversalType' => 'postOrder', + 'expected' => [ + 70 => 'Value 70', 50 => 'Value 50', 115 => 'Value 115', + 110 => 'Value 110', 130 => 'Value 130', 140 => 'Value 140', + 160 => 'Value 160', 185 => 'Value 185', 180 => 'Value 180', + 170 => 'Value 170', 150 => 'Value 150', 215 => 'Value 215', 225 => 'Value 225', + 240 => 'Value 240', 230 => 'Value 230', 220 => 'Value 220', 360 => 'Value 360', + 300 => 'Value 300', 250 => 'Value 250', 200 => 'Value 200', + ], + ], + ]; + } + + /** + * Test: Iterating over the tree with inOrder, preOrder, and postOrder Traversals. + * + * @dataProvider traversalProvider + */ + public function testIteratorWithTraversalTypes(string $traversalType, array $expected): void + { + $this->tree->setTraversalType($traversalType); + $this->populateTree(); + + $expectedKeys = array_keys($expected); + $expectedValues = array_values($expected); + + $index = 0; + + foreach ($this->tree as $node) { + $this->assertEquals( + $expectedKeys[$index], + $node->key, + "Did not match the expected $traversalType key. Failed tree iteration." + ); + $this->assertEquals( + $expectedValues[$index], + $node->value, + "Did not match the expected $traversalType value. Failed tree iteration." + ); + $index++; + } + $this->assertEquals(count($expected), $index, "Tree iteration did not visit the expected number of nodes."); + } +} diff --git a/tests/DataStructures/CompareBinaryTreeTest.php b/tests/DataStructures/CompareBinaryTreeTest.php new file mode 100644 index 00000000..7ae5e581 --- /dev/null +++ b/tests/DataStructures/CompareBinaryTreeTest.php @@ -0,0 +1,91 @@ +assertTrue($sut->areTreesEqual($tree1, $tree2)); + } + + public function testBinaryTreesAreNotEqualWhenAreNotEqualInReality() + { + + $tree1 = new BinaryTreeNode( + 'A', + new BinaryTreeNode( + 'B', + new BinaryTreeNode( + 'F' + ), + new BinaryTreeNode( + 'E', + null, + new BinaryTreeNode( + 'D' + ) + ) + ), + new BinaryTreeNode( + 'C', + new BinaryTreeNode('G') + ) + ); + + $tree2 = new BinaryTreeNode( + 'A', + new BinaryTreeNode( + 'B', + new BinaryTreeNode( + 'F' + ), + new BinaryTreeNode( + 'E', + null, + new BinaryTreeNode( + 'D' + ) + ) + ), + new BinaryTreeNode( + 'C' + ) + ); + + $sut = new CompareBinaryTree(); + $this->assertFalse($sut->areTreesEqual($tree1, $tree2)); + } +} diff --git a/tests/DataStructures/DisjointSetTest.php b/tests/DataStructures/DisjointSetTest.php new file mode 100644 index 00000000..c7cd9a66 --- /dev/null +++ b/tests/DataStructures/DisjointSetTest.php @@ -0,0 +1,95 @@ +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; + } +} diff --git a/tests/DataStructures/DoublyLinkedListTest.php b/tests/DataStructures/DoublyLinkedListTest.php new file mode 100644 index 00000000..a3a62072 --- /dev/null +++ b/tests/DataStructures/DoublyLinkedListTest.php @@ -0,0 +1,171 @@ +assertEquals(0, $list->length()); + } + + /** + * Test for the append method + */ + public function testAppend() + { + $list = new DoublyLinkedList(); + $list->append(1); + $list->append(2); + $list->append(3); + $this->assertEquals(3, $list->length()); + } + + /** + * Test for the insert method + */ + public function testInsert() + { + $list = new DoublyLinkedList(); + $list->append(1); + $list->append(2); + $list->append(3); + $list->insert(1, 4); + $this->assertEquals(4, $list->length()); + } + + /** + * Test for the delete method + */ + public function testDelete() + { + $list = new DoublyLinkedList(); + $list->append(1); + $list->append(2); + $list->append(3); + $list->delete(1); + $this->assertEquals(2, $list->length()); + } + + /** + * Test for the deleteAt method + */ + public function testDeleteAt() + { + $list = new DoublyLinkedList(); + $list->append(1); + $list->append(2); + $list->append(3); + $list->deleteAt(1); + $this->assertEquals(2, $list->length()); + } + + /** + * Test for printList method + */ + public function testPrintList() + { + $list = new DoublyLinkedList(); + $list->append(1); + $list->append(2); + $list->append(3); + $this->expectOutputString("1\n2\n3\n"); + $list->printList(); + } + + /** + * Test for the printListReverse method + */ + public function testPrintListReverse() + { + $list = new DoublyLinkedList(); + $list->append(1); + $list->append(2); + $list->append(3); + $this->expectOutputString("3\n2\n1\n"); + $list->printListReverse(); + } + + /** + * Test for the reverse method + */ + public function testReverse() + { + $list = new DoublyLinkedList(); + $list->append(1); + $list->append(2); + $list->append(3); + $list->reverse(); + $this->expectOutputString("3\n2\n1\n"); + $list->printList(); + } + + /** + * Test for the length method + */ + public function testLength() + { + $list = new DoublyLinkedList(); + $list->append(1); + $list->append(2); + $list->append(3); + $this->assertEquals(3, $list->length()); + } + /** + * Test for the Search method + */ + public function testSearch() + { + $list = new DoublyLinkedList(); + $list->append(1); + $list->append(2); + $list->append(3); + $searchItem = $list->search(2); + $this->assertEquals(2, $searchItem->data); + } + + /** + * Test for the isEmpty method + */ + public function testIsEmpty() + { + $list = new DoublyLinkedList(); + $this->assertEquals(true, $list->isEmpty()); + } + + /** + * Test for __toString method + */ + public function testToString() + { + $list = new DoublyLinkedList(); + $list->append(1); + $list->append(2); + $list->append(3); + $this->expectOutputString("1, 2, 3"); + echo $list; + } + + /** + * Test for the toArray method + */ + public function testToArray() + { + $list = new DoublyLinkedList(); + $list->append(1); + $list->append(2); + $list->append(3); + $this->assertEquals([1,2,3], $list->toArray()); + } +} diff --git a/tests/DataStructures/InvertBinaryTreeTest.php b/tests/DataStructures/InvertBinaryTreeTest.php new file mode 100644 index 00000000..ce73bb27 --- /dev/null +++ b/tests/DataStructures/InvertBinaryTreeTest.php @@ -0,0 +1,37 @@ +setValue(1); + $bl = (new BinaryTree())->setValue(3); + $b->setLeft($bl); + $br = (new BinaryTree())->setValue(2); + $b->setRight($br); + $br->setLeft((new BinaryTree())->setValue(4)); + $br->setRight((new BinaryTree())->setValue(5)); + + $expected = (new BinaryTree())->setValue(1); + $expectedBr = (new BinaryTree())->setValue(3); + $expected->setRight($expectedBr); + $expectedBl = (new BinaryTree())->setValue(2); + $expected->setLeft($expectedBl); + $expectedBl->setRight((new BinaryTree())->setValue(4)); + $expectedBl->setLeft((new BinaryTree())->setValue(5)); + + (new InvertBinaryTree())->invert($b); + + $this->assertEquals($expected, $b); + } +} diff --git a/tests/DataStructures/QueueTest.php b/tests/DataStructures/QueueTest.php new file mode 100644 index 00000000..e77d568b --- /dev/null +++ b/tests/DataStructures/QueueTest.php @@ -0,0 +1,153 @@ +enqueue(1); + $queue->enqueue(2); + $queue->enqueue(3); + + $this->assertEquals(3, $queue->size()); + $this->assertEquals(1, $queue->peek()); + } + + /** + * @test + */ + public function shouldRemoveElementFromQueue(): void + { + $queue = new Queue(); + $queue->enqueue(1); + $queue->enqueue(2); + $queue->enqueue(3); + + $this->assertEquals(1, $queue->dequeue()); + $this->assertEquals(2, $queue->peek()); + $this->assertEquals(2, $queue->size()); + } + + /** + * @test + */ + public function shouldReturnCorrectValueWhenDequeue(): void + { + $queue = new Queue(); + $queue->enqueue(1); + $queue->enqueue(2); + $queue->enqueue(3); + + $this->assertEquals(1, $queue->dequeue()); + $this->assertEquals(2, $queue->dequeue()); + $this->assertEquals(3, $queue->dequeue()); + } + + /** + * @test + */ + public function shouldReturnNullWhenDequeueEmptyQueue(): void + { + $queue = new Queue(); + + $this->assertNull($queue->dequeue()); + } + + /** + * @test + */ + public function shouldReturnTrueIfQueueIsEmpty(): void + { + $queue = new Queue(); + + $this->assertTrue($queue->isEmpty()); + } + + /** + * @test + */ + public function shouldReturnFalseIfQueueIsNotEmpty(): void + { + $queue = new Queue(); + $queue->enqueue(1); + + $this->assertFalse($queue->isEmpty()); + } + + /** + * @test + */ + public function shouldReturnQueueSize(): void + { + $queue = new Queue(); + $queue->enqueue(1); + $queue->enqueue(2); + $queue->enqueue(3); + + $this->assertEquals(3, $queue->size()); + $this->assertEquals(1, $queue->peek()); + } + + /** + * @test + */ + public function shouldReturnFrontElement(): void + { + $queue = new Queue(); + $queue->enqueue(1); + $queue->enqueue(2); + $queue->enqueue(3); + + $this->assertEquals(1, $queue->peek()); + $this->assertEquals(3, $queue->size()); + } + + /** + * @test + */ + public function shouldReturnNullWhenPeekEmptyQueue(): void + { + $queue = new Queue(); + + $this->assertNull($queue->peek()); + } + + /** + * @test + */ + public function shouldClearQueue(): void + { + $queue = new Queue(); + $queue->enqueue(1); + $queue->enqueue(2); + $queue->enqueue(3); + + $queue->clear(); + + $this->assertTrue($queue->isEmpty()); + $this->assertEquals(0, $queue->size()); + $this->assertNull($queue->peek()); + } + + /** + * @test + */ + public function shouldReturnStringRepresentation(): void + { + $queue = new Queue(); + $queue->enqueue(1); + $queue->enqueue(2); + $queue->enqueue(3); + + $this->assertIsString($queue->toString()); + $this->assertEquals("1, 2, 3", $queue->toString(', ')); + } +} diff --git a/tests/DataStructures/ReverseLinkedListTest.php b/tests/DataStructures/ReverseLinkedListTest.php new file mode 100644 index 00000000..667bd45a --- /dev/null +++ b/tests/DataStructures/ReverseLinkedListTest.php @@ -0,0 +1,37 @@ +setValue(0); + + $prevItem = $firstItem; + + foreach ($list as $value) { + $item = new LinkedListItem(); + $item->setValue($value); + $item->setPrev($prevItem); + $prevItem->setNext($item); + $prevItem = $item; + } + + $newFirstItem = (new ReverseLinkedList())->reverse($firstItem); + do { + $this->assertEquals($newFirstItem->getValue(), array_pop($list)); + } while ($newFirstItem = $newFirstItem->getNext()); + } +} diff --git a/tests/DataStructures/SegmentTreeTest.php b/tests/DataStructures/SegmentTreeTest.php new file mode 100644 index 00000000..ebe5a7e6 --- /dev/null +++ b/tests/DataStructures/SegmentTreeTest.php @@ -0,0 +1,347 @@ +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 + } +} diff --git a/tests/DataStructures/SplayTreeTest.php b/tests/DataStructures/SplayTreeTest.php new file mode 100644 index 00000000..82348207 --- /dev/null +++ b/tests/DataStructures/SplayTreeTest.php @@ -0,0 +1,649 @@ +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 which 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 which 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 which 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(); + + $nodesNumber = $this->tree->size(); + $node = $this->tree->delete(22); + + $isFound = $this->tree->isFound(22); + $this->assertFalse($isFound, "Node with key 22 was not deleted."); + + $this->assertEquals( + $nodesNumber - 1, + $this->tree->size(), + "After deletion, total nodes count was not updated correctly." + ); + + $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.' + ); + } + + /** + * Tests deletion of multiple nodes and checks if the tree size is updated. + */ + public function testDeleteMultipleKeys() + { + $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", 28 => "Value 28", + 164 => "Value 164", 321 => "Value 321", 40 => "Value 40" + ]; + + $splayTree = new SplayTree($arrayData); + $treeSize = $splayTree->size(); + + $nodesToDelete = [150, 300, 50, 240, 170]; + $expectedSize = $treeSize - count($nodesToDelete); + + foreach ($nodesToDelete as $key) { + $splayTree->delete($key); + $isFound = $this->tree->isFound($key); + $this->assertFalse($isFound, "Node with key $key was not deleted."); + } + + $this->assertEquals( + $expectedSize, + $splayTree->size(), + "After deletion, total nodes count was not updated correctly." + ); + } + + /** + * 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 insert, search, delete on large trees + */ + 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, "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->assertFalse($this->tree->isFound($i), "Node was not deleted correctly"); + } + } + + + // ------------- 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'); + } +} diff --git a/tests/DataStructures/StackTest.php b/tests/DataStructures/StackTest.php new file mode 100644 index 00000000..7f1c44be --- /dev/null +++ b/tests/DataStructures/StackTest.php @@ -0,0 +1,110 @@ +assertEquals([1, 2, 3], $stack->toArray()); + } + + public function testDestruct() + { + $stack = new Stack([1, 2, 3]); + unset($stack); + $this->expectNotToPerformAssertions(); + } + + public function testPush() + { + $stack = new Stack(); + $stack->push(1); + $stack->push(2); + $stack->push(3); + $this->assertEquals([1, 2, 3], $stack->toArray()); + } + + public function testPop() + { + $stack = new Stack([1, 2, 3]); + $this->assertEquals(3, $stack->pop()); + $this->assertEquals([1, 2], $stack->toArray()); + } + + public function testPeek() + { + $stack = new Stack([1, 2, 3]); + $this->assertEquals(3, $stack->peek()); + } + + public function testIsEmpty() + { + $stack = new Stack(); + $this->assertTrue($stack->isEmpty()); + } + + public function testPrint() + { + $stack = new Stack([1, 2, 3]); + $this->expectOutputString("1, 2, 3"); + $stack->print(); + } + + public function testToString() + { + $stack = new Stack([1, 2, 3]); + $this->expectOutputString("1, 2, 3"); + echo $stack; + } + + public function testLength() + { + $stack = new Stack([1, 2, 3]); + $this->assertEquals(3, $stack->length()); + } + + public function testClear() + { + $stack = new Stack([1, 2, 3]); + $stack->clear(); + $this->assertEquals([], $stack->toArray()); + } + + public function testSearch() + { + $stack = new Stack([1, 2, 3]); + $this->assertEquals(2, $stack->search(3)); + } + + public function testToArray() + { + $stack = new Stack([1, 2, 3]); + $this->assertEquals([1, 2, 3], $stack->toArray()); + } + + public function testFromArray() + { + $stack = new Stack(); + $stack->fromArray([1, 2, 3]); + $this->assertEquals([1, 2, 3], $stack->toArray()); + } + + public function testReverse() + { + $stack = new Stack([1, 2, 3]); + $stack->reverse(); + $this->assertEquals([3, 2, 1], $stack->toArray()); + } + + public function testSort() + { + $stack = new Stack([3, 2, 1]); + $stack->sort(); + $this->assertEquals([1, 2, 3], $stack->toArray()); + } +} diff --git a/tests/DataStructures/TrieTest.php b/tests/DataStructures/TrieTest.php new file mode 100644 index 00000000..a8ab2cdc --- /dev/null +++ b/tests/DataStructures/TrieTest.php @@ -0,0 +1,302 @@ +trie = new Trie(); + } + + /** + * Test insertion and search functionality of the 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.' + ); + } + + /** + * Test insertion and search functionality with mixed case words. + */ + public function testInsertAndSearchMixedCase() + { + $this->trie->insert('Apple'); + $this->trie->insert('aPPle'); + $this->assertTrue($this->trie->search('apple'), 'Expected "apple" to be found in the Trie.'); + $this->assertTrue($this->trie->search('APPLE'), 'Expected "APPLE" to be found in the Trie.'); + } + + /** + * Test insertion and search functionality with special characters. + */ + public function testInsertAndSearchWithSpecialCharacters() + { + $this->trie->insert('hello123'); + $this->trie->insert('user@domain.com'); + $this->assertTrue($this->trie->search('hello123'), 'Expected "hello123" to be found in the Trie.'); + $this->assertTrue( + $this->trie->search('UseR@domain.CoM'), + 'Expected "user@domain.com" to be found in the Trie.' + ); + $this->assertTrue( + $this->trie->search('HELLO123'), + 'Expected "HELLO123" not to be found in the Trie (case-sensitive).' + ); + } + + /** + * Test insertion and search functionality with long strings. + */ + public function testInsertAndSearchLongStrings() + { + $longString = str_repeat('a', 1000); + $this->trie->insert($longString); + $this->assertTrue($this->trie->search($longString), 'Expected the long string to be found in the Trie.'); + } + + /** + * Test the startsWith functionality of 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.' + ); + } + + /** + * Test startsWith functionality with mixed case prefixes. + */ + public function testStartsWithMixedCase() + { + $this->trie->insert('PrefixMatch'); + $this->trie->insert('PreFixTesting'); + $this->assertEquals( + ['prefixmatch', 'prefixtesting'], + $this->trie->startsWith('prefix'), + 'Expected words starting with "prefix" to be found in the Trie (case-insensitive).' + ); + + $this->assertEquals( + ['prefixmatch', 'prefixtesting'], + $this->trie->startsWith('PREFIX'), + 'Expected words starting with "PREFIX" to be found in the Trie (case-insensitive).' + ); + } + + /** + * Test deletion of existing words from the Trie. + */ + public function testDelete() + { + $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.'); + } + + /** + * Test deletion of mixed case words from the Trie. + */ + public function testDeleteMixedCase() + { + $this->trie->insert('MixedCase'); + $this->assertTrue($this->trie->search('mixedcase'), 'Expected "mixedcase" to be found before deletion.'); + + $this->trie->delete('MIXEDCASE'); + $this->assertFalse( + $this->trie->search('MixedCase'), + 'Expected "MixedCase" not to be found after deletion (case-insensitive).' + ); + } + + /** + * Test deletion of words with special characters. + */ + public function testDeleteWithSpecialCharacters() + { + $this->trie->insert('spec!@l#chars'); + $this->assertTrue( + $this->trie->search('spec!@l#chars'), + 'Expected "spec!@l#chars" to be found before deletion.' + ); + + $this->trie->delete('SPEC!@L#CHARS'); + $this->assertFalse( + $this->trie->search('spec!@l#chars'), + 'Expected "spec!@l#chars" not to be found after deletion.' + ); + } + + /** + * Test deletion of a non-existent word from the Trie. + */ + public function testDeleteNonExistentWord() + { + $this->trie->delete('nonexistent'); + $this->assertFalse($this->trie->search('nonexistent'), 'Expected "nonexistent" to not be found.'); + } + + /** + * Test traversal of the Trie and retrieval of words. + */ + 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.'); + } + + /** + * Test behavior of an empty Trie. + */ + public function testEmptyTrie() + { + $this->assertEquals([], $this->trie->getWords(), 'Expected an empty Trie to return an empty array.'); + } + + /** + * Test retrieval of words from the Trie. + */ + 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.'); + } + + /** + * Test insertion of an empty string into the Trie. + */ + public function testInsertEmptyString() + { + $this->trie->insert(''); + $this->assertTrue($this->trie->search(''), 'Expected empty string to be found in the Trie.'); + } + + /** + * Test deletion of an empty string from 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.'); + } + + /** + * Test the startsWith functionality with a common prefix. + */ + 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".'); + } + + /** + * Test retrieval of the root node of the Trie. + */ + public function testGetRoot() + { + $root = $this->trie->getRoot(); + $this->assertInstanceOf(TrieNode::class, $root, 'Expected root to be an instance of TrieNode.'); + $this->assertFalse($root->isEndOfWord, 'Expected the root node not to be the end of a word.'); + $this->assertCount(0, $root->children, 'Expected the root node to have no children initially.'); + } + + /** + * Test retrieval of the root node after populating the Trie with words. + */ + public function testGetRootAfterPopulation() + { + $this->trie->insert('TheAlgorithms'); + $this->trie->insert('PHP'); + $this->trie->insert('DSA'); + + $root = $this->trie->getRoot(); + + $this->assertInstanceOf(TrieNode::class, $root, 'Expected root to be an instance of TrieNode.'); + + // Assert that the root node is not marked as the end of a word + $this->assertFalse($root->isEndOfWord, 'Expected the root node not to be the end of a word.'); + + // Assert that the root node has children corresponding to the inserted words + $this->assertCount(3, $root->children, 'Expected the root node to have 3 children after inserting words.'); + $this->assertTrue($root->hasChild('t'), 'Expected root to have a child for "t".'); + $this->assertTrue($root->hasChild('p'), 'Expected root to have a child for "p".'); + $this->assertTrue($root->hasChild('D'), 'Expected root to have a child for "D".'); + } +} diff --git a/tests/Graphs/BellmanFordTest.php b/tests/Graphs/BellmanFordTest.php new file mode 100644 index 00000000..a743f820 --- /dev/null +++ b/tests/Graphs/BellmanFordTest.php @@ -0,0 +1,52 @@ +start = $edgeRaw[0]; + $edge->end = $edgeRaw[2]; + $edge->weight = $edgeRaw[1]; + if (!isset($edges[$edgeRaw[0]])) { + $edges[$edgeRaw[0]] = []; + } + $edges[$edgeRaw[0]][] = $edge; + } + + $result = bellmanFord($vertices, $edges, 'S'); + + $this->assertEquals( + [ + 'S' => 0, + 'A' => 5, + 'B' => 5, + 'C' => 7, + 'D' => 9, + 'E' => 8 + ], + $result + ); + } +} diff --git a/tests/Graphs/BreadthFirstSearchTest.php b/tests/Graphs/BreadthFirstSearchTest.php index 7f5fd181..ec68c909 100644 --- a/tests/Graphs/BreadthFirstSearchTest.php +++ b/tests/Graphs/BreadthFirstSearchTest.php @@ -46,4 +46,3 @@ public function testBreadthFirstSearch3() $this->assertTrue($ans); } } - diff --git a/tests/Graphs/DijkstrasTest.php b/tests/Graphs/DijkstrasTest.php new file mode 100644 index 00000000..f00ce0b7 --- /dev/null +++ b/tests/Graphs/DijkstrasTest.php @@ -0,0 +1,52 @@ +start = $edgeRaw[0]; + $edge->end = $edgeRaw[2]; + $edge->weight = $edgeRaw[1]; + $edges[] = $edge; + } + + $result = dijkstras($vertices, $edges, 'S'); + + $this->assertEquals( + [ + 'S' => 0, + 'A' => 5, + 'B' => 5, + 'C' => 7, + 'D' => 9, + 'E' => 8 + ], + $result + ); + } +} diff --git a/tests/Maths/EratosthenesSieveTest.php b/tests/Maths/EratosthenesSieveTest.php new file mode 100644 index 00000000..065ab638 --- /dev/null +++ b/tests/Maths/EratosthenesSieveTest.php @@ -0,0 +1,16 @@ +assertEquals($result, [1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29]); + } +} diff --git a/tests/Maths/MathsTest.php b/tests/Maths/MathsTest.php index f4848725..a9428b7f 100644 --- a/tests/Maths/MathsTest.php +++ b/tests/Maths/MathsTest.php @@ -1,148 +1,210 @@ assertEquals(11, baseX(3, 2)); + $this->assertEquals(22, baseX(8, 3)); + $this->assertEquals(21, baseX(15, 7)); + $this->assertEquals(20, baseX(16, 8)); + } + public function testFactorial() { - assertEquals(1, factorial(1)); - assertEquals(120, factorial(5)); - assertEquals(1, factorial(0)); + $this->assertEquals(1, factorial(1)); + $this->assertEquals(120, factorial(5)); + $this->assertEquals(1, factorial(0)); $this->expectException(\Exception::class); factorial(-25); } + public function testIsEven() + { + $this->assertTrue(isEven(2)); + $this->assertTrue(isEven(0)); + $this->assertFalse(isEven(3)); + $this->assertFalse(isEven(17)); + $this->assertTrue(isEven(-4)); + } + public function testIsNumberArmstrong() { - assertTrue(isNumberArmstrong(153)); - assertFalse(isNumberArmstrong(123)); - assertTrue(isNumberArmstrong(370)); - assertFalse(isNumberArmstrong(2468)); + $this->assertTrue(isNumberArmstrong(153)); + $this->assertFalse(isNumberArmstrong(123)); + $this->assertTrue(isNumberArmstrong(370)); + $this->assertFalse(isNumberArmstrong(2468)); } public function testIsNumberPalindromic() { - assertTrue(isNumberPalindromic(121)); - assertFalse(isNumberPalindromic(123)); - assertTrue(isNumberPalindromic(123321)); - assertFalse(isNumberPalindromic(2468)); + $this->assertTrue(isNumberPalindromic(121)); + $this->assertFalse(isNumberPalindromic(123)); + $this->assertTrue(isNumberPalindromic(123321)); + $this->assertFalse(isNumberPalindromic(2468)); + } + + public function testIsOdd() + { + $this->assertTrue(isOdd(3)); + $this->assertTrue(isOdd(17)); + $this->assertFalse(isOdd(4)); + $this->assertFalse(isOdd(0)); + $this->assertTrue(isOdd(-5)); } public function testIsPrime() { - assertTrue(isPrime(73)); - assertFalse(isPrime(21)); - assertFalse(isPrime(1)); - assertTrue(isPrime(997)); + $this->assertTrue(isPrime(73)); + $this->assertFalse(isPrime(21)); + $this->assertFalse(isPrime(1)); + $this->assertTrue(isPrime(997)); } public function testAbsoluteMax() { - assertEquals(50, absolute_max(12, 20, 35, 50, 50, 23)); - assertEquals(13, absolute_max(13)); - assertEquals(54, absolute_max(12, 13, 54, 22)); + $this->assertEquals(50, absolute_max(12, 20, 35, 50, 50, 23)); + $this->assertEquals(13, absolute_max(13)); + $this->assertEquals(54, absolute_max(12, 13, 54, 22)); $this->expectException(\Exception::class); absolute_max(); } public function testAbsoluteMin() { - assertEquals(12, absolute_min(12, 20, 35, 50, 50, 23)); - assertEquals(13, absolute_min(13)); - assertEquals(12, absolute_min(12, 13, 54, 22)); + $this->assertEquals(12, absolute_min(12, 20, 35, 50, 50, 23)); + $this->assertEquals(13, absolute_min(13)); + $this->assertEquals(12, absolute_min(12, 13, 54, 22)); $this->expectException(\Exception::class); absolute_min(); } public function testPerfectSquare() { - assertTrue(isPerfectSquare(25)); - assertFalse(isPerfectSquare(43)); - assertFalse(isPerfectSquare(2)); - assertTrue(isPerfectSquare(225)); + $this->assertTrue(isPerfectSquare(25)); + $this->assertFalse(isPerfectSquare(43)); + $this->assertFalse(isPerfectSquare(2)); + $this->assertTrue(isPerfectSquare(225)); } public function testFastExponentiation() { - assertEquals(fastExponentiation(10, 0), 1); - assertEquals(fastExponentiation(10, 1), 10); - assertEquals(fastExponentiation(10, 2), 100); - assertEquals(fastExponentiation(10, 3), 1000); - assertEquals(fastExponentiation(20, 5), 3200000); + $this->assertEquals(1, fastExponentiation(10, 0)); + $this->assertEquals(10, fastExponentiation(10, 1)); + $this->assertEquals(100, fastExponentiation(10, 2)); + $this->assertEquals(1000, fastExponentiation(10, 3)); + $this->assertEquals(3200000, fastExponentiation(20, 5)); } public function testFibonacciSeries() { - assertEquals([0, 1, 1, 2, 3], fibonacciRecursive(5)); - assertEquals([0, 1, 1, 2, 3, 5, 8, 13, 21, 34], fibonacciRecursive(10)); + $this->assertEquals([0, 1, 1, 2, 3], fibonacciRecursive(5)); + $this->assertEquals([0, 1, 1, 2, 3, 5, 8, 13, 21, 34], fibonacciRecursive(10)); - assertEquals([0, 1, 1, 2, 3], fibonacciWithBinetFormula(5)); - assertEquals([0, 1, 1, 2, 3, 5, 8, 13, 21, 34], fibonacciWithBinetFormula(10)); + $this->assertEquals([0, 1, 1, 2, 3], fibonacciWithBinetFormula(5)); + $this->assertEquals([0, 1, 1, 2, 3, 5, 8, 13, 21, 34], fibonacciWithBinetFormula(10)); } public function testNeonNumber() { - assertTrue(isNumberNeon(1)); - assertFalse(isNumberNeon(43)); - assertFalse(isNumberNeon(123)); - assertTrue(isNumberNeon(9)); + $this->assertTrue(isNumberNeon(1)); + $this->assertFalse(isNumberNeon(43)); + $this->assertFalse(isNumberNeon(123)); + $this->assertTrue(isNumberNeon(9)); } - + public function testFibonacciGenerator() { - assertEquals([0, 1, 1, 2, 3], iterator_to_array(loop(5, fib()))); - assertEquals([0, 1, 1, 2, 3, 5, 8, 13, 21, 34], iterator_to_array(loop(10, fib()))); + $this->assertEquals([0, 1, 1, 2, 3], iterator_to_array(loop(5, fib()))); + $this->assertEquals([0, 1, 1, 2, 3, 5, 8, 13, 21, 34], iterator_to_array(loop(10, fib()))); - assertEquals([0, 1, 1, 2, 3], iterator_to_array(loop(5, fib()))); - assertEquals([0, 1, 1, 2, 3, 5, 8, 13, 21, 34], iterator_to_array(loop(10, fib()))); + $this->assertEquals([0, 1, 1, 2, 3], iterator_to_array(loop(5, fib()))); + $this->assertEquals([0, 1, 1, 2, 3, 5, 8, 13, 21, 34], iterator_to_array(loop(10, fib()))); } public function testMean() { - assertEquals( - (2 + 4 + 6 + 8 + 20 + 50 + 70) / 7, + $this->assertEquals( + (2 + 4 + 6 + 8 + 20 + 50 + 70) / 7, mean(2, 4, 6, 8, 20, 50, 70) ); - assertEquals( - (-5 - 7 + 10) / 3, + $this->assertEquals( + (-5 - 7 + 10) / 3, mean(-5, -7, 10) ); - - assertEquals(-1, mean(-1)); + + $this->assertEquals(-1, mean(-1)); } public function testMedian() { - assertEquals(3, median(1, 2, 3, 4, 5)); - assertEquals(4.5, median(1, 2, 3, 4, 5, 6, 7, 8)); - assertEquals(3, median(5, 3, 1, 2, 4)); + $this->assertEquals(3, median(1, 2, 3, 4, 5)); + $this->assertEquals(4.5, median(1, 2, 3, 4, 5, 6, 7, 8)); + $this->assertEquals(3, median(5, 3, 1, 2, 4)); } public function testMode() { - mode(4,2,7,9,2,9,0,4,2); - mode(21,72,40,21,0,99,21,99,0,40); - mode(3,3,3,3); + $this->assertEquals([3], mode(1, 2, 3, 3, 4, 5)); + $this->assertEquals([5, 6], mode(5, 5, 6, 6, 7)); + $this->assertEquals([1, 2, 3, 4, 5], mode(1, 2, 3, 4, 5)); + $this->assertEquals([2, 3, 4], mode(2, 2, 3, 3, 4, 4)); + } + + public function testGreatestCommonDivisor() + { + $this->assertEquals(8, gcd(24, 16)); + $this->assertEquals(5, gcd(10, 5)); + $this->assertEquals(25, gcd(100, 75)); + $this->assertEquals(6, gcd(12, 18)); + $this->assertEquals(5, gcd(10, 15)); + $this->assertEquals(3, gcd(9, 12)); + } + + public function testPerfectNumber() + { + $this->assertTrue(perfect_number(6)); + $this->assertTrue(perfect_number(28)); + $this->assertTrue(perfect_number(496)); + + $this->assertFalse(perfect_number(10)); + $this->assertFalse(perfect_number(15)); + + $this->assertFalse(perfect_number(-6)); + $this->assertFalse(perfect_number(0)); + $this->assertFalse(perfect_number(1)); + $this->assertFalse(perfect_number(2.5)); + $this->assertFalse(perfect_number("string")); + } + + public function testFastInverseSquareRoot() + { + $this->assertEqualsWithDelta(0.31568579235273, fastInvSqrt(10), 0.00001); + $this->assertEqualsWithDelta(0.49915357479239, fastInvSqrt(4), 0.00001); } } diff --git a/tests/Maths/ProjectEulerTest.php b/tests/Maths/ProjectEulerTest.php index 3b74f3ed..43b1da47 100644 --- a/tests/Maths/ProjectEulerTest.php +++ b/tests/Maths/ProjectEulerTest.php @@ -62,7 +62,7 @@ public function testProblem9(): void { $this->assertSame(31875000, problem9()); } - + public function testProblem10(): void { $this->assertSame(142913828922, problem10()); diff --git a/tests/NeuralNetworks/PerceptronClassifier/NeuralNetworkPerceptronClassifierTest.php b/tests/NeuralNetworks/PerceptronClassifier/NeuralNetworkPerceptronClassifierTest.php new file mode 100644 index 00000000..61eacc48 --- /dev/null +++ b/tests/NeuralNetworks/PerceptronClassifier/NeuralNetworkPerceptronClassifierTest.php @@ -0,0 +1,23 @@ +generateTrainingSet(); + // Train the model + [$W, $b] = $nnClassifier->trainModel($X, $Y, 1000, 0.1); + + // Make predictions + $predictions = $nnClassifier->predict([[0, 0, 1, 1], [0, 1, 1, 0]], $W, $b); + $this->assertEquals([0, 0, 0, 1], $predictions); + } +} diff --git a/tests/Searches/SearchesTest.php b/tests/Searches/SearchesTest.php index 08d2f716..c1f3890b 100644 --- a/tests/Searches/SearchesTest.php +++ b/tests/Searches/SearchesTest.php @@ -1,8 +1,5 @@ assertEquals(0, $result); $target = 15; $result = binarySearchIterative($list, $target); - assertEquals(4, $result); + $this->assertEquals(4, $result); $target = 5; $result = binarySearchIterative($list, $target); - assertEquals(1, $result); + $this->assertEquals(1, $result); $target = 6; $result = binarySearchIterative($list, $target); - assertEquals(null, $result); + $this->assertEquals(null, $result); } public function testBinarySearchByRecursion() @@ -41,16 +40,16 @@ public function testBinarySearchByRecursion() $list = [0, 5, 7, 10, 15]; $target = 0; $result = binarySearchByRecursion($list, $target, 0, 4); - assertEquals(0, $result); + $this->assertEquals(0, $result); $target = 15; $result = binarySearchByRecursion($list, $target, 0, 4); - assertEquals(4, $result); + $this->assertEquals(4, $result); $target = 5; $result = binarySearchByRecursion($list, $target, 0, 4); - assertEquals(1, $result); + $this->assertEquals(1, $result); $target = 6; $result = binarySearchByRecursion($list, $target, 0, 4); - assertEquals(null, $result); + $this->assertEquals(null, $result); } public function testBinarySearchByRecursionWithEmptyList() @@ -58,7 +57,7 @@ public function testBinarySearchByRecursionWithEmptyList() $list = []; $target = 0; $result = binarySearchByRecursion($list, $target, 0, 0); - assertEquals(null, $result); + $this->assertEquals(null, $result); } public function testBinarySearchByRecursionWithOneElementList() @@ -66,10 +65,10 @@ public function testBinarySearchByRecursionWithOneElementList() $list = [0]; $target = 0; $result = binarySearchByRecursion($list, $target, 0, 0); - assertEquals(0, $result); + $this->assertEquals(0, $result); $target = 1; $result = binarySearchByRecursion($list, $target, 0, 0); - assertEquals(null, $result); + $this->assertEquals(null, $result); } public function testBinarySearchByRecursionWithTwoElementList() @@ -77,13 +76,13 @@ public function testBinarySearchByRecursionWithTwoElementList() $list = [0, 1]; $target = 0; $result = binarySearchByRecursion($list, $target, 0, 1); - assertEquals(0, $result); + $this->assertEquals(0, $result); $target = 1; $result = binarySearchByRecursion($list, $target, 0, 1); - assertEquals(1, $result); + $this->assertEquals(1, $result); $target = 2; $result = binarySearchByRecursion($list, $target, 0, 1); - assertEquals(null, $result); + $this->assertEquals(null, $result); } public function testBinarySearchByRecursionWithThreeElementList() @@ -91,28 +90,28 @@ public function testBinarySearchByRecursionWithThreeElementList() $list = [0, 1, 2]; $target = 0; $result = binarySearchByRecursion($list, $target, 0, 2); - assertEquals(0, $result); + $this->assertEquals(0, $result); $target = 1; $result = binarySearchByRecursion($list, $target, 0, 2); - assertEquals(1, $result); + $this->assertEquals(1, $result); $target = 2; $result = binarySearchByRecursion($list, $target, 0, 2); - assertEquals(2, $result); + $this->assertEquals(2, $result); $target = 3; $result = binarySearchByRecursion($list, $target, 0, 2); - assertEquals(null, $result); + $this->assertEquals(null, $result); } public function testFibonacciSearch() { $test1 = fibonacciPosition(6); - assertEquals(8, $test1); + $this->assertEquals(8, $test1); $test2 = fibonacciPosition(9); - assertEquals(34, $test2); + $this->assertEquals(34, $test2); $test3 = fibonacciPosition(60); - assertEquals(1548008755920, $test3); + $this->assertEquals(1548008755920, $test3); } public function testLinearSearch() @@ -120,7 +119,7 @@ public function testLinearSearch() $list = [5, 7, 8, 11, 12, 15, 17, 18, 20]; $target = 15; $result = linearSearch($list, $target); - assertEquals(6, $result); + $this->assertEquals(6, $result); } public function testLowerBound() @@ -128,7 +127,7 @@ public function testLowerBound() $list = [1, 2, 3, 3, 3, 4, 5, 9]; $target = 3; $result = lowerBound($list, $target); - assertEquals(2, $result); + $this->assertEquals(2, $result); } public function testUpperBound() @@ -136,15 +135,15 @@ public function testUpperBound() $list = [1, 2, 3, 3, 3, 4, 5, 9]; $target = 3; $result = upperBound($list, $target); - assertEquals(5, $result); + $this->assertEquals(5, $result); } - + public function testJumpSearch() { $list = array( 3,5,6,7,9,10,12,20,22,24); $target = 12; $result = jumpSearch($list, $target); - assertEquals(6, $result); + $this->assertEquals(6, $result); } public function testExponentialSearch() @@ -152,7 +151,7 @@ public function testExponentialSearch() $list = array(2,3,4,7,28,35,63,98); $target = 35; $result = exponentialSearch($list, $target); - assertEquals(5, $result); + $this->assertEquals(5, $result); } public function testTernarySearchIterative() @@ -160,16 +159,16 @@ public function testTernarySearchIterative() $list = [0, 5, 7, 10, 15]; $target = 0; $result = ternarySearchIterative($list, $target); - assertEquals(0, $result); + $this->assertEquals(0, $result); $target = 15; $result = ternarySearchIterative($list, $target); - assertEquals(4, $result); + $this->assertEquals(4, $result); $target = 5; $result = ternarySearchIterative($list, $target); - assertEquals(1, $result); + $this->assertEquals(1, $result); $target = 6; $result = ternarySearchIterative($list, $target); - assertEquals(null, $result); + $this->assertEquals(null, $result); } public function testTernarySearchByRecursion() @@ -177,16 +176,16 @@ public function testTernarySearchByRecursion() $list = [0, 5, 7, 10, 15]; $target = 0; $result = ternarySearchByRecursion($list, $target, 0, 4); - assertEquals(0, $result); + $this->assertEquals(0, $result); $target = 15; $result = ternarySearchByRecursion($list, $target, 0, 4); - assertEquals(4, $result); + $this->assertEquals(4, $result); $target = 5; $result = ternarySearchByRecursion($list, $target, 0, 4); - assertEquals(1, $result); + $this->assertEquals(1, $result); $target = 6; $result = ternarySearchByRecursion($list, $target, 0, 4); - assertEquals(null, $result); + $this->assertEquals(null, $result); } public function testInterpolationSearch() @@ -194,15 +193,46 @@ public function testInterpolationSearch() $list = [2, 6, 8, 10, 12, 14, 16, 18, 20, 22, 26, 34, 39]; $target = 20; $result = interpolationSearch($list, $target); - assertEquals(8, $result); + $this->assertEquals(8, $result); $target = 12; $result = interpolationSearch($list, $target); - assertEquals(4, $result); + $this->assertEquals(4, $result); $target = 1000; $result = interpolationSearch($list, $target); - assertEquals(null, $result); + $this->assertEquals(null, $result); $target = 39; $result = interpolationSearch($list, $target); - assertEquals(12, $result); + $this->assertEquals(12, $result); + } + + public function testSentinelSearch() + { + $list = [1,3,5,2,4,13,18,23,25,30]; + $target = 1; + $result = SentinelSearch($list, $target); + $this->assertEquals(0, $result); + $target = 2; + $result = SentinelSearch($list, $target); + $this->assertEquals(3, $result); + $target = 1000; + $result = SentinelSearch($list, $target); + $this->assertEquals(-1, $result); + $target = -2; + $result = SentinelSearch($list, $target); + $this->assertEquals(-1, $result); + } + + public function testTwoPointers() + { + $list = [1, 2, 4, 7, 8, 10, 11, 12, 15]; + $target = 3; + $result = twoPointers($list, $target); + $this->assertEquals(1, $result); + $target = 12; + $result = twoPointers($list, $target); + $this->assertEquals(3, $result); + $target = 18; + $result = twoPointers($list, $target); + $this->assertEquals(2, $result); } } diff --git a/tests/Sorting/ArrayKeysSortTest.php b/tests/Sorting/ArrayKeysSortTest.php index 2b054987..31c322df 100644 --- a/tests/Sorting/ArrayKeysSortTest.php +++ b/tests/Sorting/ArrayKeysSortTest.php @@ -7,7 +7,8 @@ class ArrayKeysSortTest extends TestCase { - public function testArrayKeysSort() { + public function testArrayKeysSort() + { //test array of arrays $array1 = [ ['fruit' => 'banana', 'color' => 'yellow', 'cant' => 5], @@ -36,22 +37,22 @@ public function testArrayKeysSort() { $this->assertEquals($result2, $test2); //test array of objects - $object1 = new \stdClass; + $object1 = new \stdClass(); $object1->fruit = 'banana'; $object1->color = 'yellow'; $object1->cant = 5; - $object2 = new \stdClass; + $object2 = new \stdClass(); $object2->fruit = 'apple'; $object2->color = 'red'; $object2->cant = 2; - $object3 = new \stdClass; + $object3 = new \stdClass(); $object3->fruit = 'apple'; $object3->color = 'green'; $object3->cant = 7; - $object4 = new \stdClass; + $object4 = new \stdClass(); $object4->fruit = 'grapes'; $object4->color = 'purple'; $object4->cant = 4; diff --git a/tests/Sorting/ShellSortTest.php b/tests/Sorting/ShellSortTest.php new file mode 100644 index 00000000..f19aadb0 --- /dev/null +++ b/tests/Sorting/ShellSortTest.php @@ -0,0 +1,80 @@ +assertEquals([ + 272, 1392, 1689, 1807, 1856, 2246, 2811, 2847, 3720, 3938, 4204, 4337, 4605, 4696, 4813, 5239, 5257, 5520, + 5523, 5676, 6466, 6506, 7018, 7515, 7762, 7853, 7873, 8181, 8350, 8459, 8935, 9358, 9508, 9592, 10786, + 10867, 11122, 11754, 12029, 12440, 12973, 13720, 13732, 14098, 14123, 14202, 15003, 15352, 16122, 16387, + 16544, 16677, 16868, 17860, 18158, 18362, 18456, 18492, 18708, 19074, 19713, 19756, 20620, 21165, 21746, + 22192, 22296, 22479, 22874, 23232, 23606, 23954, 24105, 24689, 24714, 24932, 25236, 25385, 25774, 26277, + 26281, 26993, 27230, 27760, 27892, 28229, 28302, 28309, 28493, 28651, 29373, 29411, 29724, 30110, 30258, + 30298, 30819, 30948, 31159, 31263, 31577, 31805, 31919, 32074, 32158, 32178, 32392, 33446, 33679, 33963, + 33982, 34201, 34649, 34671, 34925, 35245, 35374, 36141, 36625, 36828, 36852, 37348, 38265, 38386, 39583, + 39621, 40171, 40206, 40372, 40459, 40565, 40742, 40789, 40858, 42310, 42348, 42422, 42685, 43340, 43688, + 43780, 43836, 44044, 44518, 44628, 44637, 44654, 45344, 45519, 45755, 45799, 45894, 46184, 46186, 46528, + 46574, 46704, 47432, 47489, 47596, 47615, 47662, 47910, 48208, 48964, 49048, 49052, 49168, 49709, 49809, + 49935, 50209, 50596, 50800, 51158, 51433, 51830, 51886, 52068, 52328, 52905, 52928, 53635, 54211, 54616, + 54955, 55295, 55416, 55515, 55549, 55908, 56063, 56301, 56494, 56841, 56963, 56992, 57402, 57954, 57981, + 58213, 58223, 58336, 59748, 59973, 59992, 61349, 61591, 61968, 62310, 63015, 63178, 63245, 63395, 63771, + 63968, 64204, 64208, 65764, 66174, 66178, 66206, 66570, 67025, 67338, 67452, 67957, 68125, 68514, 68756, + 68831, 68975, 69091, 69858, 70466, 70474, 71526, 71774, 72472, 72686, 72849, 73437, 73907, 74474, 74813, + 74872, 75043, 75045, 75190, 75197, 75786, 76201, 76277, 76474, 76818, 77026, 77849, 77977, 78095, 78105, + 78533, 78629, 78828, 79028, 79488, 79935, 80188, 81097, 81960, 82300, 82656, 82785, 83246, 83638, 84076, + 84402, 85799, 86070, 86198, 86309, 86358, 86805, 86814, 86897, 86968, 87450, 88246, 90235, 90728, 90876, + 91503, 92106, 92215, 92559, 92837, 93070, 93144, 93729, 94083, 94257, 94706, 94807, 96145, 96407, 96782, + 97156, 97222, 97577, 97799, 98758, 98845, 99156, 99211, 99272, 99519, 99711 + ], $sorted); + } + + public function testShellSortWithEmptyInput() + { + $array = []; + $sorted = shellSort($array); + $this->assertEmpty($sorted); + } + + public function testShellSortPerformance() + { + $array = range(1, 10000); + $n = count($array); + shuffle($array); // Randomize the order + $start = microtime(true); + shellSort($array); + $end = microtime(true); + $this->assertLessThan(pow($n, 3 / 2), $end - $start); + } +} diff --git a/tests/Sorting/SortingTest.php b/tests/Sorting/SortingTest.php new file mode 100644 index 00000000..2f93b62a --- /dev/null +++ b/tests/Sorting/SortingTest.php @@ -0,0 +1,294 @@ +assertEquals([1, 2, 3, 4, 5], $sorted); + } + + public function testBubbleSort2() + { + $array = [ + 51158, 1856, 8459, 67957, 59748, 58213, 90876, 39621, 66570, 64204, 79935, 27892, 47615, 94706, 34201, + 74474, 63968, 4337, 43688, 42685, 31577, 5239, 25385, 56301, 94083, 23232, 67025, 44044, 74813, 34671, + 90235, 65764, 49709, 12440, 21165, 20620, 38265, 12973, 25236, 93144, 13720, 4204, 77026, 42348, 19756, + 97222, 78828, 73437, 48208, 69858, 19713, 29411, 49809, 66174, 5257, 43340, 40565, 9592, 52328, 16677, + 38386, 55416, 99519, 13732, 84076, 52905, 47662, 31805, 46184, 2811, 35374, 50800, 53635, 51886, 49052, + 75197, 3720, 75045, 28309, 63771, 71526, 16122, 36625, 44654, 86814, 98845, 44637, 54955, 24714, 81960, + 78095, 49048, 99711, 272, 45755, 31919, 8181, 1392, 15352, 82656, 27760, 18362, 43780, 50209, 51433, 2847, + 62310, 87450, 22874, 40789, 56841, 52928, 5523, 76474, 8935, 63245, 16387, 21746, 47596, 84402, 49168, + 58223, 26993, 55908, 92837, 64208, 86309, 30819, 83638, 9508, 44628, 10786, 68125, 14123, 70474, 50596, + 44518, 74872, 61968, 36828, 17860, 4605, 68756, 86070, 52068, 51830, 56992, 45799, 42422, 68514, 92559, + 40206, 31263, 71774, 14202, 94807, 25774, 15003, 54211, 18708, 32074, 43836, 48964, 40742, 26281, 67338, + 75786, 34925, 34649, 45519, 72472, 80188, 40858, 83246, 92215, 66178, 67452, 86198, 82300, 45894, 97156, + 73907, 31159, 7018, 55549, 35245, 68975, 88246, 14098, 59973, 7762, 40459, 86358, 63178, 47489, 55515, + 79488, 46528, 99272, 10867, 75190, 56963, 5520, 56494, 42310, 40171, 78105, 29724, 30110, 28493, 36141, + 22479, 85799, 70466, 92106, 16868, 57402, 4813, 47432, 24689, 78533, 97577, 32178, 30258, 82785, 56063, + 76277, 96407, 77849, 1807, 45344, 30298, 18158, 49935, 90728, 22192, 36852, 33982, 66206, 30948, 40372, + 33446, 99156, 28651, 61591, 79028, 1689, 94257, 32158, 11122, 81097, 57981, 26277, 7515, 7873, 8350, 28229, + 24105, 76818, 86897, 18456, 29373, 7853, 24932, 93070, 4696, 63015, 9358, 28302, 3938, 11754, 33679, 18492, + 91503, 63395, 12029, 23954, 27230, 58336, 16544, 23606, 61349, 37348, 78629, 96145, 57954, 32392, 76201, + 54616, 59992, 5676, 97799, 47910, 98758, 75043, 72849, 6466, 68831, 2246, 69091, 22296, 6506, 93729, 86968, + 39583, 46186, 96782, 19074, 46574, 46704, 99211, 55295, 33963, 77977, 86805, 72686 + ]; + $sorted = bubbleSort2($array); + $this->assertEquals([ + 272, 1392, 1689, 1807, 1856, 2246, 2811, 2847, 3720, 3938, 4204, 4337, 4605, 4696, 4813, 5239, 5257, 5520, + 5523, 5676, 6466, 6506, 7018, 7515, 7762, 7853, 7873, 8181, 8350, 8459, 8935, 9358, 9508, 9592, 10786, + 10867, 11122, 11754, 12029, 12440, 12973, 13720, 13732, 14098, 14123, 14202, 15003, 15352, 16122, 16387, + 16544, 16677, 16868, 17860, 18158, 18362, 18456, 18492, 18708, 19074, 19713, 19756, 20620, 21165, 21746, + 22192, 22296, 22479, 22874, 23232, 23606, 23954, 24105, 24689, 24714, 24932, 25236, 25385, 25774, 26277, + 26281, 26993, 27230, 27760, 27892, 28229, 28302, 28309, 28493, 28651, 29373, 29411, 29724, 30110, 30258, + 30298, 30819, 30948, 31159, 31263, 31577, 31805, 31919, 32074, 32158, 32178, 32392, 33446, 33679, 33963, + 33982, 34201, 34649, 34671, 34925, 35245, 35374, 36141, 36625, 36828, 36852, 37348, 38265, 38386, 39583, + 39621, 40171, 40206, 40372, 40459, 40565, 40742, 40789, 40858, 42310, 42348, 42422, 42685, 43340, 43688, + 43780, 43836, 44044, 44518, 44628, 44637, 44654, 45344, 45519, 45755, 45799, 45894, 46184, 46186, 46528, + 46574, 46704, 47432, 47489, 47596, 47615, 47662, 47910, 48208, 48964, 49048, 49052, 49168, 49709, 49809, + 49935, 50209, 50596, 50800, 51158, 51433, 51830, 51886, 52068, 52328, 52905, 52928, 53635, 54211, 54616, + 54955, 55295, 55416, 55515, 55549, 55908, 56063, 56301, 56494, 56841, 56963, 56992, 57402, 57954, 57981, + 58213, 58223, 58336, 59748, 59973, 59992, 61349, 61591, 61968, 62310, 63015, 63178, 63245, 63395, 63771, + 63968, 64204, 64208, 65764, 66174, 66178, 66206, 66570, 67025, 67338, 67452, 67957, 68125, 68514, 68756, + 68831, 68975, 69091, 69858, 70466, 70474, 71526, 71774, 72472, 72686, 72849, 73437, 73907, 74474, 74813, + 74872, 75043, 75045, 75190, 75197, 75786, 76201, 76277, 76474, 76818, 77026, 77849, 77977, 78095, 78105, + 78533, 78629, 78828, 79028, 79488, 79935, 80188, 81097, 81960, 82300, 82656, 82785, 83246, 83638, 84076, + 84402, 85799, 86070, 86198, 86309, 86358, 86805, 86814, 86897, 86968, 87450, 88246, 90235, 90728, 90876, + 91503, 92106, 92215, 92559, 92837, 93070, 93144, 93729, 94083, 94257, 94706, 94807, 96145, 96407, 96782, + 97156, 97222, 97577, 97799, 98758, 98845, 99156, 99211, 99272, 99519, 99711 + ], $sorted); + } + + public function testCountSort() + { + $array = [-5, -10, 0, -3, 8, 5, -1, 10]; + $sorted = countSort($array); + $this->assertEquals([-10, -5, -3, -1, 0, 5, 8, 10], $sorted); + } + + public function testInsertionSort() + { + $array = [1, 5, 4, 2, 3]; + $sorted = insertionSort($array); + $this->assertEquals([1, 2, 3, 4, 5], $sorted); + } + + public function testMergeSort() + { + $array = [1, 5, 4, 2, 3]; + $sorted = mergeSort($array); + $this->assertEquals([1, 2, 3, 4, 5], $sorted); + } + + public function testQuickSort() + { + $array = [ + 51158, 1856, 8459, 67957, 59748, 58213, 90876, 39621, 66570, 64204, 79935, 27892, 47615, 94706, 34201, + 74474, 63968, 4337, 43688, 42685, 31577, 5239, 25385, 56301, 94083, 23232, 67025, 44044, 74813, 34671, + 90235, 65764, 49709, 12440, 21165, 20620, 38265, 12973, 25236, 93144, 13720, 4204, 77026, 42348, 19756, + 97222, 78828, 73437, 48208, 69858, 19713, 29411, 49809, 66174, 5257, 43340, 40565, 9592, 52328, 16677, + 38386, 55416, 99519, 13732, 84076, 52905, 47662, 31805, 46184, 2811, 35374, 50800, 53635, 51886, 49052, + 75197, 3720, 75045, 28309, 63771, 71526, 16122, 36625, 44654, 86814, 98845, 44637, 54955, 24714, 81960, + 78095, 49048, 99711, 272, 45755, 31919, 8181, 1392, 15352, 82656, 27760, 18362, 43780, 50209, 51433, 2847, + 62310, 87450, 22874, 40789, 56841, 52928, 5523, 76474, 8935, 63245, 16387, 21746, 47596, 84402, 49168, + 58223, 26993, 55908, 92837, 64208, 86309, 30819, 83638, 9508, 44628, 10786, 68125, 14123, 70474, 50596, + 44518, 74872, 61968, 36828, 17860, 4605, 68756, 86070, 52068, 51830, 56992, 45799, 42422, 68514, 92559, + 40206, 31263, 71774, 14202, 94807, 25774, 15003, 54211, 18708, 32074, 43836, 48964, 40742, 26281, 67338, + 75786, 34925, 34649, 45519, 72472, 80188, 40858, 83246, 92215, 66178, 67452, 86198, 82300, 45894, 97156, + 73907, 31159, 7018, 55549, 35245, 68975, 88246, 14098, 59973, 7762, 40459, 86358, 63178, 47489, 55515, + 79488, 46528, 99272, 10867, 75190, 56963, 5520, 56494, 42310, 40171, 78105, 29724, 30110, 28493, 36141, + 22479, 85799, 70466, 92106, 16868, 57402, 4813, 47432, 24689, 78533, 97577, 32178, 30258, 82785, 56063, + 76277, 96407, 77849, 1807, 45344, 30298, 18158, 49935, 90728, 22192, 36852, 33982, 66206, 30948, 40372, + 33446, 99156, 28651, 61591, 79028, 1689, 94257, 32158, 11122, 81097, 57981, 26277, 7515, 7873, 8350, 28229, + 24105, 76818, 86897, 18456, 29373, 7853, 24932, 93070, 4696, 63015, 9358, 28302, 3938, 11754, 33679, 18492, + 91503, 63395, 12029, 23954, 27230, 58336, 16544, 23606, 61349, 37348, 78629, 96145, 57954, 32392, 76201, + 54616, 59992, 5676, 97799, 47910, 98758, 75043, 72849, 6466, 68831, 2246, 69091, 22296, 6506, 93729, 86968, + 39583, 46186, 96782, 19074, 46574, 46704, 99211, 55295, 33963, 77977, 86805, 72686 + ]; + $sorted = quickSort($array); + $this->assertEquals([ + 272, 1392, 1689, 1807, 1856, 2246, 2811, 2847, 3720, 3938, 4204, 4337, 4605, 4696, 4813, 5239, 5257, 5520, + 5523, 5676, 6466, 6506, 7018, 7515, 7762, 7853, 7873, 8181, 8350, 8459, 8935, 9358, 9508, 9592, 10786, + 10867, 11122, 11754, 12029, 12440, 12973, 13720, 13732, 14098, 14123, 14202, 15003, 15352, 16122, 16387, + 16544, 16677, 16868, 17860, 18158, 18362, 18456, 18492, 18708, 19074, 19713, 19756, 20620, 21165, 21746, + 22192, 22296, 22479, 22874, 23232, 23606, 23954, 24105, 24689, 24714, 24932, 25236, 25385, 25774, 26277, + 26281, 26993, 27230, 27760, 27892, 28229, 28302, 28309, 28493, 28651, 29373, 29411, 29724, 30110, 30258, + 30298, 30819, 30948, 31159, 31263, 31577, 31805, 31919, 32074, 32158, 32178, 32392, 33446, 33679, 33963, + 33982, 34201, 34649, 34671, 34925, 35245, 35374, 36141, 36625, 36828, 36852, 37348, 38265, 38386, 39583, + 39621, 40171, 40206, 40372, 40459, 40565, 40742, 40789, 40858, 42310, 42348, 42422, 42685, 43340, 43688, + 43780, 43836, 44044, 44518, 44628, 44637, 44654, 45344, 45519, 45755, 45799, 45894, 46184, 46186, 46528, + 46574, 46704, 47432, 47489, 47596, 47615, 47662, 47910, 48208, 48964, 49048, 49052, 49168, 49709, 49809, + 49935, 50209, 50596, 50800, 51158, 51433, 51830, 51886, 52068, 52328, 52905, 52928, 53635, 54211, 54616, + 54955, 55295, 55416, 55515, 55549, 55908, 56063, 56301, 56494, 56841, 56963, 56992, 57402, 57954, 57981, + 58213, 58223, 58336, 59748, 59973, 59992, 61349, 61591, 61968, 62310, 63015, 63178, 63245, 63395, 63771, + 63968, 64204, 64208, 65764, 66174, 66178, 66206, 66570, 67025, 67338, 67452, 67957, 68125, 68514, 68756, + 68831, 68975, 69091, 69858, 70466, 70474, 71526, 71774, 72472, 72686, 72849, 73437, 73907, 74474, 74813, + 74872, 75043, 75045, 75190, 75197, 75786, 76201, 76277, 76474, 76818, 77026, 77849, 77977, 78095, 78105, + 78533, 78629, 78828, 79028, 79488, 79935, 80188, 81097, 81960, 82300, 82656, 82785, 83246, 83638, 84076, + 84402, 85799, 86070, 86198, 86309, 86358, 86805, 86814, 86897, 86968, 87450, 88246, 90235, 90728, 90876, + 91503, 92106, 92215, 92559, 92837, 93070, 93144, 93729, 94083, 94257, 94706, 94807, 96145, 96407, 96782, + 97156, 97222, 97577, 97799, 98758, 98845, 99156, 99211, 99272, 99519, 99711 + ], $sorted); + } + + public function testQuickSortWithEmptyInput() + { + $array = []; + $sorted = quickSort($array); + $this->assertEmpty($sorted); + } + + public function testRadixSort() + { + $array = [1, 5, 4, 2, 3]; + $sorted = radixSort($array); + $this->assertEquals([1, 2, 3, 4, 5], $sorted); + } + + public function testSelectionSort() + { + $array = [1, 5, 4, 2, 3]; + $sorted = selectionSort($array); + $this->assertEquals([1, 2, 3, 4, 5], $sorted); + } + + public function testBubbleSortPerformance() + { + $array = range(1, 1000000); + $start = microtime(true); + bubbleSort($array); + $end = microtime(true); + $this->assertLessThan(1, $end - $start); + } + + public function testBubbleSort2Performance() + { + $array = range(1, 1000000); + $start = microtime(true); + bubbleSort2($array); + $end = microtime(true); + $this->assertLessThan(1, $end - $start); + } + + public function testCountSortPerformance() + { + $array = range(1, 1000000); + $start = microtime(true); + countSort($array); + $end = microtime(true); + $this->assertLessThan(1, $end - $start); + } + + public function testInsertionSortPerformance() + { + $array = range(1, 1000000); + $start = microtime(true); + insertionSort($array); + $end = microtime(true); + $this->assertLessThan(1, $end - $start); + } + + public function testMergeSortPerformance() + { + $array = range(1, 10000); + $start = microtime(true); + mergeSort($array); + $end = microtime(true); + $this->assertLessThan(1, $end - $start); + } + + public function testQuickSortPerformance() + { + $array = range(1, 1000); + $start = microtime(true); + quickSort($array); + $end = microtime(true); + $this->assertLessThan(1, $end - $start); + } + + public function testRadixSortPerformance() + { + $array = range(1, 10000); + $start = microtime(true); + radixSort($array); + $end = microtime(true); + $this->assertLessThan(1, $end - $start); + } + + public function testSelectionSortPerformance() + { + $array = range(1, 1000); + $start = microtime(true); + selectionSort($array); + $end = microtime(true); + $this->assertLessThan(1, $end - $start); + } + + public function testCountSortCipher() + { + $firstArray = array(20, 16, -5, -8, 6, 12, 2, 4, -3, 9); + $expectedResultOne = array(-8, -5, -3, 2, 4, 6, 9, 12, 16, 20); + $secondArray = array(-6, 12, 14, 17, 5, 4, -9, 15, 0, -8); + $expectedResultTwo = array(-9, -8, -6, 0, 4, 5, 12, 14, 15, 17); + + $resultOne = countSort($firstArray); + $resultTwo = countSort($secondArray); + + $this->assertEquals($expectedResultOne, $resultOne); + $this->assertEquals($expectedResultTwo, $resultTwo); + } + + public function testQuickSortCipher() + { + $array1 = [20, 16, -5, -8, 6, 12, 2, 4, -3, 9]; + $array2 = [-6, 12, 14, 17, 5, 4, -9, 15, 0, -8]; + + $result1 = [-8, -5, -3, 2, 4, 6, 9, 12, 16, 20]; + $result2 = [-9, -8, -6, 0, 4, 5, 12, 14, 15, 17]; + + $test1 = quickSort($array1); + $test2 = quickSort($array2); + + $this->assertEquals($result1, $test1); + $this->assertEquals($result2, $test2); + } + + public function testHeapSortPerformance() + { + $array = range(1, 10000); + shuffle($array); // Randomize the order + $start = microtime(true); + heapSort($array); + $end = microtime(true); + $this->assertLessThan(1, $end - $start); + } + + public function testHeapSortCipher() + { + $firstArray = [20, 16, -5, -8, 6, 12, 2, 4, -3, 9]; + $expectedResultOne = [-8, -5, -3, 2, 4, 6, 9, 12, 16, 20]; + + $secondArray = [-6, 12, 14, 17, 5, 4, -9, 15, 0, -8]; + $expectedResultTwo = [-9, -8, -6, 0, 4, 5, 12, 14, 15, 17]; + + $resultOne = heapSort($firstArray); + $resultTwo = heapSort($secondArray); + + $this->assertEquals($expectedResultOne, $resultOne); + $this->assertEquals($expectedResultTwo, $resultTwo); + } +} diff --git a/tests/Sorting/SortingTests.php b/tests/Sorting/SortingTests.php deleted file mode 100644 index 16d0e09e..00000000 --- a/tests/Sorting/SortingTests.php +++ /dev/null @@ -1,175 +0,0 @@ -assertEquals([1, 2, 3, 4, 5], $sorted); - } - - public function testBubbleSort2() - { - $array = [51158,1856,8459,67957,59748,58213,90876,39621,66570,64204,79935,27892,47615,94706,34201,74474,63968,4337,43688,42685,31577,5239,25385,56301,94083,23232,67025,44044,74813,34671,90235,65764,49709,12440,21165,20620,38265,12973,25236,93144,13720,4204,77026,42348,19756,97222,78828,73437,48208,69858,19713,29411,49809,66174,5257,43340,40565,9592,52328,16677,38386,55416,99519,13732,84076,52905,47662,31805,46184,2811,35374,50800,53635,51886,49052,75197,3720,75045,28309,63771,71526,16122,36625,44654,86814,98845,44637,54955,24714,81960,78095,49048,99711,272,45755,31919,8181,1392,15352,82656,27760,18362,43780,50209,51433,2847,62310,87450,22874,40789,56841,52928,5523,76474,8935,63245,16387,21746,47596,84402,49168,58223,26993,55908,92837,64208,86309,30819,83638,9508,44628,10786,68125,14123,70474,50596,44518,74872,61968,36828,17860,4605,68756,86070,52068,51830,56992,45799,42422,68514,92559,40206,31263,71774,14202,94807,25774,15003,54211,18708,32074,43836,48964,40742,26281,67338,75786,34925,34649,45519,72472,80188,40858,83246,92215,66178,67452,86198,82300,45894,97156,73907,31159,7018,55549,35245,68975,88246,14098,59973,7762,40459,86358,63178,47489,55515,79488,46528,99272,10867,75190,56963,5520,56494,42310,40171,78105,29724,30110,28493,36141,22479,85799,70466,92106,16868,57402,4813,47432,24689,78533,97577,32178,30258,82785,56063,76277,96407,77849,1807,45344,30298,18158,49935,90728,22192,36852,33982,66206,30948,40372,33446,99156,28651,61591,79028,1689,94257,32158,11122,81097,57981,26277,7515,7873,8350,28229,24105,76818,86897,18456,29373,7853,24932,93070,4696,63015,9358,28302,3938,11754,33679,18492,91503,63395,12029,23954,27230,58336,16544,23606,61349,37348,78629,96145,57954,32392,76201,54616,59992,5676,97799,47910,98758,75043,72849,6466,68831,2246,69091,22296,6506,93729,86968,39583,46186,96782,19074,46574,46704,99211,55295,33963,77977,86805,72686]; - $sorted = bubbleSort2($array); - $this->assertEquals([272, 1392, 1689, 1807, 1856, 2246, 2811, 2847, 3720, 3938, 4204, 4337, 4605, 4696, 4813, 5239, 5257, 5520, 5523, 5676, 6466, 6506, 7018, 7515, 7762, 7853, 7873, 8181, 8350, 8459, 8935, 9358, 9508, 9592, 10786, 10867, 11122, 11754, 12029, 12440, 12973, 13720, 13732, 14098, 14123, 14202, 15003, 15352, 16122, 16387, 16544, 16677, 16868, 17860, 18158, 18362, 18456, 18492, 18708, 19074, 19713, 19756, 20620, 21165, 21746, 22192, 22296, 22479, 22874, 23232, 23606, 23954, 24105, 24689, 24714, 24932, 25236, 25385, 25774, 26277, 26281, 26993, 27230, 27760, 27892, 28229, 28302, 28309, 28493, 28651, 29373, 29411, 29724, 30110, 30258, 30298, 30819, 30948, 31159, 31263, 31577, 31805, 31919, 32074, 32158, 32178, 32392, 33446, 33679, 33963, 33982, 34201, 34649, 34671, 34925, 35245, 35374, 36141, 36625, 36828, 36852, 37348, 38265, 38386, 39583, 39621, 40171, 40206, 40372, 40459, 40565, 40742, 40789, 40858, 42310, 42348, 42422, 42685, 43340, 43688, 43780, 43836, 44044, 44518, 44628, 44637, 44654, 45344, 45519, 45755, 45799, 45894, 46184, 46186, 46528, 46574, 46704, 47432, 47489, 47596, 47615, 47662, 47910, 48208, 48964, 49048, 49052, 49168, 49709, 49809, 49935, 50209, 50596, 50800, 51158, 51433, 51830, 51886, 52068, 52328, 52905, 52928, 53635, 54211, 54616, 54955, 55295, 55416, 55515, 55549, 55908, 56063, 56301, 56494, 56841, 56963, 56992, 57402, 57954, 57981, 58213, 58223, 58336, 59748, 59973, 59992, 61349, 61591, 61968, 62310, 63015, 63178, 63245, 63395, 63771, 63968, 64204, 64208, 65764, 66174, 66178, 66206, 66570, 67025, 67338, 67452, 67957, 68125, 68514, 68756, 68831, 68975, 69091, 69858, 70466, 70474, 71526, 71774, 72472, 72686, 72849, 73437, 73907, 74474, 74813, 74872, 75043, 75045, 75190, 75197, 75786, 76201, 76277, 76474, 76818, 77026, 77849, 77977, 78095, 78105, 78533, 78629, 78828, 79028, 79488, 79935, 80188, 81097, 81960, 82300, 82656, 82785, 83246, 83638, 84076, 84402, 85799, 86070, 86198, 86309, 86358, 86805, 86814, 86897, 86968, 87450, 88246, 90235, 90728, 90876, 91503, 92106, 92215, 92559, 92837, 93070, 93144, 93729, 94083, 94257, 94706, 94807, 96145, 96407, 96782, 97156, 97222, 97577, 97799, 98758, 98845, 99156, 99211, 99272, 99519, 99711], $sorted); - } - - public function testCountSort() - { - $array = [-5, -10, 0, -3, 8, 5, -1, 10]; - $min = 0; - $max = 9; - $sorted = countSort($array, 0, 9); - $this->assertEquals([-10, -5, -3, -1, 0, 5, 8, 10], $sorted); - } - - public function testInsertionSort() - { - $array = [1, 5, 4, 2, 3]; - $sorted = insertionSort($array); - $this->assertEquals([1, 2, 3, 4, 5], $sorted); - } - - public function testMergeSort() - { - $array = [1, 5, 4, 2, 3]; - $sorted = mergeSort($array); - $this->assertEquals([1, 2, 3, 4, 5], $sorted); - } - - public function testQuickSort() - { - $array = [51158,1856,8459,67957,59748,58213,90876,39621,66570,64204,79935,27892,47615,94706,34201,74474,63968,4337,43688,42685,31577,5239,25385,56301,94083,23232,67025,44044,74813,34671,90235,65764,49709,12440,21165,20620,38265,12973,25236,93144,13720,4204,77026,42348,19756,97222,78828,73437,48208,69858,19713,29411,49809,66174,5257,43340,40565,9592,52328,16677,38386,55416,99519,13732,84076,52905,47662,31805,46184,2811,35374,50800,53635,51886,49052,75197,3720,75045,28309,63771,71526,16122,36625,44654,86814,98845,44637,54955,24714,81960,78095,49048,99711,272,45755,31919,8181,1392,15352,82656,27760,18362,43780,50209,51433,2847,62310,87450,22874,40789,56841,52928,5523,76474,8935,63245,16387,21746,47596,84402,49168,58223,26993,55908,92837,64208,86309,30819,83638,9508,44628,10786,68125,14123,70474,50596,44518,74872,61968,36828,17860,4605,68756,86070,52068,51830,56992,45799,42422,68514,92559,40206,31263,71774,14202,94807,25774,15003,54211,18708,32074,43836,48964,40742,26281,67338,75786,34925,34649,45519,72472,80188,40858,83246,92215,66178,67452,86198,82300,45894,97156,73907,31159,7018,55549,35245,68975,88246,14098,59973,7762,40459,86358,63178,47489,55515,79488,46528,99272,10867,75190,56963,5520,56494,42310,40171,78105,29724,30110,28493,36141,22479,85799,70466,92106,16868,57402,4813,47432,24689,78533,97577,32178,30258,82785,56063,76277,96407,77849,1807,45344,30298,18158,49935,90728,22192,36852,33982,66206,30948,40372,33446,99156,28651,61591,79028,1689,94257,32158,11122,81097,57981,26277,7515,7873,8350,28229,24105,76818,86897,18456,29373,7853,24932,93070,4696,63015,9358,28302,3938,11754,33679,18492,91503,63395,12029,23954,27230,58336,16544,23606,61349,37348,78629,96145,57954,32392,76201,54616,59992,5676,97799,47910,98758,75043,72849,6466,68831,2246,69091,22296,6506,93729,86968,39583,46186,96782,19074,46574,46704,99211,55295,33963,77977,86805,72686]; - $sorted = quickSort($array); - $this->assertEquals([272, 1392, 1689, 1807, 1856, 2246, 2811, 2847, 3720, 3938, 4204, 4337, 4605, 4696, 4813, 5239, 5257, 5520, 5523, 5676, 6466, 6506, 7018, 7515, 7762, 7853, 7873, 8181, 8350, 8459, 8935, 9358, 9508, 9592, 10786, 10867, 11122, 11754, 12029, 12440, 12973, 13720, 13732, 14098, 14123, 14202, 15003, 15352, 16122, 16387, 16544, 16677, 16868, 17860, 18158, 18362, 18456, 18492, 18708, 19074, 19713, 19756, 20620, 21165, 21746, 22192, 22296, 22479, 22874, 23232, 23606, 23954, 24105, 24689, 24714, 24932, 25236, 25385, 25774, 26277, 26281, 26993, 27230, 27760, 27892, 28229, 28302, 28309, 28493, 28651, 29373, 29411, 29724, 30110, 30258, 30298, 30819, 30948, 31159, 31263, 31577, 31805, 31919, 32074, 32158, 32178, 32392, 33446, 33679, 33963, 33982, 34201, 34649, 34671, 34925, 35245, 35374, 36141, 36625, 36828, 36852, 37348, 38265, 38386, 39583, 39621, 40171, 40206, 40372, 40459, 40565, 40742, 40789, 40858, 42310, 42348, 42422, 42685, 43340, 43688, 43780, 43836, 44044, 44518, 44628, 44637, 44654, 45344, 45519, 45755, 45799, 45894, 46184, 46186, 46528, 46574, 46704, 47432, 47489, 47596, 47615, 47662, 47910, 48208, 48964, 49048, 49052, 49168, 49709, 49809, 49935, 50209, 50596, 50800, 51158, 51433, 51830, 51886, 52068, 52328, 52905, 52928, 53635, 54211, 54616, 54955, 55295, 55416, 55515, 55549, 55908, 56063, 56301, 56494, 56841, 56963, 56992, 57402, 57954, 57981, 58213, 58223, 58336, 59748, 59973, 59992, 61349, 61591, 61968, 62310, 63015, 63178, 63245, 63395, 63771, 63968, 64204, 64208, 65764, 66174, 66178, 66206, 66570, 67025, 67338, 67452, 67957, 68125, 68514, 68756, 68831, 68975, 69091, 69858, 70466, 70474, 71526, 71774, 72472, 72686, 72849, 73437, 73907, 74474, 74813, 74872, 75043, 75045, 75190, 75197, 75786, 76201, 76277, 76474, 76818, 77026, 77849, 77977, 78095, 78105, 78533, 78629, 78828, 79028, 79488, 79935, 80188, 81097, 81960, 82300, 82656, 82785, 83246, 83638, 84076, 84402, 85799, 86070, 86198, 86309, 86358, 86805, 86814, 86897, 86968, 87450, 88246, 90235, 90728, 90876, 91503, 92106, 92215, 92559, 92837, 93070, 93144, 93729, 94083, 94257, 94706, 94807, 96145, 96407, 96782, 97156, 97222, 97577, 97799, 98758, 98845, 99156, 99211, 99272, 99519, 99711], $sorted); - } - - public function testRadixSort() - { - $array = [1, 5, 4, 2, 3]; - $sorted = radixSort($array); - $this->assertEquals([1, 2, 3, 4, 5], $sorted); - } - - public function testSelectionSort() - { - $array = [1, 5, 4, 2, 3]; - $sorted = selectionSort($array); - $this->assertEquals([1, 2, 3, 4, 5], $sorted); - } - - public function testBubbleSortPerformance() - { - $array = range(1, 1000000); - $start = microtime(true); - bubbleSort($array); - $end = microtime(true); - $this->assertLessThan(1, $end - $start); - } - - public function testBubbleSort2Performance() - { - $array = range(1, 1000000); - $start = microtime(true); - bubbleSort2($array); - $end = microtime(true); - $this->assertLessThan(1, $end - $start); - } - - public function testCountSortPerformance() - { - $array = range(1, 1000000); - $start = microtime(true); - countSort($array); - $end = microtime(true); - $this->assertLessThan(1, $end - $start); - } - - public function testInsertionSortPerformance() - { - $array = range(1, 1000000); - $start = microtime(true); - insertionSort($array); - $end = microtime(true); - $this->assertLessThan(1, $end - $start); - } - - public function testMergeSortPerformance() - { - $array = range(1, 1000000); - $start = microtime(true); - mergeSort($array); - $end = microtime(true); - $this->assertLessThan(1, $end - $start); - } - - public function testQuickSortPerformance() - { - $array = range(1, 1000000); - $start = microtime(true); - quickSort($array); - $end = microtime(true); - $this->assertLessThan(1, $end - $start); - } - - public function testRadixSortPerformance() - { - $array = range(1, 1000000); - $start = microtime(true); - radixSort($array); - $end = microtime(true); - $this->assertLessThan(1, $end - $start); - } - - public function testSelectionSortPerformance() - { - $array = range(1, 1000000); - $start = microtime(true); - selectionSort($array); - $end = microtime(true); - $this->assertLessThan(1, $end - $start); - } - - public function testCountSortCipher() - { - $firstArray = array(20, 16, -5, -8, 6, 12, 2, 4, -3, 9); - $expectedResultOne = array(-8, -5, -3, 2, 4, 6, 9, 12, 16, 20); - $secondArray = array(-6, 12, 14, 17, 5, 4, -9, 15, 0, -8); - $expectedResultTwo = array(-9, -8, -6, 0, 4, 5, 12, 14, 15, 17); - - $resultOne = countSort($firstArray, $minRange = -10, $maxRange = 20); - $resultTwo = countSort($secondArray, $minRange = -10, $maxRange = 20); - - $this->assertEquals($expectedResultOne, $resultOne); - $this->assertEquals($expectedResultTwo, $resultTwo); - } - - public function testQuickSortCipher() - { - $array1 = [20, 16, -5, -8, 6, 12, 2, 4, -3, 9]; - $array2 = [-6, 12, 14, 17, 5, 4, -9, 15, 0, -8]; - - $result1 = [-8, -5, -3, 2, 4, 6, 9, 12, 16, 20]; - $result2 = [-9, -8, -6, 0, 4, 5, 12, 14, 15, 17]; - - $test1 = quickSort($array1); - $test2 = quickSort($array2); - - $this->assertEquals($result1, $test1); - $this->assertEquals($result2, $test2); - } -} diff --git a/tests/Strings/StringsTest.php b/tests/Strings/StringsTest.php index 3b17907e..871933e5 100644 --- a/tests/Strings/StringsTest.php +++ b/tests/Strings/StringsTest.php @@ -1,9 +1,5 @@ assertTrue(isPalindrome('MaDam')); // true + $this->assertFalse(isPalindrome('ThisIsTest')); // false + $this->assertFalse(isPalindrome('')); // false + $this->assertTrue(isPalindrome('Sator Arepo Tenet Opera Rotas')); // true + $this->assertFalse(isPalindrome('Sator Arepo Tenet Opera Rotas', false)); // false since we are doing + // case-sensitive check } public function testCountSentences() { - assertEquals(countSentences('Hi! Hello world.'), 2); - assertEquals(countSentences('i am testing. test....'), 2); - assertEquals(countSentences('How are you?'), 1); + $this->assertEquals(2, countSentences('Hi! Hello world.')); + $this->assertEquals(2, countSentences('i am testing. test....')); + $this->assertEquals(1, countSentences('How are you?')); } public function testReverseString() { - assertEquals('txet emos si sihT', reverseString('This is some text')); - assertEquals('mADaM', reverseString('MaDAm')); - assertNotEquals('MaDAm', reverseString('MaDAm')); + $this->assertEquals('txet emos si sihT', reverseString('This is some text')); + $this->assertEquals('mADaM', reverseString('MaDAm')); + $this->assertNotEquals('MaDAm', reverseString('MaDAm')); } public function testReverseWords() { - assertEquals('Fun is Coding PHP', reverseWords('PHP Coding is Fun')); - assertEquals('OneWord', reverseWords('OneWord')); - assertEquals('Text different some is This', reverseWords('This is some different Text')); + $this->assertEquals('Fun is Coding PHP', reverseWords('PHP Coding is Fun')); + $this->assertEquals('OneWord', reverseWords('OneWord')); + $this->assertEquals('Text different some is This', reverseWords('This is some different Text')); } public function testIsAnagram() { - assertTrue(isAnagram("php", "PHP")); // By default it's case-insensitive - assertFalse(isAnagram("php", "PHP", false)); // Make case-sensitive check - assertTrue(isAnagram("php is fun", "pin hpf us")); - assertFalse(isAnagram("Hello", " Hello")); //Extra space character - assertTrue(isAnagram("ScRamble", "SRlmcaeb", false)); // Check with a mixture of upper and lower case + $this->assertTrue(isAnagram("php", "PHP")); // By default it's case-insensitive + $this->assertFalse(isAnagram("php", "PHP", false)); // Make case-sensitive check + $this->assertTrue(isAnagram("php is fun", "pin hpf us")); + $this->assertFalse(isAnagram("Hello", " Hello")); //Extra space character + $this->assertTrue(isAnagram("ScRamble", "SRlmcaeb", false)); // Check with a mixture of upper and lower case } public function testMaxCharacter() { - assertEquals(maxCharacter("this is test for max character repetition"), 't'); - assertEquals(maxCharacter("This is Test for max characTer repetition"), 't'); - assertEquals(maxCharacter(" "), ' '); + $this->assertEquals('t', maxCharacter("this is test for max character repetition")); + $this->assertEquals('t', maxCharacter("This is Test for max characTer repetition")); + $this->assertEquals(' ', maxCharacter(" ")); } public function testCountVowels() { - assertEquals(countVowelsSimple("This is a string with 7 vowels"), 7); - assertEquals(countVowelsSimple("hello world"), 3); - assertEquals(countVowelsRegex("Just A list of somE aaaaaaaaaa"), 16); + $this->assertEquals(7, countVowelsSimple("This is a string with 7 vowels")); + $this->assertEquals(3, countVowelsSimple("hello world")); + $this->assertEquals(16, countVowelsRegex("Just A list of somE aaaaaaaaaa")); - assertEquals(countVowelsRegex("This is a string with 7 vowels"), 7); - assertEquals(countVowelsRegex("hello world"), 3); - assertEquals(countVowelsRegex("Just A list of somE aaaaaaaaaa"), 16); + $this->assertEquals(7, countVowelsRegex("This is a string with 7 vowels")); + $this->assertEquals(3, countVowelsRegex("hello world")); + $this->assertEquals(16, countVowelsRegex("Just A list of somE aaaaaaaaaa")); } public function testCountConsonants() { - assertEquals(countConsonants("This is a string with 19 consonants"), 19); - assertEquals(countConsonants("hello world"), 7); - assertEquals(countConsonants("Just A list of somE aaaaaaaaaa"), 9); + $this->assertEquals(19, countConsonants("This is a string with 19 consonants")); + $this->assertEquals(7, countConsonants("hello world")); + $this->assertEquals(9, countConsonants("Just A list of somE aaaaaaaaaa")); + } + public function testCountHomogenous() + { + $this->assertEquals(4, countHomogenous("abbcccaa")); + $this->assertEquals(2, countHomogenous("xy")); } public function testFindDistance() { - assertEquals(findDistance("hello", "hallo"), 1); - assertEquals(findDistance("hallo", "hello"), 1); - assertEquals(findDistance("sunday", "sunday"), 0); - assertEquals(findDistance("saturday", "sunday"), 3); + $this->assertEquals(1, findDistance("hello", "hallo")); + $this->assertEquals(1, findDistance("hallo", "hello")); + $this->assertEquals(0, findDistance("sunday", "sunday")); + $this->assertEquals(3, findDistance("saturday", "sunday")); } }