mirror of
https://github.com/Karaka-Management/phpOMS.git
synced 2026-01-11 09:48: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,
|
||||
'dest' => 'test_base_owns_one_self',
|
||||
],
|
||||
]; /**
|
||||
];
|
||||
|
||||
/**
|
||||
* Has many relation.
|
||||
*
|
||||
* @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