Skip to content

Commit

Permalink
Merge pull request #7 from KaririCode-Framework/develop
Browse files Browse the repository at this point in the history
Implement TreeMap and TreeMapNode with comprehensive test coverage
  • Loading branch information
walmir-silva authored Jul 1, 2024
2 parents 54c81cf + 9523292 commit 397d36b
Show file tree
Hide file tree
Showing 8 changed files with 916 additions and 4 deletions.
358 changes: 358 additions & 0 deletions src/Map/TreeMap.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,358 @@
<?php

declare(strict_types=1);

namespace KaririCode\DataStructure\Map;

use KaririCode\Contract\DataStructure\Map;
use KaririCode\DataStructure\TreeMapNode;

/**
* TreeMap implementation.
*
* This class implements a map using a self-balancing binary search tree (Red-Black Tree).
* It provides O(log n) time complexity for put, get, and remove operations.
*
* @category Maps
*
* @author Walmir Silva <walmir.silva@kariricode.org>
* @license MIT
*
* @see https://kariricode.org/
*/
class TreeMap implements Map
{
private ?TreeMapNode $root = null;

public function put(mixed $key, mixed $value): void
{
$newNode = new TreeMapNode($key, $value);
if (null === $this->root) {
$this->root = $newNode;
$this->root->setBlack();
} else {
$this->insertNode($newNode);
$this->balanceAfterInsertion($newNode);
}
}

public function get(mixed $key): mixed
{
return $this->findNode($key)?->value;
}

public function remove(mixed $key): bool
{
$node = $this->findNode($key);
if (null === $node) {
return false;
}
$this->deleteNode($node);

return true;
}

private function insertNode(TreeMapNode $newNode): void
{
$current = $this->root;
$parent = null;
while (null !== $current) {
$parent = $current;
if ($newNode->key < $current->key) {
$current = $current->left;
} elseif ($newNode->key > $current->key) {
$current = $current->right;
} else {
// Key already exists, update the value
$current->value = $newNode->value;

return;
}
}

$newNode->parent = $parent;
if ($newNode->key < $parent->key) {
$parent->left = $newNode;
} else {
$parent->right = $newNode;
}
$this->balanceAfterInsertion($newNode);
}

private function balanceAfterInsertion(TreeMapNode $node): void
{
while ($node !== $this->root && null !== $node->parent && $node->parent->isRed()) {
if ($node->parent === $node->parent->parent->left) {
$uncle = $node->parent->parent->right;
if (null !== $uncle && $uncle->isRed()) {
$node->parent->setBlack();
$uncle->setBlack();
if (null !== $node->parent->parent) {
$node->parent->parent->setRed();
$node = $node->parent->parent;
}
} else {
if ($node === $node->parent->right) {
$node = $node->parent;
$this->rotateLeft($node);
}
if (null !== $node->parent) {
$node->parent->setBlack();
if (null !== $node->parent->parent) {
$node->parent->parent->setRed();
$this->rotateRight($node->parent->parent);
}
}
}
} else {
$uncle = $node->parent->parent->left;
if (null !== $uncle && $uncle->isRed()) {
$node->parent->setBlack();
$uncle->setBlack();
if (null !== $node->parent->parent) {
$node->parent->parent->setRed();
$node = $node->parent->parent;
}
} else {
if ($node === $node->parent->left) {
$node = $node->parent;
$this->rotateRight($node);
}
if (null !== $node->parent) {
$node->parent->setBlack();
if (null !== $node->parent->parent) {
$node->parent->parent->setRed();
$this->rotateLeft($node->parent->parent);
}
}
}
}
}
$this->root->setBlack();
}

private function rotateLeft(TreeMapNode $node): void
{
$rightChild = $node->right;
$node->setRight($rightChild->left);
if (null !== $rightChild->left) {
$rightChild->left->parent = $node;
}
$rightChild->parent = $node->parent;
if (null === $node->parent) {
$this->root = $rightChild;
} elseif ($node === $node->parent->left) {
$node->parent->left = $rightChild;
} else {
$node->parent->right = $rightChild;
}
$rightChild->left = $node;
$node->parent = $rightChild;
}

private function rotateRight(TreeMapNode $node): void
{
$leftChild = $node->left;
$node->setLeft($leftChild->right);
if (null !== $leftChild->right) {
$leftChild->right->parent = $node;
}
$leftChild->parent = $node->parent;
if (null === $node->parent) {
$this->root = $leftChild;
} elseif ($node === $node->parent->right) {
$node->parent->right = $leftChild;
} else {
$node->parent->left = $leftChild;
}
$leftChild->right = $node;
$node->parent = $leftChild;
}

private function findNode(mixed $key): ?TreeMapNode
{
$current = $this->root;
while (null !== $current) {
if ($key === $current->key) {
return $current;
}
$current = $key < $current->key ? $current->left : $current->right;
}

return null;
}

private function deleteNode(TreeMapNode $node): void
{
$replacementNode = $this->getReplacementNode($node);
$needsBalancing = $node->isBlack() && (null === $replacementNode || $replacementNode->isBlack());

if (null === $replacementNode) {
if ($node === $this->root) {
$this->root = null;
} else {
if ($needsBalancing) {
$this->balanceBeforeRemoval($node);
}
$node->removeFromParent();
}

return;
}

if (null === $node->left || null === $node->right) {
if ($node === $this->root) {
$this->root = $replacementNode;
$replacementNode->setBlack();
$replacementNode->parent = null;
} else {
$node->replaceWith($replacementNode);
if ($needsBalancing) {
$this->balanceBeforeRemoval($replacementNode);
}
}

return;
}

$successor = $this->minimum($node->right);
$originalColor = $successor->isBlack();
$node->key = $successor->key;
$node->value = $successor->value;
$replacementNode = $successor->right;

if ($successor->parent === $node) {
if (null !== $replacementNode) {
$replacementNode->parent = $successor;
}
} else {
$this->transplant($successor, $successor->right);
$successor->right = $node->right;
if (null !== $successor->right) {
$successor->right->parent = $successor;
}
}

$this->transplant($node, $successor);
$successor->left = $node->left;
if (null !== $successor->left) {
$successor->left->parent = $successor;
}
$successor->color = $node->color;

if (TreeMapNode::BLACK === $originalColor && null !== $replacementNode) {
$this->balanceBeforeRemoval($replacementNode);
}
}

private function transplant(TreeMapNode $u, ?TreeMapNode $v): void
{
if (null === $u->parent) {
$this->root = $v;
} elseif ($u === $u->parent->left) {
$u->parent->left = $v;
} else {
$u->parent->right = $v;
}
if (null !== $v) {
$v->parent = $u->parent;
}
}

private function getReplacementNode(TreeMapNode $node): ?TreeMapNode
{
if (null !== $node->left && null !== $node->right) {
return $this->minimum($node->right);
}

return $node->left ?? $node->right;
}

private function balanceBeforeRemoval(TreeMapNode $node): void
{
while ($node !== $this->root && $node->isBlack()) {
if (null === $node->parent) {
break;
}
if ($node === $node->parent->left) {
$sibling = $node->parent->right;
if (null !== $sibling && $sibling->isRed()) {
$sibling->setBlack();
$node->parent->setRed();
$this->rotateLeft($node->parent);
$sibling = $node->parent->right;
}
if (null === $sibling
|| (null === $sibling->left || $sibling->left->isBlack())
&& (null === $sibling->right || $sibling->right->isBlack())) {
if (null !== $sibling) {
$sibling->setRed();
}
$node = $node->parent;
} else {
if (null === $sibling->right || $sibling->right->isBlack()) {
if (null !== $sibling->left) {
$sibling->left->setBlack();
}
$sibling->setRed();
$this->rotateRight($sibling);
$sibling = $node->parent->right;
}
if (null !== $sibling) {
$sibling->color = $node->parent->color;
$node->parent->setBlack();
if (null !== $sibling->right) {
$sibling->right->setBlack();
}
$this->rotateLeft($node->parent);
}
$node = $this->root;
}
} else {
$sibling = $node->parent->left;
if (null !== $sibling && $sibling->isRed()) {
$sibling->setBlack();
$node->parent->setRed();
$this->rotateRight($node->parent);
$sibling = $node->parent->left;
}
if (null === $sibling
|| (null === $sibling->right || $sibling->right->isBlack())
&& (null === $sibling->left || $sibling->left->isBlack())) {
if (null !== $sibling) {
$sibling->setRed();
}
$node = $node->parent;
} else {
if (null === $sibling->left || $sibling->left->isBlack()) {
if (null !== $sibling->right) {
$sibling->right->setBlack();
}
$sibling->setRed();
$this->rotateLeft($sibling);
$sibling = $node->parent->left;
}
if (null !== $sibling) {
$sibling->color = $node->parent->color;
$node->parent->setBlack();
if (null !== $sibling->left) {
$sibling->left->setBlack();
}
$this->rotateRight($node->parent);
}
$node = $this->root;
}
}
}
$node->setBlack();
}

private function minimum(TreeMapNode $node): TreeMapNode
{
while (null !== $node->left) {
$node = $node->left;
}

return $node;
}
}
6 changes: 4 additions & 2 deletions src/Queue/ArrayDeque.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@
*
* @category Queues
*
* @implements Queue<mixed>
* @author Walmir Silva <walmir.silva@kariricode.org>
* @license MIT
*
* @see https://kariricode.org/
*/

class ArrayDeque extends CircularArrayQueue implements Deque
{
public function addFirst(mixed $element): void
Expand Down
1 change: 0 additions & 1 deletion src/Queue/ArrayQueue.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
*
* @see https://kariricode.org/
*/

class ArrayQueue extends CircularArrayQueue implements Queue
{
// No additional methods required, uses methods from CircularArrayQueue
Expand Down
3 changes: 2 additions & 1 deletion src/Queue/CircularArrayQueue.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public function __construct(int $initialCapacity = 16)

public function isEmpty(): bool
{
return $this->size === 0;
return 0 === $this->size;
}

public function size(): int
Expand Down Expand Up @@ -94,6 +94,7 @@ public function getItems(): array
for ($i = 0; $i < $this->size; ++$i) {
$items[] = $this->elements[($this->front + $i) % $this->capacity];
}

return $items;
}
}
Empty file removed src/Tree/TreeMap.php
Empty file.
Loading

0 comments on commit 397d36b

Please sign in to comment.