mirror of
https://github.com/Karaka-Management/phpOMS.git
synced 2026-02-07 13:08:40 +00:00
Add heap
This commit is contained in:
parent
34cc6325da
commit
e0aa1406e2
|
|
@ -0,0 +1,227 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Orange Management
|
||||||
|
*
|
||||||
|
* PHP Version 7.4
|
||||||
|
*
|
||||||
|
* @package phpOMS\Stdlib\Base
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 1.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://orange-management.org
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Stdlib\Base;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Heap class.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Stdlib\Base
|
||||||
|
* @license OMS License 1.0
|
||||||
|
* @link https://orange-management.org
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
class Heap
|
||||||
|
{
|
||||||
|
private \Closure $compare = null;
|
||||||
|
|
||||||
|
private array $nodes = [];
|
||||||
|
|
||||||
|
public function __construct(\Closure $compare = null)
|
||||||
|
{
|
||||||
|
$this->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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -57,7 +57,9 @@ class BaseModelMapper extends DataMapperAbstract
|
||||||
'mapper' => OwnsOneModelMapper::class,
|
'mapper' => OwnsOneModelMapper::class,
|
||||||
'dest' => 'test_base_owns_one_self',
|
'dest' => 'test_base_owns_one_self',
|
||||||
],
|
],
|
||||||
]; /**
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
* Has many relation.
|
* Has many relation.
|
||||||
*
|
*
|
||||||
* @var array<string, array<string, null|string>>
|
* @var array<string, array<string, null|string>>
|
||||||
|
|
|
||||||
167
tests/Stdlib/Base/HeapTest.php
Normal file
167
tests/Stdlib/Base/HeapTest.php
Normal file
|
|
@ -0,0 +1,167 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Orange Management
|
||||||
|
*
|
||||||
|
* PHP Version 7.4
|
||||||
|
*
|
||||||
|
* @package tests
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 1.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://orange-management.org
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\tests\Stdlib\Base;
|
||||||
|
|
||||||
|
use phpOMS\Stdlib\Base\Heap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
class HeapTest extends \PHPUnit\Framework\TestCase
|
||||||
|
{
|
||||||
|
public function testPushAndPop() : void
|
||||||
|
{
|
||||||
|
$heap = new Heap();
|
||||||
|
for ($i = 0; $i < 10; ++$i) {
|
||||||
|
$heap->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());
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user