diff --git a/benchmarks/AbstractBench.php b/benchmarks/AbstractBench.php index 6d5958f..f8212b4 100644 --- a/benchmarks/AbstractBench.php +++ b/benchmarks/AbstractBench.php @@ -11,6 +11,6 @@ abstract class AbstractBench */ public function getData() { - return \range(1, 1000); + return \range(1, 100); } } diff --git a/benchmarks/DrupolPhpTreeBench.php b/benchmarks/DrupolPhpTreeBench.php index 6a66e94..519a3e8 100644 --- a/benchmarks/DrupolPhpTreeBench.php +++ b/benchmarks/DrupolPhpTreeBench.php @@ -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)); } } } diff --git a/spec/drupol/phptree/Exporter/GraphSpec.php b/spec/drupol/phptree/Exporter/GraphSpec.php index 850a9cd..50136aa 100644 --- a/spec/drupol/phptree/Exporter/GraphSpec.php +++ b/spec/drupol/phptree/Exporter/GraphSpec.php @@ -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; @@ -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); diff --git a/spec/drupol/phptree/Exporter/SimpleArraySpec.php b/spec/drupol/phptree/Exporter/SimpleArraySpec.php index 9e7e162..d1cf62f 100644 --- a/spec/drupol/phptree/Exporter/SimpleArraySpec.php +++ b/spec/drupol/phptree/Exporter/SimpleArraySpec.php @@ -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)); diff --git a/spec/drupol/phptree/Exporter/TextSpec.php b/spec/drupol/phptree/Exporter/TextSpec.php index ec22112..23e1478 100644 --- a/spec/drupol/phptree/Exporter/TextSpec.php +++ b/spec/drupol/phptree/Exporter/TextSpec.php @@ -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)); diff --git a/spec/drupol/phptree/Node/NodeSpec.php b/spec/drupol/phptree/Node/NodeSpec.php index 8186e14..b24b6d5 100644 --- a/spec/drupol/phptree/Node/NodeSpec.php +++ b/spec/drupol/phptree/Node/NodeSpec.php @@ -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 @@ -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); diff --git a/spec/drupol/phptree/Traverser/InOrderSpec.php b/spec/drupol/phptree/Traverser/InOrderSpec.php index bdcd6be..01ca341 100644 --- a/spec/drupol/phptree/Traverser/InOrderSpec.php +++ b/spec/drupol/phptree/Traverser/InOrderSpec.php @@ -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); @@ -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)); + } } diff --git a/src/Node/NaryNode.php b/src/Node/NaryNode.php index 22bdc4c..99eccda 100644 --- a/src/Node/NaryNode.php +++ b/src/Node/NaryNode.php @@ -5,6 +5,7 @@ namespace drupol\phptree\Node; use drupol\phptree\Traverser\BreadthFirst; +use drupol\phptree\Traverser\TraverserInterface; /** * Class NaryNode. @@ -18,6 +19,13 @@ class NaryNode extends Node */ private $capacity; + /** + * The traverser. + * + * @var TraverserInterface + */ + private $traverser; + /** * NaryNode constructor. * @@ -25,14 +33,18 @@ class NaryNode extends Node * 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(); } /** @@ -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; diff --git a/src/Node/NaryNodeInterface.php b/src/Node/NaryNodeInterface.php index 2b91b95..0ff3b9f 100644 --- a/src/Node/NaryNodeInterface.php +++ b/src/Node/NaryNodeInterface.php @@ -4,6 +4,8 @@ namespace drupol\phptree\Node; +use drupol\phptree\Traverser\TraverserInterface; + /** * Interface NaryNodeInterface */ @@ -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; } diff --git a/src/Node/Node.php b/src/Node/Node.php index fd590ad..21bd2a2 100644 --- a/src/Node/Node.php +++ b/src/Node/Node.php @@ -25,7 +25,7 @@ public function __construct(NodeInterface $parent = null) { $this->storage = [ 'parent' => $parent, - 'children' => [], + 'children' => new \ArrayObject(), ]; } @@ -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; @@ -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; @@ -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']; } /** @@ -109,7 +103,7 @@ public function getAncestors(): \Traversable */ public function isLeaf(): bool { - return [] === $this->storage['children']; + return 0 === $this->storage['children']->count(); } /** @@ -145,7 +139,7 @@ public function getSibblings(): \Traversable */ public function degree(): int { - return \count($this->storage['children']); + return $this->storage['children']->count(); } /** @@ -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: diff --git a/src/Node/NodeInterface.php b/src/Node/NodeInterface.php index 64422c0..8abb0eb 100644 --- a/src/Node/NodeInterface.php +++ b/src/Node/NodeInterface.php @@ -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; } diff --git a/src/Traverser/InOrder.php b/src/Traverser/InOrder.php index 6156cfb..7052fd9 100644 --- a/src/Traverser/InOrder.php +++ b/src/Traverser/InOrder.php @@ -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++; }