Skip to content

Commit 397d36b

Browse files
authored
Merge pull request #7 from KaririCode-Framework/develop
Implement TreeMap and TreeMapNode with comprehensive test coverage
2 parents 54c81cf + 9523292 commit 397d36b

8 files changed

+916
-4
lines changed

src/Map/TreeMap.php

Lines changed: 358 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,358 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace KaririCode\DataStructure\Map;
6+
7+
use KaririCode\Contract\DataStructure\Map;
8+
use KaririCode\DataStructure\TreeMapNode;
9+
10+
/**
11+
* TreeMap implementation.
12+
*
13+
* This class implements a map using a self-balancing binary search tree (Red-Black Tree).
14+
* It provides O(log n) time complexity for put, get, and remove operations.
15+
*
16+
* @category Maps
17+
*
18+
* @author Walmir Silva <walmir.silva@kariricode.org>
19+
* @license MIT
20+
*
21+
* @see https://kariricode.org/
22+
*/
23+
class TreeMap implements Map
24+
{
25+
private ?TreeMapNode $root = null;
26+
27+
public function put(mixed $key, mixed $value): void
28+
{
29+
$newNode = new TreeMapNode($key, $value);
30+
if (null === $this->root) {
31+
$this->root = $newNode;
32+
$this->root->setBlack();
33+
} else {
34+
$this->insertNode($newNode);
35+
$this->balanceAfterInsertion($newNode);
36+
}
37+
}
38+
39+
public function get(mixed $key): mixed
40+
{
41+
return $this->findNode($key)?->value;
42+
}
43+
44+
public function remove(mixed $key): bool
45+
{
46+
$node = $this->findNode($key);
47+
if (null === $node) {
48+
return false;
49+
}
50+
$this->deleteNode($node);
51+
52+
return true;
53+
}
54+
55+
private function insertNode(TreeMapNode $newNode): void
56+
{
57+
$current = $this->root;
58+
$parent = null;
59+
while (null !== $current) {
60+
$parent = $current;
61+
if ($newNode->key < $current->key) {
62+
$current = $current->left;
63+
} elseif ($newNode->key > $current->key) {
64+
$current = $current->right;
65+
} else {
66+
// Key already exists, update the value
67+
$current->value = $newNode->value;
68+
69+
return;
70+
}
71+
}
72+
73+
$newNode->parent = $parent;
74+
if ($newNode->key < $parent->key) {
75+
$parent->left = $newNode;
76+
} else {
77+
$parent->right = $newNode;
78+
}
79+
$this->balanceAfterInsertion($newNode);
80+
}
81+
82+
private function balanceAfterInsertion(TreeMapNode $node): void
83+
{
84+
while ($node !== $this->root && null !== $node->parent && $node->parent->isRed()) {
85+
if ($node->parent === $node->parent->parent->left) {
86+
$uncle = $node->parent->parent->right;
87+
if (null !== $uncle && $uncle->isRed()) {
88+
$node->parent->setBlack();
89+
$uncle->setBlack();
90+
if (null !== $node->parent->parent) {
91+
$node->parent->parent->setRed();
92+
$node = $node->parent->parent;
93+
}
94+
} else {
95+
if ($node === $node->parent->right) {
96+
$node = $node->parent;
97+
$this->rotateLeft($node);
98+
}
99+
if (null !== $node->parent) {
100+
$node->parent->setBlack();
101+
if (null !== $node->parent->parent) {
102+
$node->parent->parent->setRed();
103+
$this->rotateRight($node->parent->parent);
104+
}
105+
}
106+
}
107+
} else {
108+
$uncle = $node->parent->parent->left;
109+
if (null !== $uncle && $uncle->isRed()) {
110+
$node->parent->setBlack();
111+
$uncle->setBlack();
112+
if (null !== $node->parent->parent) {
113+
$node->parent->parent->setRed();
114+
$node = $node->parent->parent;
115+
}
116+
} else {
117+
if ($node === $node->parent->left) {
118+
$node = $node->parent;
119+
$this->rotateRight($node);
120+
}
121+
if (null !== $node->parent) {
122+
$node->parent->setBlack();
123+
if (null !== $node->parent->parent) {
124+
$node->parent->parent->setRed();
125+
$this->rotateLeft($node->parent->parent);
126+
}
127+
}
128+
}
129+
}
130+
}
131+
$this->root->setBlack();
132+
}
133+
134+
private function rotateLeft(TreeMapNode $node): void
135+
{
136+
$rightChild = $node->right;
137+
$node->setRight($rightChild->left);
138+
if (null !== $rightChild->left) {
139+
$rightChild->left->parent = $node;
140+
}
141+
$rightChild->parent = $node->parent;
142+
if (null === $node->parent) {
143+
$this->root = $rightChild;
144+
} elseif ($node === $node->parent->left) {
145+
$node->parent->left = $rightChild;
146+
} else {
147+
$node->parent->right = $rightChild;
148+
}
149+
$rightChild->left = $node;
150+
$node->parent = $rightChild;
151+
}
152+
153+
private function rotateRight(TreeMapNode $node): void
154+
{
155+
$leftChild = $node->left;
156+
$node->setLeft($leftChild->right);
157+
if (null !== $leftChild->right) {
158+
$leftChild->right->parent = $node;
159+
}
160+
$leftChild->parent = $node->parent;
161+
if (null === $node->parent) {
162+
$this->root = $leftChild;
163+
} elseif ($node === $node->parent->right) {
164+
$node->parent->right = $leftChild;
165+
} else {
166+
$node->parent->left = $leftChild;
167+
}
168+
$leftChild->right = $node;
169+
$node->parent = $leftChild;
170+
}
171+
172+
private function findNode(mixed $key): ?TreeMapNode
173+
{
174+
$current = $this->root;
175+
while (null !== $current) {
176+
if ($key === $current->key) {
177+
return $current;
178+
}
179+
$current = $key < $current->key ? $current->left : $current->right;
180+
}
181+
182+
return null;
183+
}
184+
185+
private function deleteNode(TreeMapNode $node): void
186+
{
187+
$replacementNode = $this->getReplacementNode($node);
188+
$needsBalancing = $node->isBlack() && (null === $replacementNode || $replacementNode->isBlack());
189+
190+
if (null === $replacementNode) {
191+
if ($node === $this->root) {
192+
$this->root = null;
193+
} else {
194+
if ($needsBalancing) {
195+
$this->balanceBeforeRemoval($node);
196+
}
197+
$node->removeFromParent();
198+
}
199+
200+
return;
201+
}
202+
203+
if (null === $node->left || null === $node->right) {
204+
if ($node === $this->root) {
205+
$this->root = $replacementNode;
206+
$replacementNode->setBlack();
207+
$replacementNode->parent = null;
208+
} else {
209+
$node->replaceWith($replacementNode);
210+
if ($needsBalancing) {
211+
$this->balanceBeforeRemoval($replacementNode);
212+
}
213+
}
214+
215+
return;
216+
}
217+
218+
$successor = $this->minimum($node->right);
219+
$originalColor = $successor->isBlack();
220+
$node->key = $successor->key;
221+
$node->value = $successor->value;
222+
$replacementNode = $successor->right;
223+
224+
if ($successor->parent === $node) {
225+
if (null !== $replacementNode) {
226+
$replacementNode->parent = $successor;
227+
}
228+
} else {
229+
$this->transplant($successor, $successor->right);
230+
$successor->right = $node->right;
231+
if (null !== $successor->right) {
232+
$successor->right->parent = $successor;
233+
}
234+
}
235+
236+
$this->transplant($node, $successor);
237+
$successor->left = $node->left;
238+
if (null !== $successor->left) {
239+
$successor->left->parent = $successor;
240+
}
241+
$successor->color = $node->color;
242+
243+
if (TreeMapNode::BLACK === $originalColor && null !== $replacementNode) {
244+
$this->balanceBeforeRemoval($replacementNode);
245+
}
246+
}
247+
248+
private function transplant(TreeMapNode $u, ?TreeMapNode $v): void
249+
{
250+
if (null === $u->parent) {
251+
$this->root = $v;
252+
} elseif ($u === $u->parent->left) {
253+
$u->parent->left = $v;
254+
} else {
255+
$u->parent->right = $v;
256+
}
257+
if (null !== $v) {
258+
$v->parent = $u->parent;
259+
}
260+
}
261+
262+
private function getReplacementNode(TreeMapNode $node): ?TreeMapNode
263+
{
264+
if (null !== $node->left && null !== $node->right) {
265+
return $this->minimum($node->right);
266+
}
267+
268+
return $node->left ?? $node->right;
269+
}
270+
271+
private function balanceBeforeRemoval(TreeMapNode $node): void
272+
{
273+
while ($node !== $this->root && $node->isBlack()) {
274+
if (null === $node->parent) {
275+
break;
276+
}
277+
if ($node === $node->parent->left) {
278+
$sibling = $node->parent->right;
279+
if (null !== $sibling && $sibling->isRed()) {
280+
$sibling->setBlack();
281+
$node->parent->setRed();
282+
$this->rotateLeft($node->parent);
283+
$sibling = $node->parent->right;
284+
}
285+
if (null === $sibling
286+
|| (null === $sibling->left || $sibling->left->isBlack())
287+
&& (null === $sibling->right || $sibling->right->isBlack())) {
288+
if (null !== $sibling) {
289+
$sibling->setRed();
290+
}
291+
$node = $node->parent;
292+
} else {
293+
if (null === $sibling->right || $sibling->right->isBlack()) {
294+
if (null !== $sibling->left) {
295+
$sibling->left->setBlack();
296+
}
297+
$sibling->setRed();
298+
$this->rotateRight($sibling);
299+
$sibling = $node->parent->right;
300+
}
301+
if (null !== $sibling) {
302+
$sibling->color = $node->parent->color;
303+
$node->parent->setBlack();
304+
if (null !== $sibling->right) {
305+
$sibling->right->setBlack();
306+
}
307+
$this->rotateLeft($node->parent);
308+
}
309+
$node = $this->root;
310+
}
311+
} else {
312+
$sibling = $node->parent->left;
313+
if (null !== $sibling && $sibling->isRed()) {
314+
$sibling->setBlack();
315+
$node->parent->setRed();
316+
$this->rotateRight($node->parent);
317+
$sibling = $node->parent->left;
318+
}
319+
if (null === $sibling
320+
|| (null === $sibling->right || $sibling->right->isBlack())
321+
&& (null === $sibling->left || $sibling->left->isBlack())) {
322+
if (null !== $sibling) {
323+
$sibling->setRed();
324+
}
325+
$node = $node->parent;
326+
} else {
327+
if (null === $sibling->left || $sibling->left->isBlack()) {
328+
if (null !== $sibling->right) {
329+
$sibling->right->setBlack();
330+
}
331+
$sibling->setRed();
332+
$this->rotateLeft($sibling);
333+
$sibling = $node->parent->left;
334+
}
335+
if (null !== $sibling) {
336+
$sibling->color = $node->parent->color;
337+
$node->parent->setBlack();
338+
if (null !== $sibling->left) {
339+
$sibling->left->setBlack();
340+
}
341+
$this->rotateRight($node->parent);
342+
}
343+
$node = $this->root;
344+
}
345+
}
346+
}
347+
$node->setBlack();
348+
}
349+
350+
private function minimum(TreeMapNode $node): TreeMapNode
351+
{
352+
while (null !== $node->left) {
353+
$node = $node->left;
354+
}
355+
356+
return $node;
357+
}
358+
}

