-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
208 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,14 +10,14 @@ | |
use LogicException; | ||
|
||
/** | ||
* ShadowNode | ||
* Shadow node used internally when building materialized path trees. | ||
* | ||
* @author Andrej Rypak <[email protected]> | ||
*/ | ||
final class ShadowNode extends Node implements MovableNodeContract | ||
{ | ||
public function __construct( | ||
?TreeNodeContract $node, | ||
?MovableNodeContract $node, | ||
?ShadowNode $parent = null, | ||
) { | ||
parent::__construct( | ||
|
@@ -50,7 +50,7 @@ public function reconstructRealTree(): ?TreeNodeContract | |
return $realNode; | ||
} | ||
|
||
public function realNode(): ?TreeNodeContract | ||
public function realNode(): ?MovableNodeContract | ||
{ | ||
return $this->data(); | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Dakujem\Oliva\Recursive; | ||
|
||
use Dakujem\Oliva\MovableNodeContract; | ||
use Dakujem\Oliva\TreeNodeContract; | ||
use LogicException; | ||
|
||
/** | ||
* Recursive tree builder. | ||
* Builds trees from flat data collections. | ||
* Each item of a collection must contain self reference (an ID) and a reference to its parent. | ||
* | ||
* Example for collections containing items with `id` and `parent` props, the root being the node with `null` parent: | ||
* ``` | ||
* $root = (new TreeBuilder())->build( | ||
* $myItemCollection, | ||
* fn(MyItem $item) => new Node($item), | ||
* TreeBuilder::prop('id'), | ||
* TreeBuilder::prop('parent'), | ||
* ); | ||
* ``` | ||
* | ||
* @author Andrej Rypak <[email protected]> | ||
*/ | ||
final class TreeBuilder | ||
{ | ||
public static function prop(string $name): callable | ||
{ | ||
return fn(object $item) => $item->{$name} ?? null; | ||
} | ||
|
||
public static function attr(string $name): callable | ||
{ | ||
return fn(array $item) => $item[$name] ?? null; | ||
} | ||
|
||
public function build( | ||
iterable $input, | ||
callable $node, | ||
callable $self, | ||
callable $parent, | ||
string|int|null $root = null, | ||
): TreeNodeContract { | ||
[$root] = $this->processData( | ||
input: $input, | ||
nodeFactory: $node, | ||
selfRefExtractor: $self, | ||
parentRefExtractor: $parent, | ||
rootRef: $root, | ||
); | ||
return $root; | ||
} | ||
|
||
private function processData( | ||
iterable $input, | ||
callable $nodeFactory, | ||
callable $selfRefExtractor, | ||
callable $parentRefExtractor, | ||
string|int|null $rootRef = null, | ||
): array { | ||
/** @var array<string|int, array<int, string|int>> $childRegister */ | ||
$childRegister = []; | ||
/** @var array<string|int, MovableNodeContract> $nodeRegister */ | ||
$nodeRegister = []; | ||
// $root = null; | ||
foreach ($input as $inputIndex => $data) { | ||
// Create a node using the provided factory. | ||
$node = $nodeFactory($data, $inputIndex); | ||
|
||
// Check for consistency. | ||
if (!$node instanceof MovableNodeContract) { | ||
// TODO improve exceptions | ||
throw new LogicException('The node factory must return a movable node instance.'); | ||
} | ||
|
||
$self = $selfRefExtractor($data, $inputIndex, $node); | ||
$parent = $parentRefExtractor($data, $inputIndex, $node); | ||
|
||
if (isset($nodeRegister[$self])) { | ||
// TODO improve exceptions | ||
throw new LogicException('Duplicate node reference: ' . $self); | ||
} | ||
$nodeRegister[$self] = $node; | ||
|
||
// No parent, when this node is the root. | ||
if ($rootRef === $self) { | ||
continue; | ||
} | ||
|
||
if (!isset($childRegister[$parent])) { | ||
$childRegister[$parent] = []; | ||
} | ||
$childRegister[$parent][] = $self; | ||
} | ||
$this->connectNode( | ||
$nodeRegister, | ||
$childRegister, | ||
$rootRef, | ||
); | ||
|
||
return [ | ||
$nodeRegister[$rootRef], | ||
$nodeRegister, | ||
$childRegister, | ||
]; | ||
} | ||
|
||
/** | ||
* @param array<string|int, MovableNodeContract> $nodeRegister | ||
* @param array<string|int, array<int, string|int>> $childRegister | ||
*/ | ||
private function connectNode(array $nodeRegister, array $childRegister, string|int|null $ref): void | ||
{ | ||
$parent = $nodeRegister[$ref]; | ||
foreach ($childRegister[$ref] ?? [] as $childRef) { | ||
$child = $nodeRegister[$childRef]; | ||
$child->setParent($parent); | ||
$parent->addChild($child); | ||
$this->connectNode( | ||
$nodeRegister, | ||
$childRegister, | ||
$childRef, | ||
); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
use Dakujem\Oliva\Iterator\Data; | ||
use Dakujem\Oliva\Node; | ||
use Dakujem\Oliva\Recursive\TreeBuilder; | ||
use Tester\Environment; | ||
|
||
require_once __DIR__ . '/../vendor/autoload.php'; | ||
Environment::setup(); | ||
|
||
class Item | ||
{ | ||
public function __construct( | ||
public int $id, | ||
public ?int $parent, | ||
) { | ||
} | ||
} | ||
|
||
$data = [ | ||
new Item(1, 2), | ||
new Item(2, 4), | ||
new Item(3, 4), | ||
new Item(4, null), | ||
new Item(5, 4), | ||
new Item(77, 42), | ||
new Item(8, 7), | ||
new Item(6, 5), | ||
]; | ||
|
||
$builder = new TreeBuilder(); | ||
|
||
$tree = $builder->build( | ||
input: Data::nullFirst($data), | ||
node: fn(?Item $item) => new Node($item), | ||
self: fn(?Item $item) => $item?->id, | ||
parent: fn(?Item $item) => $item?->parent, | ||
); | ||
|
||
xdebug_break(); | ||
$foo = 1; |