diff --git a/src/IntervalTree.php b/src/IntervalTree.php index 08c2704..d2aafce 100644 --- a/src/IntervalTree.php +++ b/src/IntervalTree.php @@ -290,7 +290,7 @@ private function deleteFixup(Node $fixNode): void $brotherNode = $currentNode->getParent()->getRight(); } - if ($brotherNode->getLeft()->getColor()->isBlack()) { + if ($brotherNode->getLeft()->getColor()->isBlack() && $brotherNode->getRight()->getColor()->isBlack()) { $brotherNode->setColor(NodeColor::red()); $currentNode = $currentNode->getParent(); } else { @@ -298,6 +298,7 @@ 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()); @@ -313,7 +314,7 @@ private function deleteFixup(Node $fixNode): void $this->rotateRight($currentNode->getParent()); $brotherNode = $currentNode->getParent()->getLeft(); } - if ($brotherNode->getRight()->getColor()->isBlack()) { + if ($brotherNode->getRight()->getColor()->isBlack() && $brotherNode->getLeft()->getColor()->isBlack()) { $brotherNode->setColor(NodeColor::red()); $currentNode = $currentNode->getParent(); } else { @@ -321,6 +322,7 @@ 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()); @@ -473,7 +475,7 @@ private function rotateRight(Node $y): void $x = $y->getParent(); if ($x !== null && $x !== $this->nilNode) { - $y->updateMax(); + $x->updateMax(); } } diff --git a/tests/IntervalTreeRandomizedTest.php b/tests/IntervalTreeRandomizedTest.php new file mode 100644 index 0000000..e573ca3 --- /dev/null +++ b/tests/IntervalTreeRandomizedTest.php @@ -0,0 +1,77 @@ + + */ + public function provideSeeds(): array + { + $seeds = []; + for ($seed = 1; $seed <= 50; $seed++) { + $seeds["seed $seed"] = [$seed]; + } + return $seeds; + } + + /** + * @dataProvider provideSeeds + */ + public function testRandomInsertRemoveMatchesBruteForce(int $seed): void + { + mt_srand($seed); + /** @var IntervalTree $tree */ + $tree = new IntervalTree(); + /** @var array $live */ + $live = []; + for ($op = 1; $op <= 300; $op++) { + if (count($live) < 2 || mt_rand(0, 2) > 0) { + $low = mt_rand(0, 1000) * 1000 + $op; + $high = $low + mt_rand(0, 30000); + $tree->insert(new NumericInterval($low, $high), $op); + $live[$op] = [$low, $high]; + } else { + $value = array_rand($live); + [$low, $high] = $live[$value]; + self::assertTrue($tree->remove(new NumericInterval($low, $high), $value)); + unset($live[$value]); + } + + $queryLow = mt_rand(0, 1030000); + $queryHigh = $queryLow + mt_rand(0, 20000); + $expected = []; + foreach ($live as $value => [$low, $high]) { + if (!($high < $queryLow || $queryHigh < $low)) { + $expected[] = $value; + } + } + $actual = []; + foreach ($tree->findIntersections(new NumericInterval($queryLow, $queryHigh)) as $pair) { + $actual[] = $pair->getValue(); + } + sort($expected); + sort($actual); + self::assertSame($expected, $actual, "seed $seed, operation $op"); + self::assertSame(count($live), $tree->getSize()); + } + } +}