Skip to content

Commit

Permalink
Merge pull request #5 from drupol/ArrayObject
Browse files Browse the repository at this point in the history
Array object and many fixes
  • Loading branch information
drupol authored Dec 28, 2018
2 parents 5889010 + 478eef8 commit fb8588a
Show file tree
Hide file tree
Showing 12 changed files with 129 additions and 72 deletions.
2 changes: 1 addition & 1 deletion benchmarks/AbstractBench.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ abstract class AbstractBench
*/
public function getData()
{
return \range(1, 1000);
return \range(1, 100);
}
}
6 changes: 3 additions & 3 deletions benchmarks/DrupolPhpTreeBench.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ public function initObject()
* @Iterations(5)
* @Warmup(10)
*/
public function benchHash()
public function benchTreeAdd()
{
$this->tree = new ValueNode();
$this->tree = new ValueNode('root', 2);

foreach ($this->getData() as $value) {
$this->tree->add(new ValueNode($value));
$this->tree->add(new ValueNode($value, 2));
}
}
}
7 changes: 3 additions & 4 deletions spec/drupol/phptree/Exporter/GraphSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
namespace spec\drupol\phptree\Exporter;

use drupol\phptree\Exporter\Graph;
use drupol\phptree\Node\Node;
use drupol\phptree\Node\ValueNode;
use drupol\phptree\Traverser\BreadthFirst;
use PhpSpec\ObjectBehavior;
Expand All @@ -20,9 +19,9 @@ public function it_is_initializable()
public function it_can_generate_a_graph()
{
$root = new ValueNode('root');
$child1 = new Node();
$child2 = new Node();
$child3 = new Node();
$child1 = new ValueNode();
$child2 = new ValueNode();
$child3 = new ValueNode();
$root
->add($child1, $child2, $child3);

Expand Down
2 changes: 1 addition & 1 deletion spec/drupol/phptree/Exporter/SimpleArraySpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public function it_can_export_to_an_array()

$nodes = [];
foreach (\range('A', 'J') as $value) {
$nodes[$value] = new ValueNode($value);
$nodes[$value] = new ValueNode($value, 2);
}

$tree->add(...\array_values($nodes));
Expand Down
2 changes: 1 addition & 1 deletion spec/drupol/phptree/Exporter/TextSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public function it_can_export_to_text()

$nodes = [];
foreach (\range('A', 'J') as $value) {
$nodes[$value] = new ValueNode($value);
$nodes[$value] = new ValueNode($value, 2);
}

$tree->add(...\array_values($nodes));
Expand Down
16 changes: 1 addition & 15 deletions spec/drupol/phptree/Node/NodeSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,20 +98,6 @@ public function it_can_get_its_children()
->shouldYield(new \ArrayIterator([$node, $node]));
}

public function it_can_get_the_last_children()
{
$this
->lastChild()
->shouldReturn(null);

$node = new Node();

$this
->add($node)
->lastChild()
->shouldReturn($node);
}

public function it_can_check_if_its_a_leaf()
{
$this
Expand Down Expand Up @@ -237,7 +223,7 @@ public function it_can_get_its_depth()

$nodes = [];
foreach (\range('A', 'Z') as $v) {
$nodes[] = new \drupol\phptree\Node\ValueNode($v);
$nodes[] = new \drupol\phptree\Node\ValueNode($v, 2);
}

$tree->add(...$nodes);
Expand Down
48 changes: 47 additions & 1 deletion spec/drupol/phptree/Traverser/InOrderSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public function it_is_initializable()
$this->shouldHaveType(InOrder::class);
}

public function it_can_traverse_a_tree()
public function it_can_traverse_a_tree_of_degree2()
{
$tree = new ValueNode('root', 2);

Expand All @@ -39,4 +39,50 @@ public function it_can_traverse_a_tree()
->traverse($tree)
->shouldYield(new \ArrayIterator($nodes));
}

public function it_can_traverse_a_tree_of_degree4()
{
$tree = new ValueNode('root', 4);

foreach (\range('A', 'Z') as $key => $value) {
$nodes[$value] = new ValueNode($value, 4);
}

$tree->add(...\array_values($nodes));

$nodes['root'] = $tree;
$nodes = [
$nodes['U'],
$nodes['V'],
$nodes['E'],
$nodes['W'],
$nodes['X'],
$nodes['Y'],
$nodes['F'],
$nodes['Z'],
$nodes['A'],
$nodes['G'],
$nodes['H'],
$nodes['I'],
$nodes['J'],
$nodes['B'],
$nodes['K'],
$nodes['L'],
$nodes['root'],
$nodes['M'],
$nodes['N'],
$nodes['C'],
$nodes['O'],
$nodes['P'],
$nodes['Q'],
$nodes['R'],
$nodes['D'],
$nodes['S'],
$nodes['T'],
];

$this
->traverse($tree)
->shouldYield(new \ArrayIterator($nodes));
}
}
57 changes: 42 additions & 15 deletions src/Node/NaryNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace drupol\phptree\Node;

use drupol\phptree\Traverser\BreadthFirst;
use drupol\phptree\Traverser\TraverserInterface;

/**
* Class NaryNode.
Expand All @@ -18,21 +19,32 @@ class NaryNode extends Node
*/
private $capacity;

/**
* The traverser.
*
* @var TraverserInterface
*/
private $traverser;

