From 9421f084c5e3935be519891eaaa3dd404b34bc8e Mon Sep 17 00:00:00 2001 From: Akhmetov Daniil Date: Tue, 18 Jan 2022 21:07:02 +0500 Subject: [PATCH 1/3] feature: add generic types support (using phpstan and psalm) --- phpstan.neon.dist | 2 +- src/Interval/DateTimeInterval.php | 38 ++++++----- src/Interval/IntervalInterface.php | 61 ++++++++++++----- src/Interval/NumericInterval.php | 42 +++++++----- src/IntervalTree.php | 76 ++++++++++++++++----- src/Node.php | 75 ++++++++++++++++++-- src/Pair.php | 17 +++-- tests/Benchmark/CountIntersectionsBench.php | 6 +- tests/Benchmark/GenerateIntervalTrait.php | 8 ++- tests/Benchmark/HasIntersectionBench.php | 6 +- tests/Interval/DateTimeIntervalTest.php | 1 + tests/Interval/NumericIntervalTest.php | 1 + tests/IntervalTreeTest.php | 3 +- tests/PairTest.php | 2 +- 14 files changed, 247 insertions(+), 91 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index e31ab91..e86e598 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -2,4 +2,4 @@ parameters: level: 7 paths: - src - - tests \ No newline at end of file + - tests diff --git a/src/Interval/DateTimeInterval.php b/src/Interval/DateTimeInterval.php index 9dc8d43..d8a8c0a 100644 --- a/src/Interval/DateTimeInterval.php +++ b/src/Interval/DateTimeInterval.php @@ -9,22 +9,26 @@ use function count; +/** + * @template TPoint of DateTimeInterface + * @implements IntervalInterface + */ final class DateTimeInterval implements IntervalInterface { /** - * @var DateTimeInterface + * @var TPoint */ private $low; /** - * @var DateTimeInterface + * @var TPoint */ private $high; /** * DateTimeInterval constructor - * @param DateTimeInterface $low - * @param DateTimeInterface $high + * @param TPoint $low + * @param TPoint $high */ public function __construct($low, $high) { @@ -37,10 +41,12 @@ public function __construct($low, $high) } /** - * @param DateTimeInterface[] $interval - * @return DateTimeInterval + * @phpstan-ignore-next-line + * @psalm-template TPoint of DateTimeInterface + * @param TPoint[] $interval + * @return IntervalInterface */ - public static function fromArray($interval): DateTimeInterval + public static function fromArray(array $interval): IntervalInterface { if (count($interval) !== 2) { throw new InvalidArgumentException('Wrong interval array'); @@ -59,20 +65,20 @@ public function getHigh(): DateTimeInterface } /** - * @param DateTimeInterval $otherInterval + * @param IntervalInterface $otherInterval * @return bool */ - public function equalTo($otherInterval): bool + public function equalTo(IntervalInterface $otherInterval): bool { return $this->getLow()->getTimestamp() === $otherInterval->getLow()->getTimestamp() && $this->getHigh()->getTimestamp() === $otherInterval->getHigh()->getTimestamp(); } /** - * @param DateTimeInterval $otherInterval + * @param IntervalInterface $otherInterval * @return bool */ - public function lessThan($otherInterval): bool + public function lessThan(IntervalInterface $otherInterval): bool { return $this->getLow()->getTimestamp() < $otherInterval->getLow()->getTimestamp() || ( @@ -82,19 +88,19 @@ public function lessThan($otherInterval): bool } /** - * @param DateTimeInterval $otherInterval + * @param IntervalInterface $otherInterval * @return bool */ - public function intersect($otherInterval): bool + public function intersect(IntervalInterface $otherInterval): bool { return !($this->getHigh() < $otherInterval->getLow() || $otherInterval->getHigh() < $this->getLow()); } /** - * @param DateTimeInterval $otherInterval - * @return DateTimeInterval + * @param IntervalInterface $otherInterval + * @return IntervalInterface */ - public function merge($otherInterval): DateTimeInterval + public function merge(IntervalInterface $otherInterval): IntervalInterface { return new DateTimeInterval( min($this->getLow(), $otherInterval->getLow()), diff --git a/src/Interval/IntervalInterface.php b/src/Interval/IntervalInterface.php index b3185b5..10abafe 100644 --- a/src/Interval/IntervalInterface.php +++ b/src/Interval/IntervalInterface.php @@ -4,29 +4,56 @@ namespace Danon\IntervalTree\Interval; +/** + * @template TPoint + */ interface IntervalInterface { - // @phpstan-ignore-next-line + /** + * @param TPoint $low + * @param TPoint $high + */ public function __construct($low, $high); - // @phpstan-ignore-next-line - public static function fromArray($interval); - - // @phpstan-ignore-next-line + /** + * @phpstan-ignore-next-line + * @psalm-template TPoint + * @param TPoint[] $interval + * @return IntervalInterface + */ + public static function fromArray(array $interval): IntervalInterface; + + /** + * @return TPoint + */ public function getLow(); - // @phpstan-ignore-next-line + /** + * @return TPoint + */ public function getHigh(); - // @phpstan-ignore-next-line - public function equalTo($otherInterval): bool; - - // @phpstan-ignore-next-line - public function lessThan($otherInterval): bool; - - // @phpstan-ignore-next-line - public function intersect($otherInterval): bool; - - // @phpstan-ignore-next-line - public function merge($otherInterval); + /** + * @param IntervalInterface $otherInterval + * @return bool + */ + public function equalTo(IntervalInterface $otherInterval): bool; + + /** + * @param IntervalInterface $otherInterval + * @return bool + */ + public function lessThan(IntervalInterface $otherInterval): bool; + + /** + * @param IntervalInterface $otherInterval + * @return bool + */ + public function intersect(IntervalInterface $otherInterval): bool; + + /** + * @param IntervalInterface $otherInterval + * @return IntervalInterface + */ + public function merge(IntervalInterface $otherInterval): IntervalInterface; } diff --git a/src/Interval/NumericInterval.php b/src/Interval/NumericInterval.php index 22606d2..4153543 100644 --- a/src/Interval/NumericInterval.php +++ b/src/Interval/NumericInterval.php @@ -8,22 +8,26 @@ use function count; +/** + * @template TPoint of int|float + * @implements IntervalInterface + */ final class NumericInterval implements IntervalInterface { /** - * @var int|float + * @var TPoint */ private $low; /** - * @var int|float + * @var TPoint */ private $high; /** * NumericInterval constructor - * @param int|float $low - * @param int|float $high + * @param TPoint $low + * @param TPoint $high */ public function __construct($low, $high) { @@ -36,10 +40,12 @@ public function __construct($low, $high) } /** - * @param int[] $interval - * @return NumericInterval + * @phpstan-ignore-next-line + * @psalm-template TPoint of int|float + * @param TPoint[] $interval + * @return IntervalInterface */ - public static function fromArray($interval): NumericInterval + public static function fromArray(array $interval): IntervalInterface { if (count($interval) !== 2) { throw new InvalidArgumentException('Wrong interval array'); @@ -48,7 +54,7 @@ public static function fromArray($interval): NumericInterval } /** - * @return int|float + * @return TPoint */ public function getLow() { @@ -56,7 +62,7 @@ public function getLow() } /** - * @return int|float + * @return TPoint */ public function getHigh() { @@ -64,38 +70,38 @@ public function getHigh() } /** - * @param NumericInterval $otherInterval + * @param IntervalInterface $otherInterval * @return bool */ - public function equalTo($otherInterval): bool + public function equalTo(IntervalInterface $otherInterval): bool { return $this->getLow() === $otherInterval->getLow() && $this->getHigh() === $otherInterval->getHigh(); } /** - * @param NumericInterval $otherInterval + * @param IntervalInterface $otherInterval * @return bool */ - public function lessThan($otherInterval): bool + public function lessThan(IntervalInterface $otherInterval): bool { return $this->getLow() < $otherInterval->getLow() || ($this->getLow() === $otherInterval->getLow() && $this->getHigh() < $otherInterval->getHigh()); } /** - * @param NumericInterval $otherInterval + * @param IntervalInterface $otherInterval * @return bool */ - public function intersect($otherInterval): bool + public function intersect(IntervalInterface $otherInterval): bool { return !($this->getHigh() < $otherInterval->getLow() || $otherInterval->getHigh() < $this->getLow()); } /** - * @param NumericInterval $otherInterval - * @return NumericInterval + * @param IntervalInterface $otherInterval + * @return IntervalInterface */ - public function merge($otherInterval): NumericInterval + public function merge(IntervalInterface $otherInterval): IntervalInterface { return new NumericInterval( min($this->getLow(), $otherInterval->getLow()), diff --git a/src/IntervalTree.php b/src/IntervalTree.php index 8ceb0b6..899dd43 100644 --- a/src/IntervalTree.php +++ b/src/IntervalTree.php @@ -7,12 +7,16 @@ use Danon\IntervalTree\Interval\IntervalInterface; use Iterator; +/** + * @template TPoint + * @template TValue + */ final class IntervalTree { - /** @var Node*/ + /** @var Node */ private $root; - /** @var Node */ + /** @var Node */ private $nilNode; /** @@ -45,9 +49,8 @@ public function isEmpty(): bool /** * Find pairs which intervals intersect with given interval - * - * @param IntervalInterface $interval - * @return Iterator + * @param IntervalInterface $interval + * @return Iterator> */ public function findIntersections(IntervalInterface $interval): Iterator { @@ -60,7 +63,7 @@ public function findIntersections(IntervalInterface $interval): Iterator /** * Returns true if interval has at least one intersection in tree * - * @param IntervalInterface $interval + * @param IntervalInterface $interval * @return bool */ public function hasIntersection(IntervalInterface $interval): bool @@ -72,7 +75,7 @@ public function hasIntersection(IntervalInterface $interval): bool /** * Count intervals that has intersections * - * @param IntervalInterface $interval + * @param IntervalInterface $interval * @return int */ public function countIntersections(IntervalInterface $interval): int @@ -84,8 +87,8 @@ public function countIntersections(IntervalInterface $interval): int /** * Insert new pair (interval + value) into interval tree * - * @param IntervalInterface $interval - * @param mixed $value + * @param IntervalInterface $interval + * @param TValue $value */ public function insert(IntervalInterface $interval, $value = null): void { @@ -103,8 +106,8 @@ public function insert(IntervalInterface $interval, $value = null): void /** * Returns true if interval and value exist in the tree * - * @param IntervalInterface $interval - * @param mixed $value + * @param IntervalInterface $interval + * @param TValue $value * @return bool */ public function exist(IntervalInterface $interval, $value): bool @@ -116,8 +119,8 @@ public function exist(IntervalInterface $interval, $value): bool /** * Remove node from tree by interval and value * - * @param IntervalInterface $interval - * @param mixed $value + * @param IntervalInterface $interval + * @param TValue $value * @return bool */ public function remove(IntervalInterface $interval, $value): bool @@ -131,6 +134,10 @@ public function remove(IntervalInterface $interval, $value): bool return false; } + /** + * @param Node $node + * @return void + */ private function recalculateMax(Node $node): void { $nodeCurrent = $node; @@ -140,6 +147,10 @@ private function recalculateMax(Node $node): void } } + /** + * @param Node $insertNode + * @return void + */ private function treeInsert(Node $insertNode): void { $currentNode = $this->root; @@ -174,7 +185,7 @@ private function treeInsert(Node $insertNode): void * And this is a single possible violation * Go upwards to the root and re-color until violation will be resolved * - * @param Node $insertNode + * @param Node $insertNode */ private function insertFixup(Node $insertNode): void { @@ -218,6 +229,10 @@ private function insertFixup(Node $insertNode): void $this->root->setColor(NodeColor::black()); } + /** + * @param Node $deleteNode + * @return void + */ private function treeDelete(Node $deleteNode): void { if ($deleteNode->getLeft() === $this->nilNode || $deleteNode->getRight() === $this->nilNode) { @@ -261,6 +276,10 @@ private function treeDelete(Node $deleteNode): void } } + /** + * @param Node $fixNode + * @return void + */ private function deleteFixup(Node $fixNode): void { $currentNode = $fixNode; @@ -324,6 +343,11 @@ private function deleteFixup(Node $fixNode): void $currentNode->setColor(NodeColor::black()); } + /** + * @param Node $node + * @param Node $searchNode + * @return Node|null + */ private function treeSearch(Node $node, Node $searchNode): ?Node { if ($node === $this->nilNode) { @@ -342,9 +366,9 @@ private function treeSearch(Node $node, Node $searchNode): ?Node } /** - * @param Node $searchNode - * @param Node|null $fromNode - * @return Iterator + * @param Node $searchNode + * @param Node|null $fromNode + * @return Iterator> */ private function treeSearchInterval(Node $searchNode, Node $fromNode = null): Iterator { @@ -360,6 +384,10 @@ private function treeSearchInterval(Node $searchNode, Node $fromNode = null): It } } + /** + * @param Node $node + * @return Node + */ private function localMinimum(Node $node): Node { $nodeMin = $node; @@ -369,6 +397,10 @@ private function localMinimum(Node $node): Node return $nodeMin; } + /** + * @param Node $node + * @return Node|null + */ private function treeSuccessor(Node $node): ?Node { if ($node->getRight() !== $this->nilNode) { @@ -385,6 +417,10 @@ private function treeSuccessor(Node $node): ?Node return $nodeSuccessor; } + /** + * @param Node $x + * @return void + */ private function rotateLeft(Node $x): void { $y = $x->getRight(); @@ -415,6 +451,10 @@ private function rotateLeft(Node $x): void } } + /** + * @param Node $y + * @return void + */ private function rotateRight(Node $y): void { $x = $y->getLeft(); @@ -447,7 +487,7 @@ private function rotateRight(Node $y): void } /** - * @return Iterator + * @return Iterator> */ public function treeWalk(): Iterator { diff --git a/src/Node.php b/src/Node.php index 51ce4d8..9d619b1 100644 --- a/src/Node.php +++ b/src/Node.php @@ -4,20 +4,24 @@ use Danon\IntervalTree\Interval\IntervalInterface; +/** + * @template TPoint + * @template TValue + */ final class Node { /** - * @var Node + * @var Node */ private $left; /** - * @var Node + * @var Node */ private $right; /** - * @var Node + * @var Node */ private $parent; @@ -27,12 +31,12 @@ final class Node private $color; /** - * @var Pair + * @var Pair */ private $pair; /** - * @var null|IntervalInterface + * @var null|IntervalInterface */ private $max; @@ -40,6 +44,13 @@ private function __construct() { } + /** + * @phpstan-ignore-next-line + * @psalm-template TPoint + * @psalm-template TValue + * @param Pair $pair + * @return static + */ public static function withPair(Pair $pair): self { $self = new self(); @@ -49,6 +60,9 @@ public static function withPair(Pair $pair): self return $self; } + /** + * @return Node + */ public static function nil(): self { $self = new self(); @@ -66,46 +80,78 @@ public function getColor(): NodeColor return $this->color; } + /** + * @return Node + */ public function getLeft(): Node { return $this->left; } + /** + * @param Node $node + * @return void + */ public function setLeft(Node $node): void { $this->left = $node; } + /** + * @return Node + */ public function getRight(): Node { return $this->right; } + /** + * @param Node $node + * @return void + */ public function setRight(Node $node): void { $this->right = $node; } + /** + * @return Node|null + */ public function getParent(): ?Node { return $this->parent; } + /** + * @param Node|null $node + * @return void + */ public function setParent(?Node $node): void { $this->parent = $node; } + /** + * @return Pair + */ public function getPair(): Pair { return $this->pair; } + /** + * @param Node $otherNode + * @return bool + */ public function lessThan(Node $otherNode): bool { return $this->getPair()->getInterval()->lessThan($otherNode->getPair()->getInterval()); } + /** + * @param Node $otherNode + * @return bool + */ public function equalTo(Node $otherNode): bool { $valueEqual = true; @@ -115,16 +161,27 @@ public function equalTo(Node $otherNode): bool return $this->getPair()->getInterval()->equalTo($otherNode->getPair()->getInterval()) && $valueEqual; } + /** + * @param Node $otherNode + * @return bool + */ public function intersect(Node $otherNode): bool { return $this->getPair()->getInterval()->intersect($otherNode->getPair()->getInterval()); } + /** + * @param Node $otherNode + * @return void + */ public function copyPairFrom(Node $otherNode): void { $this->pair = $otherNode->getPair(); } + /** + * @return void + */ public function updateMax(): void { $this->max = $this->getPair()->getInterval(); @@ -136,12 +193,20 @@ public function updateMax(): void } } + /** + * @param Node $searchNode + * @return bool + */ public function notIntersectLeftSubtree(Node $searchNode): bool { $high = $this->getLeft()->max->getHigh() ?? $this->getLeft()->getPair()->getInterval()->getHigh(); return $high < $searchNode->getPair()->getInterval()->getLow(); } + /** + * @param Node $searchNode + * @return bool + */ public function notIntersectRightSubtree(Node $searchNode): bool { $low = $this->getRight()->max->getLow() ?? $this->getRight()->getPair()->getInterval()->getLow(); diff --git a/src/Pair.php b/src/Pair.php index 8ac9483..8ba9ebd 100644 --- a/src/Pair.php +++ b/src/Pair.php @@ -6,17 +6,21 @@ use Danon\IntervalTree\Interval\IntervalInterface; +/** + * @template TPoint + * @template TValue + */ final class Pair { - /** @var IntervalInterface */ + /** @var IntervalInterface */ private $interval; - /** @var mixed */ + /** @var TValue */ private $value; /** - * @param IntervalInterface $interval - * @param mixed $value + * @param IntervalInterface $interval + * @param TValue $value */ public function __construct(IntervalInterface $interval, $value = null) { @@ -24,13 +28,16 @@ public function __construct(IntervalInterface $interval, $value = null) $this->value = $value; } + /** + * @return IntervalInterface + */ public function getInterval(): IntervalInterface { return $this->interval; } /** - * @return mixed + * @return TValue */ public function getValue() { diff --git a/tests/Benchmark/CountIntersectionsBench.php b/tests/Benchmark/CountIntersectionsBench.php index c26d6cd..eaf976a 100644 --- a/tests/Benchmark/CountIntersectionsBench.php +++ b/tests/Benchmark/CountIntersectionsBench.php @@ -4,9 +4,9 @@ namespace Danon\IntervalTree\Tests\Benchmark; -use Danon\IntervalTree\Interval\NumericInterval; use Danon\IntervalTree\IntervalTree; use PhpBench\Benchmark\Metadata\Annotations\Revs; +use Danon\IntervalTree\Interval\IntervalInterface; /** * @BeforeMethods({"init"}) @@ -20,12 +20,12 @@ class CountIntersectionsBench private const MAX_INTERVAL_OFFSET = 100; /** - * @var IntervalTree + * @var IntervalTree */ private $tree; /** - * @var NumericInterval[] + * @var IntervalInterface[] */ private $bruteForceList; diff --git a/tests/Benchmark/GenerateIntervalTrait.php b/tests/Benchmark/GenerateIntervalTrait.php index 7b81a77..91e6484 100644 --- a/tests/Benchmark/GenerateIntervalTrait.php +++ b/tests/Benchmark/GenerateIntervalTrait.php @@ -4,6 +4,7 @@ namespace Danon\IntervalTree\Tests\Benchmark; +use Danon\IntervalTree\Interval\IntervalInterface; use Danon\IntervalTree\Interval\NumericInterval; use Exception; use InvalidArgumentException; @@ -13,9 +14,9 @@ trait GenerateIntervalTrait /** * @param int $maxHigh * @param int $maxOffset - * @return NumericInterval + * @return IntervalInterface */ - private function generateInterval(int $maxHigh, int $maxOffset): NumericInterval + private function generateInterval(int $maxHigh, int $maxOffset): IntervalInterface { try { $low = random_int(0, $maxHigh); @@ -23,6 +24,7 @@ private function generateInterval(int $maxHigh, int $maxOffset): NumericInterval } catch (Exception $exception) { throw new InvalidArgumentException('Wrong interval arguments', $exception->getCode(), $exception); } - return new NumericInterval($low, $high); + + return NumericInterval::fromArray([$low, $high]); } } diff --git a/tests/Benchmark/HasIntersectionBench.php b/tests/Benchmark/HasIntersectionBench.php index 5dfe650..1e282b9 100644 --- a/tests/Benchmark/HasIntersectionBench.php +++ b/tests/Benchmark/HasIntersectionBench.php @@ -4,9 +4,9 @@ namespace Danon\IntervalTree\Tests\Benchmark; -use Danon\IntervalTree\Interval\NumericInterval; use Danon\IntervalTree\IntervalTree; use PhpBench\Benchmark\Metadata\Annotations\Revs; +use Danon\IntervalTree\Interval\IntervalInterface; /** * @BeforeMethods({"init"}) @@ -20,12 +20,12 @@ class HasIntersectionBench private const MAX_INTERVAL_OFFSET = 100; /** - * @var IntervalTree + * @var IntervalTree */ private $tree; /** - * @var NumericInterval[] + * @var IntervalInterface[] */ private $bruteForceList; diff --git a/tests/Interval/DateTimeIntervalTest.php b/tests/Interval/DateTimeIntervalTest.php index 41f99a3..3221f0b 100644 --- a/tests/Interval/DateTimeIntervalTest.php +++ b/tests/Interval/DateTimeIntervalTest.php @@ -1,4 +1,5 @@ */ private $tree; public function setUp(): void @@ -42,6 +42,7 @@ public function setUp(): void public function testFindIntersections(): void { $checkInterval = [2, 3]; + /** @var array $overlappingIntervals */ $overlappingIntervals = [[0, 2], [0, 2], [0, 3], [1, 4], [2, 3], [3, 4]]; $intersections = $this->tree->findIntersections(NumericInterval::fromArray($checkInterval)); foreach ($intersections as $index => $pair) { diff --git a/tests/PairTest.php b/tests/PairTest.php index 7f0a52e..40e0c94 100644 --- a/tests/PairTest.php +++ b/tests/PairTest.php @@ -15,7 +15,7 @@ final class PairTest extends TestCase private const EXAMPLE_VALUE = '1_5'; /** - * @var Pair + * @var Pair */ protected $pair; From 54e2f57c0cdca8c1146d096044ff97c3ba07e122 Mon Sep 17 00:00:00 2001 From: Akhmetov Daniil Date: Tue, 18 Jan 2022 21:44:17 +0500 Subject: [PATCH 2/3] fix: change benchmark revs count --- tests/Benchmark/CountIntersectionsBench.php | 4 ++-- tests/Benchmark/HasIntersectionBench.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Benchmark/CountIntersectionsBench.php b/tests/Benchmark/CountIntersectionsBench.php index eaf976a..598cc9d 100644 --- a/tests/Benchmark/CountIntersectionsBench.php +++ b/tests/Benchmark/CountIntersectionsBench.php @@ -42,7 +42,7 @@ public function init(): void } /** - * @Revs(1000) + * @Revs(10) */ public function benchTree(): void { @@ -51,7 +51,7 @@ public function benchTree(): void } /** - * @Revs(1000) + * @Revs(10) */ public function benchBruteForce(): void { diff --git a/tests/Benchmark/HasIntersectionBench.php b/tests/Benchmark/HasIntersectionBench.php index 1e282b9..fa43408 100644 --- a/tests/Benchmark/HasIntersectionBench.php +++ b/tests/Benchmark/HasIntersectionBench.php @@ -42,7 +42,7 @@ public function init(): void } /** - * @Revs(1000) + * @Revs(10) */ public function benchTree(): void { @@ -51,7 +51,7 @@ public function benchTree(): void } /** - * @Revs(1000) + * @Revs(10) */ public function benchBruteForce(): void { From 8e642bf519c33019aee06886f50fb623744c89c4 Mon Sep 17 00:00:00 2001 From: Akhmetov Daniil Date: Tue, 18 Jan 2022 21:44:58 +0500 Subject: [PATCH 3/3] fix: fix code style issues --- src/IntervalTree.php | 63 +++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 36 deletions(-) diff --git a/src/IntervalTree.php b/src/IntervalTree.php index 899dd43..08c2704 100644 --- a/src/IntervalTree.php +++ b/src/IntervalTree.php @@ -181,10 +181,6 @@ private function treeInsert(Node $insertNode): void } /** - * After insertion insert_node may have red-colored parent - * And this is a single possible violation - * Go upwards to the root and re-color until violation will be resolved - * * @param Node $insertNode */ private function insertFixup(Node $insertNode): void @@ -241,7 +237,6 @@ private function treeDelete(Node $deleteNode): void $cutNode = $this->treeSuccessor($deleteNode); } - // fix_node if single child of cut_node if ($cutNode->getLeft() !== $this->nilNode) { $fixNode = $cutNode->getLeft(); } else { @@ -258,17 +253,15 @@ private function treeDelete(Node $deleteNode): void } else { $cutNode->getParent()->setRight($fixNode); } - $cutNode->getParent()->updateMax(); // update max property of the parent + $cutNode->getParent()->updateMax(); } - $this->recalculateMax($fixNode); // update max property upward from fix_node to root + $this->recalculateMax($fixNode); - // deleteNode becomes cutNode, it means that we cannot hold reference - // to node in outer structure and we will have to delete by key, additional search need if ($cutNode !== $deleteNode) { $deleteNode->copyPairFrom($cutNode); - $deleteNode->updateMax(); // update max property of the cut node at the new place - $this->recalculateMax($deleteNode); // update max property upward from deleteNode to root + $deleteNode->updateMax(); + $this->recalculateMax($deleteNode); } if ($cutNode->getColor()->isBlack()) { @@ -305,7 +298,6 @@ private function deleteFixup(Node $fixNode): void $brotherNode->setColor(NodeColor::red()); $brotherNode->getLeft()->setColor(NodeColor::black()); $this->rotateRight($brotherNode); - $brotherNode = $currentNode->getParent()->getRight(); } $brotherNode->setColor($currentNode->getParent()->getColor()); $currentNode->getParent()->setColor(NodeColor::black()); @@ -329,7 +321,6 @@ private function deleteFixup(Node $fixNode): void $brotherNode->setColor(NodeColor::red()); $brotherNode->getRight()->setColor(NodeColor::black()); $this->rotateLeft($brotherNode); - $brotherNode = $currentNode->getParent()->getLeft(); } $brotherNode->setColor($currentNode->getParent()->getColor()); $currentNode->getParent()->setColor(NodeColor::black()); @@ -344,25 +335,25 @@ private function deleteFixup(Node $fixNode): void } /** + * @param Node $startingNode * @param Node $node - * @param Node $searchNode * @return Node|null */ - private function treeSearch(Node $node, Node $searchNode): ?Node + private function treeSearch(Node $startingNode, Node $node): ?Node { - if ($node === $this->nilNode) { + if ($startingNode === $this->nilNode) { return null; } - if ($searchNode->equalTo($node)) { - return $node; - } - - if ($searchNode->lessThan($node)) { - return $this->treeSearch($node->getLeft(), $searchNode); + if ($node->equalTo($startingNode)) { + $searchedNode = $startingNode; + } elseif ($node->lessThan($startingNode)) { + $searchedNode = $this->treeSearch($startingNode->getLeft(), $node); + } else { + $searchedNode = $this->treeSearch($startingNode->getRight(), $node); } - return $this->treeSearch($node->getRight(), $searchNode); + return $searchedNode; } /** @@ -424,22 +415,22 @@ private function treeSuccessor(Node $node): ?Node private function rotateLeft(Node $x): void { $y = $x->getRight(); - $x->setRight($y->getLeft()); // b goes to x.right + $x->setRight($y->getLeft()); if ($y->getLeft() !== $this->nilNode) { - $y->getLeft()->setParent($x); // x becomes parent of b + $y->getLeft()->setParent($x); } - $y->setParent($x->getParent()); // move parent + $y->setParent($x->getParent()); if ($x->getParent() === null) { - $this->root = $y; // y becomes root + $this->root = $y; } elseif ($x === $x->getParent()->getLeft()) { $x->getParent()->setLeft($y); } else { $x->getParent()->setRight($y); } - $y->setLeft($x); // x becomes left child of y - $x->setParent($y); // and y becomes parent of x + $y->setLeft($x); + $x->setParent($y); if ($x !== $this->nilNode) { $x->updateMax(); @@ -459,22 +450,22 @@ private function rotateRight(Node $y): void { $x = $y->getLeft(); - $y->setLeft($x->getRight()); // b goes to y.left + $y->setLeft($x->getRight()); if ($x->getRight() !== $this->nilNode) { - $x->getRight()->setParent($y); // y becomes parent of b + $x->getRight()->setParent($y); } - $x->setParent($y->getParent()); // move parent + $x->setParent($y->getParent()); - if ($y->getParent() === null) { // x becomes root + if ($y->getParent() === null) { $this->root = $x; } elseif ($y === $y->getParent()->getLeft()) { $y->getParent()->setLeft($x); } else { $y->getParent()->setRight($x); } - $x->setRight($y); // y becomes right child of x - $y->setParent($x); // and x becomes parent of y + $x->setRight($y); + $y->setParent($x); if ($y !== $this->nilNode) { $y->updateMax(); @@ -489,7 +480,7 @@ private function rotateRight(Node $y): void /** * @return Iterator> */ - public function treeWalk(): Iterator + private function treeWalk(): Iterator { if ($this->root !== null) { $stack = [$this->root];