diff --git a/Stdlib/Base/Heap.php b/Stdlib/Base/Heap.php index e69de29bb..83af3ae15 100644 --- a/Stdlib/Base/Heap.php +++ b/Stdlib/Base/Heap.php @@ -0,0 +1,227 @@ +compare = $compare ?? function($a, $b) { return $a <=> $b; }; + } + + public function insort($x, $lo = 0) : void + { + $hi = \count($this->nodes); + + while ($lo < $hi) { + $mid = (int) \floor(($lo + $hi) / 2); + if ($this->compare($x, $this->node[$mid]) < 0) { + $hi = $mid; + } else { + $lo = $mid + 1; + } + } + + $this->nodes = \array_splice($this->nodes, $lo, 0, $x); + } + + public function push($item) : void + { + $this->nodes[] = $item; + $this->siftDown(0, \count($this->nodes) - 1); + } + + public function pop() + { + $last = \array_pop($this->nodes); + if (empty($this->nodes)) { + return $last; + } + + $item = $this->nodes[0]; + $this->nodes[0] = $last; + $this->siftUp(0); + + return $item; + } + + public function peek() + { + return $this->nodes[0]; + } + + public function contains($item) : bool + { + foreach ($this->nodes as $key => $node) { + if (\is_scalar($item)) { + if ($node === $item) { + return true; + } + } else { + if ($item->isEqual($node)) { + return true; + } + } + } + + return false; + } + + public function replace($new) + { + $old = $this->nodes[0]; + $this->nodes[0] = $new; + $this->siftUp(0); + + return $old; + } + + public function pushpop($item) + { + if (!empty($this->nodes) && $this->compare($this->nodes[0], $item) < 0) { + $temp = $item; + $item = $this->nodes[0]; + $this->nodes[0] = $temp; + $this->siftUp(0); + } + + return $item; + } + + public function heapify() : void + { + for ($i = (int) \floor(\count($this->nodes) / 2); $i > -1; --$i) { + $this->siftUp($i); + } + } + + public function update($item) : bool + { + $pos = null; + foreach ($this->nodes as $key => $node) { + if (\is_scalar($item)) { + if ($node === $item) { + $pos = $key; + break; + } + } else { + if ($item->isEqual($node)) { + $pos = $key; + break; + } + } + } + + if ($pos === null) { + return false; + } + + $this->siftDown(0, $pos); + $this->siftUp($pos); + + return true; + } + + public function getNLargest(int $n) : array + { + $nodes = $this->nodes; + \uasort($nodes, [$this, 'compare']); + + return \array_slice($nodes, 0, $n); + } + + public function getNSmallest(int $n): array + { + $nodes = $this->nodes; + \uasort($nodes, [$this, 'compare']); + + return \array_slice(\array_reverse($nodes), 0, $n); + } + + private function siftDown(int $start, int $pos) : void; + { + $item = $this->nodes[$pos]; + while ($pos > $start) { + $pPos = ($pos - 1) >> 1; + $parent = $this->nodes[$pPos]; + + if ($this->compare($item, $parent) < 0) { + $this->nodes[$pos] = $parent; + $pos = $pPos; + + continue; + } + + break; + } + + $this->nodes[$pos] = $item; + } + + private function siftUp(int $pos) : void + { + $ePos = \count($this->nodes); + $sPos = $pos; + $item = $this->nodes[$pos]; + $cPos = 2 * $pos + 1; + + while ($cPos < $ePos) { + $rPos = $cPos + 1; + + if ($rPos < $ePos && $this->compare($this->nodes[$cPos], $this->nodes[$rPos]) > -1) { + $cPos = $rPos; + } + + $this->nodes[$pos] = $this->nodes[$cPos]; + $pos = $cPos; + $cPos = 2 * $pos + 1; + } + + $this->nodes[$pos] = $item; + $this->siftDown($sPos, $pos); + } + + public function clear() : void + { + $this->nodes = []; + } + + public function empty() : bool + { + return empty($this->nodes); + } + + public function size() : int + { + return \count($this->nodes); + } + + public function toArray() + { + return $this->nodes; + } +} diff --git a/tests/DataStorage/Database/TestModel/BaseModelMapper.php b/tests/DataStorage/Database/TestModel/BaseModelMapper.php index 0baf99103..43398a3ea 100644 --- a/tests/DataStorage/Database/TestModel/BaseModelMapper.php +++ b/tests/DataStorage/Database/TestModel/BaseModelMapper.php @@ -57,7 +57,9 @@ class BaseModelMapper extends DataMapperAbstract 'mapper' => OwnsOneModelMapper::class, 'dest' => 'test_base_owns_one_self', ], - ]; /** + ]; + + /** * Has many relation. * * @var array> diff --git a/tests/Stdlib/Base/HeapTest.php b/tests/Stdlib/Base/HeapTest.php new file mode 100644 index 000000000..8862a6036 --- /dev/null +++ b/tests/Stdlib/Base/HeapTest.php @@ -0,0 +1,167 @@ +push(\mt_rand()); + } + + $sorted = []; + while (!$heap->empty()) { + $sorted[] = $heap->pop(); + } + + $sortedFunction = $sorted; + \sort($sortedFunction); + + self::assertEquals($sortedFunction, $sorted); + } + + public function testPushAndPopCustomComparator() : void + { + $heap = new Heap(function($a, $b) { return ($a <=> $b) * -1; }); + for ($i = 0; $i < 10; ++$i) { + $heap->push(\mt_rand()); + } + + $sorted = []; + while (!$heap->empty()) { + $sorted[] = $heap->pop(); + } + + $sortedFunction = $sorted; + \sort($sortedFunction); + + self::assertEquals(\array_reverse($sortedFunction), $sorted); + } + + public function testReplace() : void + { + $heap = new Heap(); + for ($i = 1; $i < 6; ++$i) { + $heap->push($i); + } + + self::assertEquals(1, $heap->replace(3)); + self::assertEquals([2, 3, 3, 4, 5], $heap->toArray()); + } + + public function testPushPop() : void + { + $heap = new Heap(); + for ($i = 1; $i < 6; ++$i) { + $heap->push($i); + } + + self::assertEquals(1, $heap->pushpop(6)); + self::assertEquals([2, 3, 4, 5, 6], $heap->toArray()); + } + + public function testContains(): void + { + $heap = new Heap(); + for ($i = 1; $i < 6; ++$i) { + $heap->push($i); + } + + self::assertTrue($heap->contains(1)); + self::assertTrue($heap->contains(2)); + self::assertTrue($heap->contains(3)); + self::assertTrue($heap->contains(4)); + self::assertTrue($heap->contains(5)); + self::assertFalse($heap->contains(0)); + self::assertFalse($heap->contains(6)); + } + + public function testPeek() : void + { + $heap = new Heap(); + + $heap->push(1); + self::assertEqals(1, $heap->peek()); + + $heap->push(2); + self::assertEqals(1, $heap->peek()); + + $heap->pop(); + self::assertEqals(2, $heap->peek()); + } + + public function testNSmallest() : void + { + $heap = new Heap(); + $heap->push(1); + $heap->push(3); + $heap->push(1); + $heap->push(4); + + self::assertEquals([1, 1, 3], $heap->getNSmallest(3)); + } + + public function testNLargest(): void + { + $heap = new Heap(); + $heap->push(1); + $heap->push(3); + $heap->push(1); + $heap->push(4); + $heap->push(4); + + self::assertEquals([4, 4, 3], $heap->getNLargest(3)); + } + + public function testSize(): void + { + $heap = new Heap(); + for ($i = 1; $i < 6; ++$i) { + $heap->push($i); + } + + self::assertEquals(5, $heap->size()); + } + + public function testClear(): void + { + $heap = new Heap(); + for ($i = 1; $i < 6; ++$i) { + $heap->push($i); + } + + $heap->clear(); + self::assertEquals(0, $heap->size()); + } + + public function testEmpty(): void + { + $heap = new Heap(); + self::assertTrue($heap->empty()); + + for ($i = 1; $i < 6; ++$i) { + $heap->push($i); + } + + self::assertFalse($heap->empty()); + } +} \ No newline at end of file