/**
* NaryNode constructor.
*
* @param int $capacity
* The maximum children a node can have.
* @param \drupol\phptree\Node\NodeInterface|null $parent
* The parent.
* @param \drupol\phptree\Traverser\TraverserInterface|null $traverser
* The traverser.
*/
public function __construct(int $capacity = 0, NodeInterface $parent = null)
public function __construct(int $capacity = 0, NodeInterface $parent = null, TraverserInterface $traverser = null)
{
parent::__construct($parent);

$this->capacity = $capacity < 0 ?
0:
$capacity;

$this->traverser = $traverser ?? new BreadthFirst();
}

/**
Expand All @@ -49,26 +61,41 @@ public function capacity(): int
public function add(NodeInterface ...$nodes): NodeInterface
{
foreach ($nodes as $node) {
$capacity = $this->capacity();

if (0 === $capacity || $this->degree() < $capacity) {
parent::add($node);
/** @var \drupol\phptree\Node\Node $parent */
$parent = $this->findFirstAvailableNode();
$parent->storage['children'][] = $node->setParent($parent);
}

continue;
}
return $this;
}

// @todo Find a way to get rid of this.
$traverser = new BreadthFirst();
/**
* {@inheritdoc}
*/
public function getTraverser()
{
return $this->traverser;
}

foreach ($traverser->traverse($this) as $node_visited) {
if ($node_visited->degree() >= $capacity) {
continue;
}
/**
* Find first node in the tree that could have a new children.
*
* @return \drupol\phptree\Node\NodeInterface
*/
private function findFirstAvailableNode(): NodeInterface
{
$capacity = $this->capacity();

$node_visited->add($node);
foreach ($this->getTraverser()->traverse($this) as $node) {
if (\method_exists($node, 'capacity')) {
$capacity = $node->capacity();
}

break;
if ($node->degree() >= $capacity) {
continue;
}

return $node;
}

return $this;
Expand Down
10 changes: 10 additions & 0 deletions src/Node/NaryNodeInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace drupol\phptree\Node;

use drupol\phptree\Traverser\TraverserInterface;

/**
* Interface NaryNodeInterface
*/
Expand All @@ -16,4 +18,12 @@ interface NaryNodeInterface
* The node capacity.
*/
public function capacity(): int;

/**
* Get the traverser in use.
*
* @return \drupol\phptree\Traverser\TraverserInterface
* The traverser.
*/
public function getTraverser(): TraverserInterface;
}
36 changes: 15 additions & 21 deletions src/Node/Node.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public function __construct(NodeInterface $parent = null)
{
$this->storage = [
'parent' => $parent,
'children' => [],
'children' => new \ArrayObject(),
];
}

Expand All @@ -35,7 +35,9 @@ public function __construct(NodeInterface $parent = null)
public function add(NodeInterface ...$nodes): NodeInterface
{
foreach ($nodes as $node) {
$this->storage['children'][] = $node->setParent($this);
$this->storage['children']->append(
$node->setParent($this)
);
}

return $this;
Expand All @@ -46,11 +48,13 @@ public function add(NodeInterface ...$nodes): NodeInterface
*/
public function remove(NodeInterface ...$nodes): NodeInterface
{
$this->storage['children'] = \array_filter(
$this->storage['children'],
function ($child) use ($nodes) {
return !\in_array($child, $nodes, true);
}
$this->storage['children'] = new \ArrayObject(
\array_filter(
(array) $this->storage['children'],
function ($child) use ($nodes) {
return !\in_array($child, $nodes, true);
}
)
);

return $this;
Expand Down Expand Up @@ -79,17 +83,7 @@ public function getParent(): ?NodeInterface
*/
public function children(): \Traversable
{
yield from new \ArrayIterator($this->storage['children']);
}

/**
* {@inheritdoc}
*/
public function lastChild(): ?NodeInterface
{
return (false === $end = \end($this->storage['children'])) ?
null:
$end;
yield from $this->storage['children'];
}

/**
Expand All @@ -109,7 +103,7 @@ public function getAncestors(): \Traversable
*/
public function isLeaf(): bool
{
return [] === $this->storage['children'];
return 0 === $this->storage['children']->count();
}

/**
Expand Down Expand Up @@ -145,7 +139,7 @@ public function getSibblings(): \Traversable
*/
public function degree(): int
{
return \count($this->storage['children']);
return $this->storage['children']->count();
}

/**
Expand All @@ -169,7 +163,7 @@ public function count(): int
public function withChildren(NodeInterface ...$nodes): NodeInterface
{
$clone = clone $this;
$clone->storage['children'] = [];
$clone->storage['children'] = new \ArrayObject();

return [] === $nodes ?
$clone:
Expand Down
8 changes: 0 additions & 8 deletions src/Node/NodeInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,4 @@ public function depth(): int;
* The new object.
*/
public function withChildren(NodeInterface ...$nodes): NodeInterface;

/**
* Get the last child if any.
*
* @return \drupol\phptree\Node\NodeInterface|null
* The last child if any, null otherwise.
*/
public function lastChild(): ?NodeInterface;
}
7 changes: 5 additions & 2 deletions src/Traverser/InOrder.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,11 @@ public function traverse(NodeInterface $node): \Traversable
*/
private function doTraverse(NodeInterface $node): \Traversable
{
foreach ($node->children() as $child) {
if ($node->lastChild() === $child) {
$countChildren = $node->degree();
$middle = \floor($countChildren/2);

foreach ($node->children() as $key => $child) {
if ((int) $key === (int) $middle) {
yield $this->index => $node;
$this->index++;
}
Expand Down

0 comments on commit fb8588a

Please sign in to comment.