src/Queue/ArrayDeque.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515
*
1616
* @category Queues
1717
*
18-
* @implements Queue<mixed>
18+
* @author Walmir Silva <walmir.silva@kariricode.org>
19+
* @license MIT
20+
*
21+
* @see https://kariricode.org/
1922
*/
20-
2123
class ArrayDeque extends CircularArrayQueue implements Deque
2224
{
2325
public function addFirst(mixed $element): void

src/Queue/ArrayQueue.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
*
2020
* @see https://kariricode.org/
2121
*/
22-
2322
class ArrayQueue extends CircularArrayQueue implements Queue
2423
{
2524
// No additional methods required, uses methods from CircularArrayQueue

src/Queue/CircularArrayQueue.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public function __construct(int $initialCapacity = 16)
3333

3434
public function isEmpty(): bool
3535
{
36-
return $this->size === 0;
36+
return 0 === $this->size;
3737
}
3838

3939
public function size(): int
@@ -94,6 +94,7 @@ public function getItems(): array
9494
for ($i = 0; $i < $this->size; ++$i) {
9595
$items[] = $this->elements[($this->front + $i) % $this->capacity];
9696
}
97+
9798
return $items;
9899
}
99100
}

src/Tree/TreeMap.php

Whitespace-only changes.

0 commit comments

Comments
 (0)