From 08a2f05718a94cfa0eb73789de38208e6efe5d7d Mon Sep 17 00:00:00 2001
From: Yoanm <4410697+yoanm@users.noreply.github.com>
Date: Wed, 22 Oct 2025 12:27:33 +0200
Subject: [PATCH 1/5] Add binary tree traversal, data-structure and factory
---
.../BinaryTree/RecursiveReversedTraversal.php | 168 +++++++++
.../BinaryTree/RecursiveTraversal.php | 213 ++++++++++++
.../BinaryTree/ReversedTraversal.php | 257 ++++++++++++++
src/Algorithm/BinaryTree/Traversal.php | 320 ++++++++++++++++++
src/DataStructure/BinaryTree/Node.php | 18 +
.../BinaryTree/NodeInterface.php | 16 +
src/Factory/BinaryTreeFactory.php | 60 ++++
.../BinaryTree/AbstractTraversalTestCase.php | 195 +++++++++++
.../RecursiveReversedTraversalTest.php | 62 ++++
.../BinaryTree/RecursiveTraversalTest.php | 82 +++++
.../BinaryTree/ReversedTraversalTest.php | 78 +++++
.../Algorithm/BinaryTree/TraversalTest.php | 94 +++++
.../Algorithm/TraversalTestTrait.php | 42 +++
.../DataStructure/BinaryTree/NodeTest.php | 25 ++
.../Factory/BinaryTreeFactoryTest.php | 71 ++++
15 files changed, 1701 insertions(+)
create mode 100644 src/Algorithm/BinaryTree/RecursiveReversedTraversal.php
create mode 100644 src/Algorithm/BinaryTree/RecursiveTraversal.php
create mode 100644 src/Algorithm/BinaryTree/ReversedTraversal.php
create mode 100644 src/Algorithm/BinaryTree/Traversal.php
create mode 100644 src/DataStructure/BinaryTree/Node.php
create mode 100644 src/DataStructure/BinaryTree/NodeInterface.php
create mode 100644 src/Factory/BinaryTreeFactory.php
create mode 100644 tests/Technical/Algorithm/BinaryTree/AbstractTraversalTestCase.php
create mode 100644 tests/Technical/Algorithm/BinaryTree/RecursiveReversedTraversalTest.php
create mode 100644 tests/Technical/Algorithm/BinaryTree/RecursiveTraversalTest.php
create mode 100644 tests/Technical/Algorithm/BinaryTree/ReversedTraversalTest.php
create mode 100644 tests/Technical/Algorithm/BinaryTree/TraversalTest.php
create mode 100644 tests/Technical/Algorithm/TraversalTestTrait.php
create mode 100644 tests/Technical/DataStructure/BinaryTree/NodeTest.php
create mode 100644 tests/Technical/Factory/BinaryTreeFactoryTest.php
diff --git a/src/Algorithm/BinaryTree/RecursiveReversedTraversal.php b/src/Algorithm/BinaryTree/RecursiveReversedTraversal.php
new file mode 100644
index 0000000..a942bd9
--- /dev/null
+++ b/src/Algorithm/BinaryTree/RecursiveReversedTraversal.php
@@ -0,0 +1,168 @@
+
+ * โ Recursive function implies a growing stack trace, which has a limited size!
+ * See {@see \Yoanm\CommonDSA\Algorithm\BinaryTree\ReversedTraversal} for iterative implementations.
+ *
+ *
+ *
+ * โน Reversed level-order is not implemented as it implies to keep track of and look at each and every node in
+ * order to detect the last level => Time and space complexity will be ๐โฎ๐โฏ in any cases !
+ * Workaround:
+ * 1. Fetch the result of {@see \Yoanm\CommonDSA\Algorithm\BinaryTree\RecursiveTraversal::levelOrder}
+ * or {@see \Yoanm\CommonDSA\Algorithm\BinaryTree\RecursiveTraversal::levelOrderGenerator}
+ * 2. Either start from the end or reverse the whole list (the latter implies
+ * an additional ๐โฎ๐โฏ time complexity though !)
+ *
+ *
+ * @see \Yoanm\CommonDSA\Algorithm\BinaryTree\ReversedTraversal for iterative implementations.
+ *
+ *
+ * @template TNode of Node The actual Node class
+ */
+class RecursiveReversedTraversal
+{
+ /**
+ * @see \Yoanm\CommonDSA\Algorithm\BinaryTree\RecursiveReversedTraversal::preOrderGenerator()
+ *
+ * @param TNode $node
+ *
+ * @return list
+ *
+ */
+ public static function preOrder(Node $node): array
+ {
+ // โ Do not preserve keys otherwise there is conflicting keys in case "yield from" is used !
+ return iterator_to_array(self::preOrderGenerator($node), false);
+ }
+
+ /**
+ * @see \Yoanm\CommonDSA\Algorithm\BinaryTree\RecursiveReversedTraversal::preOrderGenerator()
+ *
+ * @param TNode $node
+ *
+ * @return list
+ */
+ public static function inOrder(Node $node): array
+ {
+ // โ Do not preserve keys otherwise there is conflicting keys in case "yield from" is used !
+ return iterator_to_array(self::inOrderGenerator($node), false);
+ }
+
+ /**
+ * @see \Yoanm\CommonDSA\Algorithm\BinaryTree\RecursiveReversedTraversal::inOrderGenerator()
+ *
+ * @param TNode $node
+ *
+ * @return list
+ */
+ public static function postOrder(Node $node): array
+ {
+ // โ Do not preserve keys otherwise there is conflicting keys in case "yield from" is used !
+ return iterator_to_array(self::postOrderGenerator($node), false);
+ }
+
+ /**
+ * Reversed Pre-order: N->R->L => Node, then Right children, then Left children
+ *
+ *
+ *
+ * ### Time/Space complexity
+ * With:
+ * - ๐ the number of node in the tree
+ * - ๐ฉ the tree height, usually ใ(๐) (=balanced tree).
+ * โ ๐ฉ = ๐ if every node has only a right child (skewed tree)
+ *
+ * TC: ๐โฎ๐โฏ - Each node will be visited only one time
+ *
+ * SC: ๐โฎ๐ฉโฏ - Due to the stack trace (recursive calls) + extra space for inner Generator class instances !
+ *
+ *
+ * @param TNode $node
+ *
+ * @return Generator
+ */
+ public static function preOrderGenerator(Node $node): Generator
+ {
+ yield $node;
+
+ if (null !== $node->right) {
+ yield from self::preOrderGenerator($node->right);
+ }
+ if (null !== $node->left) {
+ yield from self::preOrderGenerator($node->left);
+ }
+ }
+
+ /**
+ * Reversed In-order (=Reversed DFS): R->N->L => Right children, then Node, then Left children
+ *
+ *
+ *
+ * ### Time/Space complexity
+ * With:
+ * - ๐ the number of node in the tree
+ * - ๐ฉ the tree height, usually ใ(๐) (=balanced tree).
+ * โ ๐ฉ = ๐ if every node has only a right child (skewed tree)
+ *
+ * TC: ๐โฎ๐โฏ - Each node will be visited only one time
+ *
+ * SC: ๐โฎ๐ฉโฏ - Due to the stack trace (recursive calls) + extra space for inner Generator class instances !
+ *
+ *
+ * @param TNode $node
+ *
+ * @return Generator
+ */
+ public static function inOrderGenerator(Node $node): Generator
+ {
+ if (null !== $node->right) {
+ yield from self::inOrderGenerator($node->right);
+ }
+
+ yield $node;
+
+ if (null !== $node->left) {
+ yield from self::inOrderGenerator($node->left);
+ }
+ }
+
+ /**
+ * Reversed Post-order: R->L->N => Right children, then Left children, then Node
+ *
+ *
+ *
+ * ### Time/Space complexity
+ * With:
+ * - ๐ the number of node in the tree
+ * - ๐ฉ the tree height, usually ใ(๐) (=balanced tree).
+ * โ ๐ฉ = ๐ if every node has only a right child (skewed tree)
+ *
+ * TC: ๐โฎ๐โฏ - Each node will be visited only one time
+ *
+ * SC: ๐โฎ๐ฉโฏ - Due to the stack trace (recursive calls) + extra space for inner Generator class instances !
+ *
+ *
+ * @param TNode $node
+ *
+ * @return Generator
+ */
+ public static function postOrderGenerator(Node $node): Generator
+ {
+ if (null !== $node->right) {
+ yield from self::postOrderGenerator($node->right);
+ }
+ if (null !== $node->left) {
+ yield from self::postOrderGenerator($node->left);
+ }
+ yield $node;
+ }
+}
diff --git a/src/Algorithm/BinaryTree/RecursiveTraversal.php b/src/Algorithm/BinaryTree/RecursiveTraversal.php
new file mode 100644
index 0000000..51460d2
--- /dev/null
+++ b/src/Algorithm/BinaryTree/RecursiveTraversal.php
@@ -0,0 +1,213 @@
+
+ * โ Recursive function implies a growing stack trace, which has a limited size!
+ * See {@see \Yoanm\CommonDSA\Algorithm\BinaryTree\Traversal} for iterative implementations.
+ *
+ *
+ * @see \Yoanm\CommonDSA\Algorithm\BinaryTree\Traversal for iterative implementations.
+ *
+ *
+ * @template TNode of Node The actual Node class
+ */
+class RecursiveTraversal
+{
+ /**
+ * @see \Yoanm\CommonDSA\Algorithm\BinaryTree\RecursiveTraversal::preOrderGenerator()
+ *
+ * @param TNode $node
+ *
+ * @return list
+ */
+ public static function preOrder(Node $node): array
+ {
+ // โ Do not preserve keys otherwise there is conflicting keys in case "yield from" is used !
+ return iterator_to_array(self::preOrderGenerator($node), false);
+ }
+
+ /**
+ * @see \Yoanm\CommonDSA\Algorithm\BinaryTree\RecursiveTraversal::preOrderGenerator()
+ *
+ * @param TNode $node
+ *
+ * @return list
+ */
+ public static function inOrder(Node $node): array
+ {
+ // โ Do not preserve keys otherwise there is conflicting keys in case "yield from" is used !
+ return iterator_to_array(self::inOrderGenerator($node), false);
+ }
+
+ /**
+ * @see \Yoanm\CommonDSA\Algorithm\BinaryTree\RecursiveTraversal::inOrderGenerator()
+ *
+ * @param TNode $node
+ *
+ * @return list
+ */
+ public static function postOrder(Node $node): array
+ {
+ // โ Do not preserve keys otherwise there is conflicting keys in case "yield from" is used !
+ return iterator_to_array(self::postOrderGenerator($node), false);
+ }
+
+ /**
+ * Level order (=BFS): Traverse tree level by level, from Left to Right
+ *
+ *
+ *
+ * ๐ก Reverse the list for reversed level-order traversal !
+ *
+ *
+ * ### Time/Space complexity
+ * With:
+ * - ๐ the number of node in the tree
+ *
+ * TC: ๐โฎ๐โฏ - Each node will be visited only one time
+ *
+ * SC: ๐โฎ๐โฏ - Due to the list storing every node for every level (see levelOrderHelper() function).
+ *
+ * @return list> key is the level, value is the list of nodes for that level
+ */
+ public static function levelOrder(Node $root): array
+ {
+ $res = [];
+
+ self::levelOrderHelper($root, $res);
+
+ return $res;
+ }
+
+ /**
+ * Pre-order: N->L->R => Node, then Left children, then Right children
+ *
+ *
+ * ### Time/Space complexity
+ * With:
+ * - ๐ the number of node in the tree
+ * - ๐ฉ the tree height, usually ใ(๐) (=balanced tree).
+ * โ ๐ฉ = ๐ if every node has only a left child (skewed tree)
+ *
+ * TC: ๐โฎ๐โฏ - Each node will be visited only one time
+ *
+ * SC: ๐โฎ๐ฉโฏ - Due to the stack trace (recursive calls) + extra space for inner Generator class instances !
+ *
+ * @param TNode $node
+ *
+ * @return Generator
+ */
+ public static function preOrderGenerator(Node $node): Generator
+ {
+ yield $node;
+
+ if (null !== $node->left) {
+ yield from self::preOrderGenerator($node->left);
+ }
+ if (null !== $node->right) {
+ yield from self::preOrderGenerator($node->right);
+ }
+ }
+
+ /**
+ * In-order (=DFS): L->N->R => Left children, then Node, then Right children
+ *
+ *
+ * ### Time/Space complexity
+ * With:
+ * - ๐ the number of node in the tree
+ * - ๐ฉ the tree height, usually ใ(๐) (=balanced tree).
+ * โ ๐ฉ = ๐ if every node has only a left child (skewed tree)
+ *
+ * TC: ๐โฎ๐โฏ - Each node will be visited only one time
+ *
+ * SC: ๐โฎ๐ฉโฏ - Due to the stack trace (recursive calls) + extra space for inner Generator class instances !
+ *
+ * @param TNode $node
+ *
+ * @return Generator
+ */
+ public static function inOrderGenerator(Node $node): Generator
+ {
+ if (null !== $node->left) {
+ yield from self::inOrderGenerator($node->left);
+ }
+
+ yield $node;
+
+ if (null !== $node->right) {
+ yield from self::inOrderGenerator($node->right);
+ }
+ }
+
+ /**
+ * Post-order: L->R->N => Left children, then Right children, then Node
+ *
+ *
+ * ### Time/Space complexity
+ * With:
+ * - ๐ the number of node in the tree
+ * - ๐ฉ the tree height, usually ใ(๐) (=balanced tree).
+ * โ ๐ฉ = ๐ if every node has only a left child (skewed tree)
+ *
+ * TC: ๐โฎ๐โฏ - Each node will be visited only one time
+ *
+ * SC: ๐โฎ๐ฉโฏ - Due to the stack trace (recursive calls) + extra space for inner Generator class instances !
+ *
+ * @param TNode $node
+ *
+ * @return Generator
+ */
+ public static function postOrderGenerator(Node $node): Generator
+ {
+ if (null !== $node->left) {
+ yield from self::postOrderGenerator($node->left);
+ }
+ if (null !== $node->right) {
+ yield from self::postOrderGenerator($node->right);
+ }
+ yield $node;
+ }
+
+ /**
+ * Level order (=BFS): Traverse tree level by level, from Left to Right
+ *
+ *
+ *
+ * ๐ก Reverse the list for reversed level-order traversal !
+ *
+ *
+ * ### Time/Space complexity
+ * With:
+ * - ๐ the number of node in the tree
+ * - ๐ฉ the tree height, usually ใ(๐) (=balanced tree).
+ * โ ๐ฉ = ๐ if every node has only a left child (skewed tree)
+ *
+ * TC: ๐โฎ๐โฏ - Each node will be visited only one time
+ *
+ * SC: ๐โฎhโฏ - Due to the stack trace (recursive calls)
+ *
+ * @param TNode $node
+ *
+ * @param array> $res key is the level, value is the list of nodes for that level
+ */
+ public static function levelOrderHelper(Node $node, array &$res, int $level = 0): void
+ {
+ $res[$level] ??= []; // In case current level hasn't been seen/managed yet
+
+ $res[$level][] = $node;
+ if (null !== $node->left) {
+ self::levelOrderHelper($node->left, $res, $level + 1);
+ }
+ if (null !== $node->right) {
+ self::levelOrderHelper($node->right, $res, $level + 1);
+ }
+ }
+}
diff --git a/src/Algorithm/BinaryTree/ReversedTraversal.php b/src/Algorithm/BinaryTree/ReversedTraversal.php
new file mode 100644
index 0000000..ed51b50
--- /dev/null
+++ b/src/Algorithm/BinaryTree/ReversedTraversal.php
@@ -0,0 +1,257 @@
+
+ * โน Reversed level-order is not implemented as it implies to keep track of and look at each and every node in
+ * order to detect the last level => Time and space complexity will be ๐โฎ๐โฏ in any cases !
+ * Workaround:
+ * 1. Fetch the result of {@see \Yoanm\CommonDSA\Algorithm\BinaryTree\Traversal::levelOrder}
+ * or {@see \Yoanm\CommonDSA\Algorithm\BinaryTree\Traversal::levelOrderGenerator}
+ * 2. Either start from the end or reverse the whole list (the latter implies
+ * an additional ๐โฎ๐โฏ time complexity though !)
+ *
+ *
+ * @see \Yoanm\CommonDSA\Algorithm\BinaryTree\RecursiveReversedTraversal for recursive implementations.
+ *
+ *
+ * @template TNode of Node The actual Node class
+ */
+class ReversedTraversal
+{
+ /**
+ * @see \Yoanm\CommonDSA\Algorithm\BinaryTree\RevervedTraversal::preOrderGenerator()
+ *
+ * @param TNode $node
+ *
+ * @return list
+ */
+ public static function preOrder(Node $node): array
+ {
+ return iterator_to_array(self::preOrderGenerator($node));
+ }
+
+ /**
+ * @see \Yoanm\CommonDSA\Algorithm\BinaryTree\RevervedTraversal::inOrderGenerator()
+ *
+ * @param TNode $node
+ *
+ * @return list
+ */
+ public static function inOrder(Node $node): array
+ {
+ return iterator_to_array(self::inOrderGenerator($node));
+ }
+
+ /**
+ * @see \Yoanm\CommonDSA\Algorithm\BinaryTree\RevervedTraversal::postOrderGenerator()
+ *
+ * @param TNode $node
+ *
+ * @return list
+ */
+ public static function postOrder(Node $node): array
+ {
+ return iterator_to_array(self::postOrderGenerator($node));
+ }
+
+ /**
+ * @see \Yoanm\CommonDSA\Algorithm\BinaryTree\RevervedTraversal::BFSGenerator()
+ *
+ * @param TNode $node
+ *
+ * @return list
+ */
+ public static function BFS(Node $node): array
+ {
+ return iterator_to_array(self::BFSGenerator($node));
+ }
+
+ /**
+ * Reversed Pre-order: N->R->L => Node, then Right children, then Left children
+ *
+ *
+ *
+ * ### Time/Space complexity
+ * With:
+ * - ๐ the number of node in the tree
+ * - ๐ฉ the tree height, usually ใ(๐) (=balanced tree).
+ * โ ๐ฉ = ๐ if every node has only a right child (skewed tree)
+ *
+ * TC: ๐โฎ๐โฏ - Each node will be visited only one time
+ *
+ * SC: ๐โฎ๐ฉโฏ - Due to the stack storing parent nodes path up to the root node.
+ *
+ *
+ * @param TNode $node
+ *
+ * @return Generator
+ */
+ public static function preOrderGenerator(Node $node): Generator
+ {
+ $stack = new SplStack();
+
+ $stack->push($node);
+ while (!$stack->isEmpty()) {
+ /** @var Node $currentNode */
+ $currentNode = $stack->pop();
+
+ yield $currentNode;
+
+ if (null !== $currentNode->left) {
+ $stack->push($currentNode->left);
+ }
+ if (null !== $currentNode->right) {
+ $stack->push($currentNode->right);
+ }
+ }
+ }
+
+ /**
+ * Reversed In-order (=Reversed DFS): R->N->L => Right children, then Node, then Left children
+ *
+ *
+ *
+ * ### Time/Space complexity
+ * With:
+ * - ๐ the number of node in the tree
+ * - ๐ฉ the tree height, usually ใ(๐) (=balanced tree).
+ * โ ๐ฉ = ๐ if every node has only a right child (skewed tree)
+ *
+ * TC: ๐โฎ๐โฏ - Each node will be visited only one time
+ *
+ * SC: ๐โฎ๐ฉโฏ - Due to the stack storing parent nodes path up to the root node.
+ *
+ *
+ * @param TNode $node
+ *
+ * @return Generator
+ */
+ public static function inOrderGenerator(Node $node): Generator
+ {
+ $stack = new SplStack();
+
+ $currentNode = $node;
+ while (null !== $currentNode || !$stack->isEmpty()) {
+ while (null !== $currentNode) {
+ $stack->push($currentNode);
+ $currentNode = $currentNode->right;
+ }
+
+ // Current node becomes the rightmost leaf found
+ // (or the same current node in case it doesn't have right node!)
+ /** @var Node $currentNode */
+ $currentNode = $stack->pop();
+
+ yield $currentNode;
+
+ // Right is now managed, let's take a look on left side
+ $currentNode = $currentNode->left;
+ }
+ }
+
+ /**
+ * Reversed Post-order: R->L->N => Right children, then Left children, then Node
+ *
+ *
+ *
+ * ### Time/Space complexity
+ * With:
+ * - ๐ the number of node in the tree
+ * - ๐ฉ the tree height, usually ใ(๐) (=balanced tree).
+ * โ ๐ฉ = ๐ if every node has only a right child (skewed tree)
+ *
+ * TC: ๐โฎ๐โฏ - Each node will be visited only one time
+ *
+ * SC: ๐โฎ๐ฉโฏ - Due to the stack storing parent nodes.
+ *
+ *
+ * @param TNode $node
+ *
+ * @return Generator
+ */
+ public static function postOrderGenerator(Node $node): Generator
+ {
+ $stack = new SplStack();
+
+ $currentNode = $node;
+ while (null !== $currentNode || !$stack->isEmpty()) {
+ while (null !== $currentNode) {
+ if (null !== $currentNode->left) {
+ $stack->push($currentNode->left);
+ }
+ $stack->push($currentNode);
+ $currentNode = $currentNode->right;
+ }
+
+ /** @var Node $currentNode */
+ $currentNode = $stack->pop();
+
+ if (!$stack->isEmpty() && $currentNode->left === $stack->top()) {
+ // Remove current node left child from the stack, it will become $currentNode for next iteration
+ $stack->pop();
+
+ $stack->push($currentNode);
+ $currentNode = $currentNode->left;
+ } else {
+ yield $currentNode;
+
+ $currentNode = null;
+ }
+ }
+ }
+
+ /**
+ * Reversed BFS: Traverse tree level by level, from Right to Left.
+ *
+ *
+ *
+ * ### Time/Space complexity
+ * With:
+ * - ๐ the number of node in the tree
+ * - ๐ the number of nodes for the bigger level
+ *
+ * TC: ๐โฎ๐โฏ - Each node will be visited only one time
+ *
+ * SC: ๐โฎ๐โฏ - Due to the queue storing current level parent nodes
+ *
+ *
+ * @param TNode $node
+ *
+ * @return Generator
+ */
+ public static function BFSGenerator(Node $node): Generator
+ {
+ $queue = new SplQueue();
+
+ $queue->enqueue($node);
+ while (!$queue->isEmpty()) {
+ $currentLevelNodeCounter = $queue->count();
+ while ($currentLevelNodeCounter > 0) {
+ $node = $queue->dequeue();
+
+ yield $node;
+
+ if (null !== $node->right) {
+ $queue->enqueue($node->right);
+ }
+ if (null !== $node->left) {
+ $queue->enqueue($node->left);
+ }
+
+ --$currentLevelNodeCounter;
+ }
+ }
+ }
+}
diff --git a/src/Algorithm/BinaryTree/Traversal.php b/src/Algorithm/BinaryTree/Traversal.php
new file mode 100644
index 0000000..628dca6
--- /dev/null
+++ b/src/Algorithm/BinaryTree/Traversal.php
@@ -0,0 +1,320 @@
+
+ */
+ public static function preOrder(Node $node): array
+ {
+ return iterator_to_array(self::preOrderGenerator($node));
+ }
+
+ /**
+ * @see \Yoanm\CommonDSA\Algorithm\BinaryTree\Traversal::inOrderGenerator()
+ *
+ * @param TNode $node
+ *
+ * @return list
+ */
+ public static function inOrder(Node $node): array
+ {
+ return iterator_to_array(self::inOrderGenerator($node));
+ }
+
+ /**
+ * @see \Yoanm\CommonDSA\Algorithm\BinaryTree\Traversal::postOrderGenerator()
+ *
+ * @param TNode $node
+ *
+ * @return list
+ */
+ public static function postOrder(Node $node): array
+ {
+ return iterator_to_array(self::postOrderGenerator($node));
+ }
+
+ /**
+ * @see \Yoanm\CommonDSA\Algorithm\BinaryTree\Traversal::BFSGenerator()
+ *
+ * @see \Yoanm\CommonDSA\Algorithm\BinaryTree\Traversal::levelOrder() if node level must be known !
+ *
+ * @param TNode $node
+ *
+ * @return list
+ */
+ public static function BFS(Node $node): array
+ {
+ return iterator_to_array(self::BFSGenerator($node));
+ }
+
+ /**
+ * @see \Yoanm\CommonDSA\Algorithm\BinaryTree\Traversal::levelOrderGenerator()
+ *
+ *
+ * @see \Yoanm\CommonDSA\Algorithm\BinaryTree\Traversal::BFS() if node level isn't useful.
+ *
+ * @param TNode $node
+ *
+ * @return list> key is the level, value is the list of nodes for that level
+ */
+ public static function levelOrder(Node $node): array
+ {
+ return iterator_to_array(self::levelOrderGenerator($node));
+ }
+
+ /**
+ * Pre-order: N->L->R => Node, then Left children, then Right children.
+ *
+ *
+ *
+ * ### Time/Space complexity
+ * With:
+ * - ๐ the number of node in the tree
+ * - ๐ฉ the tree height, usually ใ(๐) (=balanced tree).
+ * โ ๐ฉ = ๐ if every node has only a left child (skewed tree)
+ *
+ * TC: ๐โฎ๐โฏ - Each node will be visited only one time
+ *
+ * SC: ๐โฎ๐ฉโฏ - Due to the stack storing parent nodes path up to the root node.
+ *
+ *
+ * @param TNode $node
+ *
+ * @return Generator
+ */
+ public static function preOrderGenerator(Node $node): Generator
+ {
+ $stack = new SplStack();
+
+ $stack->push($node);
+ while (!$stack->isEmpty()) {
+ /** @var Node $currentNode */
+ $currentNode = $stack->pop();
+
+ yield $currentNode;
+
+ if (null !== $currentNode->right) {
+ $stack->push($currentNode->right);
+ }
+ if (null !== $currentNode->left) {
+ $stack->push($currentNode->left);
+ }
+ }
+ }
+
+ /**
+ * In-order (=DFS): L->N->R => Left children, then Node, then Right children.
+ *
+ *
+ *
+ * ### Time/Space complexity
+ * With:
+ * - ๐ the number of node in the tree
+ * - ๐ฉ the tree height, usually ใ(๐) (=balanced tree).
+ * โ ๐ฉ = ๐ if every node has only a left child (skewed tree)
+ *
+ * TC: ๐โฎ๐โฏ - Each node will be visited only one time
+ *
+ * SC: ๐โฎ๐ฉโฏ - Due to the stack storing parent nodes path up to the root node.
+ *
+ *
+ * @param TNode $node
+ *
+ * @return Generator
+ */
+ public static function inOrderGenerator(Node $node): Generator
+ {
+ $stack = new SplStack();
+ $currentNode = $node;
+ while (null !== $currentNode || !$stack->isEmpty()) {
+ while (null !== $currentNode) {
+ $stack->push($currentNode);
+ $currentNode = $currentNode->left;
+ }
+
+ // Current node becomes the leftmost leaf found
+ // (or the same current node in case it doesn't have left node!)
+ /** @var Node $currentNode */
+ $currentNode = $stack->pop();
+
+ yield $currentNode;
+
+ // Left is now managed, let's take a look on right side
+ $currentNode = $currentNode->right;
+ }
+ }
+
+ /**
+ * Post-order: L->R->N => Left children, then Right children, then Node.
+ *
+ *
+ *
+ * ### Time/Space complexity
+ * With:
+ * - ๐ the number of node in the tree
+ * - ๐ฉ the tree height, usually ใ(๐) (=balanced tree).
+ * โ ๐ฉ = ๐ if every node has only a left child (skewed tree)
+ *
+ * TC: ๐โฎ๐โฏ - Each node will be visited only one time
+ *
+ * SC: ๐โฎ๐ฉโฏ - Due to the stack storing parent nodes path up to the root node.
+ *
+ *
+ * @param TNode $node
+ *
+ * @return Generator
+ */
+ public static function postOrderGenerator(Node $node): Generator
+ {
+ $stack = new SplStack();
+
+ $currentNode = $node;
+ while (null !== $currentNode || !$stack->isEmpty()) {
+ while (null !== $currentNode) {
+ if (null !== $currentNode->right) {
+ $stack->push($currentNode->right);
+ }
+ $stack->push($currentNode);
+ $currentNode = $currentNode->left;
+ }
+
+ /** @var Node $currentNode */
+ $currentNode = $stack->pop();
+
+ if (!$stack->isEmpty() && $currentNode->right === $stack->top()) {
+ // Remove current node right child from the stack, it will become $currentNode for next iteration
+ $stack->pop();
+
+ $stack->push($currentNode);
+ $currentNode = $currentNode->right;
+ } else {
+ yield $currentNode;
+
+ $currentNode = null;
+ }
+ }
+ }
+
+ /**
+ * Level order (=BFS): Traverse tree level by level, from Left to Right
+ *
+ *
+ *
+ * ๐ก Reverse the list for reversed level-order traversal !
+ *
+ *
+ * ### Time/Space complexity
+ * With:
+ * - ๐ the number of node in the tree
+ * - ๐ the number of nodes for the bigger level
+ *
+ * TC: ๐โฎ๐โฏ - Each node will be visited only one time
+ *
+ * SC: ๐โฎ๐โฏ - Due to the queue storing current level parent nodes, plus temporary list storing every node
+ * for the current level
+ *
+ *
+ *
+ * @see \Yoanm\CommonDSA\Algorithm\BinaryTree\Traversal::BFSGenerator() if node level isn't useful.
+ *
+ *
+ * @param TNode $node
+ *
+ * @return Generator> key is the level, value is the list of nodes for that level
+ */
+ public static function levelOrderGenerator(Node $node): Generator
+ {
+ $queue = new SplQueue();
+
+ $queue->enqueue($node);
+ while (!$queue->isEmpty()) {
+ $nodeList = [];
+ $currentLevelNodeCounter = $queue->count();
+ while ($currentLevelNodeCounter > 0) {
+ $node = $queue->dequeue();
+
+ $nodeList[] = $node;
+
+ if (null !== $node->left) {
+ $queue->enqueue($node->left);
+ }
+ if (null !== $node->right) {
+ $queue->enqueue($node->right);
+ }
+
+ --$currentLevelNodeCounter;
+ }
+
+ yield $nodeList;
+ }
+ }
+
+ /**
+ * BFS: Traverse tree level by level, from Left to Right.
+ *
+ *
+ *
+ * ### Time/Space complexity
+ * With:
+ * - ๐ the number of node in the tree
+ * - ๐ the number of nodes for the bigger level
+ *
+ * TC: ๐โฎ๐โฏ - Each node will be visited only one time
+ *
+ * SC: ๐โฎ๐โฏ - Due to the queue storing current level parent nodes
+ *
+ *
+ * @see \Yoanm\CommonDSA\Algorithm\BinaryTree\Traversal::levelOrderGenerator() if node level must be known !
+ *
+ *
+ * @param TNode $node
+ *
+ * @return Generator
+ */
+ public static function BFSGenerator(Node $node): Generator
+ {
+ $queue = new SplQueue();
+
+ $queue->enqueue($node);
+ while (!$queue->isEmpty()) {
+ $currentLevelNodeCounter = $queue->count();
+ while ($currentLevelNodeCounter > 0) {
+ $node = $queue->dequeue();
+
+ yield $node;
+
+ if (null !== $node->left) {
+ $queue->enqueue($node->left);
+ }
+ if (null !== $node->right) {
+ $queue->enqueue($node->right);
+ }
+
+ --$currentLevelNodeCounter;
+ }
+ }
+ }
+}
diff --git a/src/DataStructure/BinaryTree/Node.php b/src/DataStructure/BinaryTree/Node.php
new file mode 100644
index 0000000..c30f146
--- /dev/null
+++ b/src/DataStructure/BinaryTree/Node.php
@@ -0,0 +1,18 @@
+ $list
+ * @param TNodeCreator $nodeCreator
+ *
+ * @return null|Node|TNode
+ */
+ public static function fromLevelOrderList(array $list, callable $nodeCreator): null|Node
+ {
+ $tailIdx = count($list) - 1;
+ if ($tailIdx < 0 || (0 === $tailIdx && null === $list[0])) {
+ return null;
+ }
+
+ $queue = new SplQueue();
+
+ $root = call_user_func($nodeCreator, $list[0]);
+ $queue->enqueue($root);
+
+ $idx = 1; // Root value already managed, hence 1 rather 0 !
+ while ($idx <= $tailIdx) {
+ /** @var Node|TNode $parentNode */
+ $parentNode = $queue->dequeue();
+
+ // 1. Manage left node value
+ if (null !== $list[$idx]) {
+ $parentNode->left = $node = call_user_func($nodeCreator, $list[$idx]);
+ $queue->enqueue($node);
+ }
+ ++$idx;
+
+ // 1. Manage right node value (if it exists !)
+ if ($idx <= $tailIdx && null !== $list[$idx]) {
+ $parentNode->right = $node = call_user_func($nodeCreator, $list[$idx]);
+ $queue->enqueue($node);
+ }
+ ++$idx;
+ }
+
+ return $root;
+ }
+}
diff --git a/tests/Technical/Algorithm/BinaryTree/AbstractTraversalTestCase.php b/tests/Technical/Algorithm/BinaryTree/AbstractTraversalTestCase.php
new file mode 100644
index 0000000..a6d0a12
--- /dev/null
+++ b/tests/Technical/Algorithm/BinaryTree/AbstractTraversalTestCase.php
@@ -0,0 +1,195 @@
+ [
+ 'treeData' => [1,2,7,3,4,null,8,null,null,5,6,9],
+ 'expected' => [1,2,3,4,5,6,7,8,9],
+ ],
+ 'Basic case 2' => [
+ 'treeData' => [1,2,7,3,6,8,10,4,5,null,null,null,9,11,12],
+ 'expected' => [1,2,3,4,5,6,7,8,9,10,11,12],
+ ],
+ 'Skewed tree - only left' => [
+ 'treeData' => [1,2,null,3,null,4,null,5,null,6,null,7,null,8,null,9,null,10,null,11,null,12],
+ 'expected' => [1,2,3,4,5,6,7,8,9,10,11,12],
+ ],
+ 'Skewed tree - only right' => [
+ 'treeData' => [1,null,2,null,3,null,4,null,5,null,6,null,7,null,8,null,9,null,10,null,11,null,12],
+ 'expected' => [1,2,3,4,5,6,7,8,9,10,11,12],
+ ],
+ 'Zigzag tree' => [
+ 'treeData' => [1,2,null,null,3,4,null,null,5,6,null,null,7,8,null,null,9,10,null,null,11,12],
+ 'expected' => [1,2,3,4,5,6,7,8,9,10,11,12],
+ ],
+ 'Double zigzag tree' => [
+ 'treeData' => [1,2,8,null,3,9,null,4,null,null,10,null,5,11,null,6,null,null,12,null,7,13],
+ 'expected' => [1,2,3,4,5,6,7,8,9,10,11,12,13],
+ ],
+ 'Asymmetric tree - deep left nesting' => [
+ 'treeData' => [1,2,6,3,null,null,7,4,null,null,null,5],
+ 'expected' => [1,2,3,4,5,6,7],
+ ],
+ ];
+
+ return self::mapTestCases($caseDataList);
+ }
+
+ public static function provideReversedPreOrderTestCases(): array
+ {
+ $caseDataList = [
+ 'Basic case 1' => [
+ 'treeData' => [1,5,2,9,6,null,3,null,null,8,7,4],
+ 'expected' => [1,2,3,4,5,6,7,8,9],
+ ],
+ 'Basic case 2' => [
+ 'treeData' => [1,8,2,10,9,6,3,12,11,null,null,null,7,5,4],
+ 'expected' => [1,2,3,4,5,6,7,8,9,10,11,12],
+ ],
+ ];
+
+ return self::mapTestCases($caseDataList);
+ }
+
+ public static function provideInOrderTestCases(): array
+ {
+ $caseDataList = [
+ 'Basic case 1' => [
+ 'treeData' => [6,2,7,1,4,null,9,null,null,3,5,8],
+ 'expected' => [1,2,3,4,5,6,7,8,9],
+ ],
+ 'Basic case 2' => [
+ 'treeData' => [6,4,9,2,5,7,11,1,3,null,null,null,8,10,12],
+ 'expected' => [1,2,3,4,5,6,7,8,9,10,11,12],
+ ],
+ ];
+
+ return self::mapTestCases($caseDataList);
+ }
+
+ public static function provideReversedInOrderTestCases(): array
+ {
+ $caseDataList = [
+ 'Basic case 1' => [
+ 'treeData' => [4,8,3,9,6,null,1,null,null,7,5,2],
+ 'expected' => [1,2,3,4,5,6,7,8,9],
+ ],
+ 'Basic case 2' => [
+ 'treeData' => [7,9,4,11,8,6,2,12,10,null,null,null,5,3,1],
+ 'expected' => [1,2,3,4,5,6,7,8,9,10,11,12],
+ ],
+ ];
+
+ return self::mapTestCases($caseDataList);
+ }
+
+ public static function providePostorderTestCases(): array
+ {
+ $caseDataList = [
+ 'Basic case 1' => [
+ 'treeData' => [9,5,8,1,4,null,7,null,null,2,3,6],
+ 'expected' => [1,2,3,4,5,6,7,8,9],
+ ],
+ 'Basic case 2' => [
+ 'treeData' => [12,5,11,3,4,7,10,1,2,null,null,null,6,8,9],
+ 'expected' => [1,2,3,4,5,6,7,8,9,10,11,12],
+ ],
+ ];
+
+ return self::mapTestCases($caseDataList);
+ }
+
+ public static function provideReversedPostorderTestCases(): array
+ {
+ $caseDataList = [
+ 'Basic case 1' => [
+ 'treeData' => [9,8,3,7,6,null,2,null,null,5,4,1],
+ 'expected' => [1,2,3,4,5,6,7,8,9],
+ ],
+ 'Basic case 2' => [
+ 'treeData' => [12,11,6,10,7,5,3,9,8,null,null,null,4,2,1],
+ 'expected' => [1,2,3,4,5,6,7,8,9,10,11,12],
+ ],
+ ];
+
+ return self::mapTestCases($caseDataList);
+ }
+
+ public static function provideTestReversedBFSCases(): array
+ {
+ $caseDataList = [
+ 'Basic case 1' => [
+ 'treeData' => [1,3,2,6,5,null,4,null,null,9,8,7],
+ 'expected' => [1,2,3,4,5,6,7,8,9],
+ ],
+ 'Basic case 2' => [
+ 'treeData' => [1,3,2,7,6,5,4,12,11,null,null,null,10,9,8],
+ 'expected' => [1,2,3,4,5,6,7,8,9,10,11,12],
+ ],
+ 'Basic case 3' => [
+ 'treeData' => [1,3,2,7,6,5,4,12,11,null,null,null,10,9,8,null,null,15,null,null,null,14,null,null,13,17,null,null,null,16],
+ 'expected' => [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17],
+ ],
+ ];
+
+ return self::mapTestCases($caseDataList);
+ }
+
+ public static function provideTestLevelOrderCases(): array
+ {
+ $caseDataList = [
+ 'Basic case 1' => [
+ 'treeData' => [1,2,3,4,5,null,6,null,null,7,8,9],
+ 'expected' => [[1],[2,3],[4,5,6],[7,8,9]],
+ ],
+ 'Basic case 2' => [
+ 'treeData' => [1,2,3,4,5,6,7,8,9,null,null,null,10,11,12],
+ 'expected' => [[1],[2,3],[4,5,6,7],[8,9,10,11,12]],
+ ],
+ 'Basic case 3' => [
+ 'treeData' => [1,2,3,4,5,6,7,8,9,null,null,null,10,11,12,null,null,13,null,null,null,14,null,null,15,16,null,null,null,17],
+ 'expected' => [[1],[2,3],[4,5,6,7],[8,9,10,11,12],[13,14,15],[16,17]],
+ ],
+ ];
+
+ return self::mapTestCases($caseDataList);
+ }
+
+ /**
+ * @param array $caseDataList
+ *
+ * @return array
+ */
+ protected static function mapTestCases(array $caseDataList): array
+ {
+ return array_map(
+ static fn ($caseData) => [
+ TreeFactory::fromLevelOrderList($caseData['treeData'], static fn(int $val) => new Node($val)),
+ $caseData['expected'],
+ ],
+ $caseDataList,
+ );
+ }
+}
diff --git a/tests/Technical/Algorithm/BinaryTree/RecursiveReversedTraversalTest.php b/tests/Technical/Algorithm/BinaryTree/RecursiveReversedTraversalTest.php
new file mode 100644
index 0000000..fa6940f
--- /dev/null
+++ b/tests/Technical/Algorithm/BinaryTree/RecursiveReversedTraversalTest.php
@@ -0,0 +1,62 @@
+ $expected
+ * @param iterable $actual
+ */
+ protected static function assertSameTreeNodeValues(array $expected, iterable $actual, Assert $assert): void
+ {
+ $newActual = [];
+ foreach ($actual as $node) {
+ $newActual[] = $node->val;
+ }
+
+ $assert::assertSame($expected, $newActual);
+ }
+
+ /**
+ * @param list> $expected
+ * @param iterable> $actual
+ */
+ protected static function assertLevelOrderSameTreeNodeValues(array $expected, iterable $actual, Assert $assert): void
+ {
+ $newActual = [];
+ foreach ($actual as $key => $nodeList) {
+ $newActual[$key] = [];
+ foreach ($nodeList as $node) {
+ $newActual[$key][] = $node->val;
+ }
+ }
+
+ $assert::assertSame($expected, $newActual);
+ }
+}
diff --git a/tests/Technical/DataStructure/BinaryTree/NodeTest.php b/tests/Technical/DataStructure/BinaryTree/NodeTest.php
new file mode 100644
index 0000000..eb7728a
--- /dev/null
+++ b/tests/Technical/DataStructure/BinaryTree/NodeTest.php
@@ -0,0 +1,25 @@
+val);
+ self::assertSame($leftNode, $node->left);
+ self::assertSame($rightNode, $node->right);
+ }
+}
diff --git a/tests/Technical/Factory/BinaryTreeFactoryTest.php b/tests/Technical/Factory/BinaryTreeFactoryTest.php
new file mode 100644
index 0000000..7448e26
--- /dev/null
+++ b/tests/Technical/Factory/BinaryTreeFactoryTest.php
@@ -0,0 +1,71 @@
+ new Node($val));
+
+ self::assertNotNull($root);
+ self::assertInstanceOf(Node::class, $root);
+ self::assertLevelOrderSameTreeNodeValues($expected, Helper::levelOrder($root), $this);
+ }
+
+ /**
+ * @dataProvider provideFromLevelOrderListWithEmptyListTestCases
+ */
+ public function testFromLevelOrderListWithEmptyList(array $data): void
+ {
+ $root = Factory::fromLevelOrderList($data, static fn(int $val) => new Node($val));
+
+ self::assertNull($root);
+ }
+
+ public function provideFromLevelOrderListWithEmptyListTestCases(): array
+ {
+ return [
+ 'Empty list' => [
+ 'data' => [],
+ ],
+ 'Only one null value' => [
+ 'data' => [null],
+ ],
+ ];
+ }
+
+ public function provideFromLevelOrderListTestCases(): array
+ {
+ return [
+ 'Basic case 1' => [
+ 'data' => [1,2,3,4,5,null,6,null,null,7,8,9],
+ 'expected' => [[1],[2,3],[4,5,6],[7,8,9]],
+ ],
+ 'Basic case 2' => [
+ 'data' => [1,2,3,4,5,6,7,8,9,null,null,null,10,11,12],
+ 'expected' => [[1],[2,3],[4,5,6,7],[8,9,10,11,12]],
+ ],
+ 'Basic case 3' => [
+ 'data' => [1,2,3,4,5,6,7,8,9,null,null,null,10,11,12,null,null,13,null,null,null,14,null,null,15,16,null,null,null,17],
+ 'expected' => [[1],[2,3],[4,5,6,7],[8,9,10,11,12],[13,14,15],[16,17]],
+ ],
+ ];
+ }
+}
From 69c02051f5f3b117edb7350b7038ed17f4c6e0f2 Mon Sep 17 00:00:00 2001
From: Yoanm <4410697+yoanm@users.noreply.github.com>
Date: Wed, 22 Oct 2025 13:08:57 +0200
Subject: [PATCH 2/5] Improve PHPDoc
---
src/Algorithm/BinarySearch.php | 18 ++++++++++++------
.../BinaryTree/RecursiveReversedTraversal.php | 10 ++++++----
.../BinaryTree/RecursiveTraversal.php | 15 ++++++++++-----
src/Algorithm/BinaryTree/ReversedTraversal.php | 12 ++++++++----
src/Algorithm/BinaryTree/Traversal.php | 18 ++++++++++++------
src/Factory/BinaryTreeFactory.php | 3 ++-
.../Technical/Algorithm/TraversalTestTrait.php | 6 ++++--
7 files changed, 54 insertions(+), 28 deletions(-)
diff --git a/src/Algorithm/BinarySearch.php b/src/Algorithm/BinarySearch.php
index e34cb30..11c2b29 100644
--- a/src/Algorithm/BinarySearch.php
+++ b/src/Algorithm/BinarySearch.php
@@ -21,8 +21,10 @@ class BinarySearch
* SC: ๐โฎ๐ทโฏ - Constant extra space
*
*
- * @param list|ArrayAccess $list โ Must be sorted in non-decreasing order (minโmax)!
- * May contain duplicates.
+ * @param array|ArrayAccess $list โ Must be a 0 indexed list, 0 to n consecutive indexes,
+ * sorted in non-decreasing order (minโmax)!
+ * May contain duplicates.
+ * @phpstan-param list|ArrayAccess $list
* @param int|float $target
* @param int $lowIdx Lookup start index.
* Default to 0 (head index).
@@ -70,8 +72,10 @@ public static function find(
* SC: ๐โฎ๐ทโฏ - Constant extra space
*
*
- * @param list|ArrayAccess $list โ Must be sorted in non-decreasing order (minโmax)!
- * May contain duplicates.
+ * @param array|ArrayAccess $list โ Must be a 0 indexed list, 0 to n consecutive indexes,
+ * sorted in non-decreasing order (minโmax)!
+ * May contain duplicates.
+ * @phpstan-param list|ArrayAccess $list
* @param int|float $target
* @param int $lowIdx Lookup start index.
* Default to 0 (head index).
@@ -119,8 +123,10 @@ public static function lowerBound(
* SC: ๐โฎ๐ทโฏ - Constant extra space
*
*
- * @param list|ArrayAccess $list โ Must be sorted in non-decreasing order (minโmax)!
- * May contain duplicates.
+ * @param array|ArrayAccess $list โ Must be a 0 indexed list, 0 to n consecutive indexes,
+ * sorted in non-decreasing order (minโmax)!
+ * May contain duplicates.
+ * @phpstan-param list|ArrayAccess $list
* @param int|float $target
* @param int $lowIdx Lookup start index.
* Default to 0 (head index).
diff --git a/src/Algorithm/BinaryTree/RecursiveReversedTraversal.php b/src/Algorithm/BinaryTree/RecursiveReversedTraversal.php
index a942bd9..80cd22d 100644
--- a/src/Algorithm/BinaryTree/RecursiveReversedTraversal.php
+++ b/src/Algorithm/BinaryTree/RecursiveReversedTraversal.php
@@ -35,8 +35,8 @@ class RecursiveReversedTraversal
*
* @param TNode $node
*
- * @return list
- *
+ * @return array
+ * @phpstan-return list
*/
public static function preOrder(Node $node): array
{
@@ -49,7 +49,8 @@ public static function preOrder(Node $node): array
*
* @param TNode $node
*
- * @return list
+ * @return array
+ * @phpstan-return list
*/
public static function inOrder(Node $node): array
{
@@ -62,7 +63,8 @@ public static function inOrder(Node $node): array
*
* @param TNode $node
*
- * @return list
+ * @return array
+ * @phpstan-return list
*/
public static function postOrder(Node $node): array
{
diff --git a/src/Algorithm/BinaryTree/RecursiveTraversal.php b/src/Algorithm/BinaryTree/RecursiveTraversal.php
index 51460d2..0c7fbe4 100644
--- a/src/Algorithm/BinaryTree/RecursiveTraversal.php
+++ b/src/Algorithm/BinaryTree/RecursiveTraversal.php
@@ -25,7 +25,8 @@ class RecursiveTraversal
*
* @param TNode $node
*
- * @return list
+ * @return array
+ * @phpstan-return list
*/
public static function preOrder(Node $node): array
{
@@ -38,7 +39,8 @@ public static function preOrder(Node $node): array
*
* @param TNode $node
*
- * @return list
+ * @return array
+ * @phpstan-return list
*/
public static function inOrder(Node $node): array
{
@@ -51,7 +53,8 @@ public static function inOrder(Node $node): array
*
* @param TNode $node
*
- * @return list
+ * @return array
+ * @phpstan-return list
*/
public static function postOrder(Node $node): array
{
@@ -75,7 +78,8 @@ public static function postOrder(Node $node): array
*
* SC: ๐โฎ๐โฏ - Due to the list storing every node for every level (see levelOrderHelper() function).
*
- * @return list> key is the level, value is the list of nodes for that level
+ * @return array> key is the level, value is the list of nodes for that level
+ * @phpstan-return list>
*/
public static function levelOrder(Node $root): array
{
@@ -196,7 +200,8 @@ public static function postOrderGenerator(Node $node): Generator
*
* @param TNode $node
*
- * @param array> $res key is the level, value is the list of nodes for that level
+ * @param array> $res key is the level, value is the list of nodes for that level
+ * @phpstan-param array> $res
*/
public static function levelOrderHelper(Node $node, array &$res, int $level = 0): void
{
diff --git a/src/Algorithm/BinaryTree/ReversedTraversal.php b/src/Algorithm/BinaryTree/ReversedTraversal.php
index ed51b50..466e470 100644
--- a/src/Algorithm/BinaryTree/ReversedTraversal.php
+++ b/src/Algorithm/BinaryTree/ReversedTraversal.php
@@ -35,7 +35,8 @@ class ReversedTraversal
*
* @param TNode $node
*
- * @return list
+ * @return array
+ * @phpstan-return list
*/
public static function preOrder(Node $node): array
{
@@ -47,7 +48,8 @@ public static function preOrder(Node $node): array
*
* @param TNode $node
*
- * @return list
+ * @return array
+ * @phpstan-return list
*/
public static function inOrder(Node $node): array
{
@@ -59,7 +61,8 @@ public static function inOrder(Node $node): array
*
* @param TNode $node
*
- * @return list
+ * @return array
+ * @phpstan-return list
*/
public static function postOrder(Node $node): array
{
@@ -71,7 +74,8 @@ public static function postOrder(Node $node): array
*
* @param TNode $node
*
- * @return list
+ * @return array
+ * @phpstan-return list
*/
public static function BFS(Node $node): array
{
diff --git a/src/Algorithm/BinaryTree/Traversal.php b/src/Algorithm/BinaryTree/Traversal.php
index 628dca6..d2f29bb 100644
--- a/src/Algorithm/BinaryTree/Traversal.php
+++ b/src/Algorithm/BinaryTree/Traversal.php
@@ -25,7 +25,8 @@ class Traversal
*
* @param TNode $node
*
- * @return list
+ * @return array
+ * @phpstan-return list
*/
public static function preOrder(Node $node): array
{
@@ -37,7 +38,8 @@ public static function preOrder(Node $node): array
*
* @param TNode $node
*
- * @return list
+ * @return array
+ * @phpstan-return list
*/
public static function inOrder(Node $node): array
{
@@ -49,7 +51,8 @@ public static function inOrder(Node $node): array
*
* @param TNode $node
*
- * @return list
+ * @return array
+ * @phpstan-return list
*/
public static function postOrder(Node $node): array
{
@@ -63,7 +66,8 @@ public static function postOrder(Node $node): array
*
* @param TNode $node
*
- * @return list
+ * @return array
+ * @phpstan-return list
*/
public static function BFS(Node $node): array
{
@@ -78,7 +82,8 @@ public static function BFS(Node $node): array
*
* @param TNode $node
*
- * @return list> key is the level, value is the list of nodes for that level
+ * @return array> key is the level, value is the list of nodes for that level
+ * @phpstan-return list>
*/
public static function levelOrder(Node $node): array
{
@@ -243,7 +248,8 @@ public static function postOrderGenerator(Node $node): Generator
*
* @param TNode $node
*
- * @return Generator> key is the level, value is the list of nodes for that level
+ * @return Generator> key is the level, value is the list of nodes for that level
+ * @phpstan-return Generator>
*/
public static function levelOrderGenerator(Node $node): Generator
{
diff --git a/src/Factory/BinaryTreeFactory.php b/src/Factory/BinaryTreeFactory.php
index a57e58d..375c8c0 100644
--- a/src/Factory/BinaryTreeFactory.php
+++ b/src/Factory/BinaryTreeFactory.php
@@ -18,7 +18,8 @@ class BinaryTreeFactory
/**
* @TODO write example regarding expecting list format !
*
- * @param list $list
+ * @param array $list โ Must be a 0 indexed list, 0 to n consecutive indexes
+ * @phpstan-param list $list
* @param TNodeCreator $nodeCreator
*
* @return null|Node|TNode
diff --git a/tests/Technical/Algorithm/TraversalTestTrait.php b/tests/Technical/Algorithm/TraversalTestTrait.php
index 1c6e019..ce74f0c 100644
--- a/tests/Technical/Algorithm/TraversalTestTrait.php
+++ b/tests/Technical/Algorithm/TraversalTestTrait.php
@@ -10,7 +10,8 @@
trait TraversalTestTrait
{
/**
- * @param list $expected
+ * @param array $expected
+ * @phpstan-param list $expected
* @param iterable $actual
*/
protected static function assertSameTreeNodeValues(array $expected, iterable $actual, Assert $assert): void
@@ -24,7 +25,8 @@ protected static function assertSameTreeNodeValues(array $expected, iterable $ac
}
/**
- * @param list> $expected
+ * @param array> $expected
+ * @phpstan-param array> $expected
* @param iterable> $actual
*/
protected static function assertLevelOrderSameTreeNodeValues(array $expected, iterable $actual, Assert $assert): void
From fd034a4b48c23b525fc8d34b77a0144931ddd6bb Mon Sep 17 00:00:00 2001
From: Yoanm <4410697+yoanm@users.noreply.github.com>
Date: Wed, 22 Oct 2025 18:00:53 +0200
Subject: [PATCH 3/5] Improve
---
.../BinaryTree/RecursiveReversedTraversal.php | 33 +++-----
.../BinaryTree/RecursiveTraversal.php | 48 ++++++------
.../BinaryTree/ReversedTraversal.php | 48 +++++-------
src/Algorithm/BinaryTree/Traversal.php | 78 ++++++++-----------
.../BinaryTree/NodeInterface.php | 9 +--
src/Factory/BinaryTreeFactory.php | 20 +++--
.../Algorithm/TraversalTestTrait.php | 7 +-
.../Factory/BinaryTreeFactoryTest.php | 2 +-
8 files changed, 105 insertions(+), 140 deletions(-)
diff --git a/src/Algorithm/BinaryTree/RecursiveReversedTraversal.php b/src/Algorithm/BinaryTree/RecursiveReversedTraversal.php
index 80cd22d..1448455 100644
--- a/src/Algorithm/BinaryTree/RecursiveReversedTraversal.php
+++ b/src/Algorithm/BinaryTree/RecursiveReversedTraversal.php
@@ -24,51 +24,48 @@
*
*
* @see \Yoanm\CommonDSA\Algorithm\BinaryTree\ReversedTraversal for iterative implementations.
- *
- *
- * @template TNode of Node The actual Node class
*/
class RecursiveReversedTraversal
{
/**
* @see \Yoanm\CommonDSA\Algorithm\BinaryTree\RecursiveReversedTraversal::preOrderGenerator()
*
- * @param TNode $node
*
- * @return array
- * @phpstan-return list
+ * @return array
+ * @phpstan-return list
*/
public static function preOrder(Node $node): array
{
// โ Do not preserve keys otherwise there is conflicting keys in case "yield from" is used !
+ // โ Do not preserve keys in order to always return a list !
return iterator_to_array(self::preOrderGenerator($node), false);
}
/**
* @see \Yoanm\CommonDSA\Algorithm\BinaryTree\RecursiveReversedTraversal::preOrderGenerator()
*
- * @param TNode $node
*
- * @return array
- * @phpstan-return list
+ * @return array
+ * @phpstan-return list
*/
public static function inOrder(Node $node): array
{
// โ Do not preserve keys otherwise there is conflicting keys in case "yield from" is used !
+ // โ Do not preserve keys in order to always return a list !
return iterator_to_array(self::inOrderGenerator($node), false);
}
/**
* @see \Yoanm\CommonDSA\Algorithm\BinaryTree\RecursiveReversedTraversal::inOrderGenerator()
*
- * @param TNode $node
*
- * @return array
- * @phpstan-return list
+ * @return array
+ * @phpstan-return list
*/
public static function postOrder(Node $node): array
{
// โ Do not preserve keys otherwise there is conflicting keys in case "yield from" is used !
+ // โ Do not preserve keys in order to always return a list !
return iterator_to_array(self::postOrderGenerator($node), false);
}
@@ -88,9 +85,7 @@ public static function postOrder(Node $node): array
* SC: ๐โฎ๐ฉโฏ - Due to the stack trace (recursive calls) + extra space for inner Generator class instances !
*
*
- * @param TNode $node
- *
- * @return Generator
+ * @return Generator
*/
public static function preOrderGenerator(Node $node): Generator
{
@@ -120,9 +115,7 @@ public static function preOrderGenerator(Node $node): Generator
* SC: ๐โฎ๐ฉโฏ - Due to the stack trace (recursive calls) + extra space for inner Generator class instances !
*
*
- * @param TNode $node
- *
- * @return Generator
+ * @return Generator
*/
public static function inOrderGenerator(Node $node): Generator
{
@@ -153,9 +146,7 @@ public static function inOrderGenerator(Node $node): Generator
* SC: ๐โฎ๐ฉโฏ - Due to the stack trace (recursive calls) + extra space for inner Generator class instances !
*
*
- * @param TNode $node
- *
- * @return Generator
+ * @return Generator
*/
public static function postOrderGenerator(Node $node): Generator
{
diff --git a/src/Algorithm/BinaryTree/RecursiveTraversal.php b/src/Algorithm/BinaryTree/RecursiveTraversal.php
index 0c7fbe4..bac2df0 100644
--- a/src/Algorithm/BinaryTree/RecursiveTraversal.php
+++ b/src/Algorithm/BinaryTree/RecursiveTraversal.php
@@ -14,51 +14,48 @@
*
*
* @see \Yoanm\CommonDSA\Algorithm\BinaryTree\Traversal for iterative implementations.
- *
- *
- * @template TNode of Node The actual Node class
*/
class RecursiveTraversal
{
/**
* @see \Yoanm\CommonDSA\Algorithm\BinaryTree\RecursiveTraversal::preOrderGenerator()
*
- * @param TNode $node
*
- * @return array
- * @phpstan-return list
+ * @return array
+ * @phpstan-return list
*/
public static function preOrder(Node $node): array
{
// โ Do not preserve keys otherwise there is conflicting keys in case "yield from" is used !
+ // โ Do not preserve keys in order to always return a list !
return iterator_to_array(self::preOrderGenerator($node), false);
}
/**
* @see \Yoanm\CommonDSA\Algorithm\BinaryTree\RecursiveTraversal::preOrderGenerator()
*
- * @param TNode $node
*
- * @return array
- * @phpstan-return list
+ * @return array
+ * @phpstan-return list
*/
public static function inOrder(Node $node): array
{
// โ Do not preserve keys otherwise there is conflicting keys in case "yield from" is used !
+ // โ Do not preserve keys in order to always return a list !
return iterator_to_array(self::inOrderGenerator($node), false);
}
/**
* @see \Yoanm\CommonDSA\Algorithm\BinaryTree\RecursiveTraversal::inOrderGenerator()
*
- * @param TNode $node
*
- * @return array
- * @phpstan-return list
+ * @return array
+ * @phpstan-return list
*/
public static function postOrder(Node $node): array
{
// โ Do not preserve keys otherwise there is conflicting keys in case "yield from" is used !
+ // โ Do not preserve keys in order to always return a list !
return iterator_to_array(self::postOrderGenerator($node), false);
}
@@ -78,15 +75,20 @@ public static function postOrder(Node $node): array
*
* SC: ๐โฎ๐โฏ - Due to the list storing every node for every level (see levelOrderHelper() function).
*
- * @return array> key is the level, value is the list of nodes for that level
- * @phpstan-return list>
+ *
+ * @return array> key is the level, value is the list of nodes for that level
+ * @phpstan-return list>
*/
- public static function levelOrder(Node $root): array
+ public static function levelOrder(Node $node): array
{
$res = [];
- self::levelOrderHelper($root, $res);
+ self::levelOrderHelper($node, $res);
+ /**
+ * No easy way to tell PHPStan that $res is a list in case level is not provided :/
+ * @var list> $res
+ */
return $res;
}
@@ -104,9 +106,8 @@ public static function levelOrder(Node $root): array
*
* SC: ๐โฎ๐ฉโฏ - Due to the stack trace (recursive calls) + extra space for inner Generator class instances !
*
- * @param TNode $node
*
- * @return Generator
+ * @return Generator
*/
public static function preOrderGenerator(Node $node): Generator
{
@@ -134,9 +135,8 @@ public static function preOrderGenerator(Node $node): Generator
*
* SC: ๐โฎ๐ฉโฏ - Due to the stack trace (recursive calls) + extra space for inner Generator class instances !
*
- * @param TNode $node
*
- * @return Generator
+ * @return Generator
*/
public static function inOrderGenerator(Node $node): Generator
{
@@ -165,9 +165,8 @@ public static function inOrderGenerator(Node $node): Generator
*
* SC: ๐โฎ๐ฉโฏ - Due to the stack trace (recursive calls) + extra space for inner Generator class instances !
*
- * @param TNode $node
*
- * @return Generator
+ * @return Generator
*/
public static function postOrderGenerator(Node $node): Generator
{
@@ -198,10 +197,9 @@ public static function postOrderGenerator(Node $node): Generator
*
* SC: ๐โฎhโฏ - Due to the stack trace (recursive calls)
*
- * @param TNode $node
*
- * @param array> $res key is the level, value is the list of nodes for that level
- * @phpstan-param array> $res
+ * @param array> $res key is the level, value is the list of nodes for that level
+ * @phpstan-param array> $res
*/
public static function levelOrderHelper(Node $node, array &$res, int $level = 0): void
{
diff --git a/src/Algorithm/BinaryTree/ReversedTraversal.php b/src/Algorithm/BinaryTree/ReversedTraversal.php
index 466e470..5d2a08f 100644
--- a/src/Algorithm/BinaryTree/ReversedTraversal.php
+++ b/src/Algorithm/BinaryTree/ReversedTraversal.php
@@ -24,62 +24,59 @@
*
*
* @see \Yoanm\CommonDSA\Algorithm\BinaryTree\RecursiveReversedTraversal for recursive implementations.
- *
- *
- * @template TNode of Node The actual Node class
*/
class ReversedTraversal
{
/**
* @see \Yoanm\CommonDSA\Algorithm\BinaryTree\RevervedTraversal::preOrderGenerator()
*
- * @param TNode $node
*
- * @return array
- * @phpstan-return list
+ * @return array
+ * @phpstan-return list
*/
public static function preOrder(Node $node): array
{
- return iterator_to_array(self::preOrderGenerator($node));
+ // โ Do not preserve keys in order to always return a list !
+ return iterator_to_array(self::preOrderGenerator($node), false);
}
/**
* @see \Yoanm\CommonDSA\Algorithm\BinaryTree\RevervedTraversal::inOrderGenerator()
*
- * @param TNode $node
*
- * @return array
- * @phpstan-return list
+ * @return array
+ * @phpstan-return list
*/
public static function inOrder(Node $node): array
{
- return iterator_to_array(self::inOrderGenerator($node));
+ // โ Do not preserve keys in order to always return a list !
+ return iterator_to_array(self::inOrderGenerator($node), false);
}
/**
* @see \Yoanm\CommonDSA\Algorithm\BinaryTree\RevervedTraversal::postOrderGenerator()
*
- * @param TNode $node
*
- * @return array
- * @phpstan-return list
+ * @return array
+ * @phpstan-return list
*/
public static function postOrder(Node $node): array
{
- return iterator_to_array(self::postOrderGenerator($node));
+ // โ Do not preserve keys in order to always return a list !
+ return iterator_to_array(self::postOrderGenerator($node), false);
}
/**
* @see \Yoanm\CommonDSA\Algorithm\BinaryTree\RevervedTraversal::BFSGenerator()
*
- * @param TNode $node
*
- * @return array
- * @phpstan-return list
+ * @return array
+ * @phpstan-return list
*/
public static function BFS(Node $node): array
{
- return iterator_to_array(self::BFSGenerator($node));
+ // โ Do not preserve keys in order to always return a list !
+ return iterator_to_array(self::BFSGenerator($node), false);
}
/**
@@ -98,9 +95,7 @@ public static function BFS(Node $node): array
* SC: ๐โฎ๐ฉโฏ - Due to the stack storing parent nodes path up to the root node.
*
*
- * @param TNode $node
- *
- * @return Generator
+ * @return Generator
*/
public static function preOrderGenerator(Node $node): Generator
{
@@ -138,8 +133,6 @@ public static function preOrderGenerator(Node $node): Generator
* SC: ๐โฎ๐ฉโฏ - Due to the stack storing parent nodes path up to the root node.
*
*
- * @param TNode $node
- *
* @return Generator
*/
public static function inOrderGenerator(Node $node): Generator
@@ -181,8 +174,6 @@ public static function inOrderGenerator(Node $node): Generator
* SC: ๐โฎ๐ฉโฏ - Due to the stack storing parent nodes.
*
*
- * @param TNode $node
- *
* @return Generator
*/
public static function postOrderGenerator(Node $node): Generator
@@ -231,12 +222,11 @@ public static function postOrderGenerator(Node $node): Generator
* SC: ๐โฎ๐โฏ - Due to the queue storing current level parent nodes
*
*
- * @param TNode $node
- *
- * @return Generator
+ * @return Generator
*/
public static function BFSGenerator(Node $node): Generator
{
+ /** @var SplQueue $queue */
$queue = new SplQueue();
$queue->enqueue($node);
diff --git a/src/Algorithm/BinaryTree/Traversal.php b/src/Algorithm/BinaryTree/Traversal.php
index d2f29bb..9581f27 100644
--- a/src/Algorithm/BinaryTree/Traversal.php
+++ b/src/Algorithm/BinaryTree/Traversal.php
@@ -14,80 +14,74 @@
*
*
* @see \Yoanm\CommonDSA\Algorithm\BinaryTree\RecursiveTraversal for recursive implementations.
- *
- *
- * @template TNode of Node The actual Node class
*/
class Traversal
{
/**
* @see \Yoanm\CommonDSA\Algorithm\BinaryTree\Traversal::preOrderGenerator()
*
- * @param TNode $node
+
*
- * @return array
- * @phpstan-return list
+ * @return Node[]
+ * @phpstan-return list
*/
public static function preOrder(Node $node): array
{
- return iterator_to_array(self::preOrderGenerator($node));
+ // โ Do not preserve keys in order to always return a list !
+ return iterator_to_array(self::preOrderGenerator($node), false);
}
/**
* @see \Yoanm\CommonDSA\Algorithm\BinaryTree\Traversal::inOrderGenerator()
*
- * @param TNode $node
*
- * @return array
- * @phpstan-return list
+ * @return Node[]
+ * @phpstan-return list
*/
public static function inOrder(Node $node): array
{
- return iterator_to_array(self::inOrderGenerator($node));
+ // โ Do not preserve keys in order to always return a list !
+ return iterator_to_array(self::inOrderGenerator($node), false);
}
/**
* @see \Yoanm\CommonDSA\Algorithm\BinaryTree\Traversal::postOrderGenerator()
*
- * @param TNode $node
*
- * @return array
- * @phpstan-return list
+ * @return Node[]
+ * @phpstan-return list
*/
public static function postOrder(Node $node): array
{
- return iterator_to_array(self::postOrderGenerator($node));
+ // โ Do not preserve keys in order to always return a list !
+ return iterator_to_array(self::postOrderGenerator($node), false);
}
/**
* @see \Yoanm\CommonDSA\Algorithm\BinaryTree\Traversal::BFSGenerator()
*
- * @see \Yoanm\CommonDSA\Algorithm\BinaryTree\Traversal::levelOrder() if node level must be known !
*
- * @param TNode $node
- *
- * @return array
- * @phpstan-return list
+ * @return Node[]
+ * @phpstan-return list
*/
public static function BFS(Node $node): array
{
- return iterator_to_array(self::BFSGenerator($node));
+ // โ Do not preserve keys in order to always return a list !
+ return iterator_to_array(self::BFSGenerator($node), false);
}
/**
* @see \Yoanm\CommonDSA\Algorithm\BinaryTree\Traversal::levelOrderGenerator()
- *
- *
* @see \Yoanm\CommonDSA\Algorithm\BinaryTree\Traversal::BFS() if node level isn't useful.
*
- * @param TNode $node
*
- * @return array> key is the level, value is the list of nodes for that level
- * @phpstan-return list>
+ * @return array> key is the level, value is the list of nodes for that level
+ * @phpstan-return list>
*/
public static function levelOrder(Node $node): array
{
- return iterator_to_array(self::levelOrderGenerator($node));
+ // โ Do not preserve keys in order to always return a list !
+ return iterator_to_array(self::levelOrderGenerator($node), false);
}
/**
@@ -106,17 +100,16 @@ public static function levelOrder(Node $node): array
* SC: ๐โฎ๐ฉโฏ - Due to the stack storing parent nodes path up to the root node.
*
*
- * @param TNode $node
- *
- * @return Generator
+ * @return Generator
*/
public static function preOrderGenerator(Node $node): Generator
{
+ /** @var SplStack $stack */
$stack = new SplStack();
$stack->push($node);
+
while (!$stack->isEmpty()) {
- /** @var Node $currentNode */
$currentNode = $stack->pop();
yield $currentNode;
@@ -145,14 +138,13 @@ public static function preOrderGenerator(Node $node): Generator
*
* SC: ๐โฎ๐ฉโฏ - Due to the stack storing parent nodes path up to the root node.
*
- *
- * @param TNode $node
- *
* @return Generator
*/
public static function inOrderGenerator(Node $node): Generator
{
+ /** @var SplStack $stack */
$stack = new SplStack();
+
$currentNode = $node;
while (null !== $currentNode || !$stack->isEmpty()) {
while (null !== $currentNode) {
@@ -162,7 +154,6 @@ public static function inOrderGenerator(Node $node): Generator
// Current node becomes the leftmost leaf found
// (or the same current node in case it doesn't have left node!)
- /** @var Node $currentNode */
$currentNode = $stack->pop();
yield $currentNode;
@@ -188,12 +179,11 @@ public static function inOrderGenerator(Node $node): Generator
* SC: ๐โฎ๐ฉโฏ - Due to the stack storing parent nodes path up to the root node.
*
*
- * @param TNode $node
- *
* @return Generator
*/
public static function postOrderGenerator(Node $node): Generator
{
+ /** @var SplStack $stack */
$stack = new SplStack();
$currentNode = $node;
@@ -206,7 +196,6 @@ public static function postOrderGenerator(Node $node): Generator
$currentNode = $currentNode->left;
}
- /** @var Node $currentNode */
$currentNode = $stack->pop();
if (!$stack->isEmpty() && $currentNode->right === $stack->top()) {
@@ -246,16 +235,16 @@ public static function postOrderGenerator(Node $node): Generator
* @see \Yoanm\CommonDSA\Algorithm\BinaryTree\Traversal::BFSGenerator() if node level isn't useful.
*
*
- * @param TNode $node
- *
- * @return Generator> key is the level, value is the list of nodes for that level
- * @phpstan-return Generator>
+ * @return Generator> key is the level, value is the list of nodes for that level
+ * @phpstan-return Generator>
*/
public static function levelOrderGenerator(Node $node): Generator
{
+ /** @var SplQueue $queue */
$queue = new SplQueue();
$queue->enqueue($node);
+
while (!$queue->isEmpty()) {
$nodeList = [];
$currentLevelNodeCounter = $queue->count();
@@ -296,12 +285,11 @@ public static function levelOrderGenerator(Node $node): Generator
* @see \Yoanm\CommonDSA\Algorithm\BinaryTree\Traversal::levelOrderGenerator() if node level must be known !
*
*
- * @param TNode $node
- *
- * @return Generator
+ * @return Generator
*/
public static function BFSGenerator(Node $node): Generator
{
+ /** @var SplQueue $queue */
$queue = new SplQueue();
$queue->enqueue($node);
diff --git a/src/DataStructure/BinaryTree/NodeInterface.php b/src/DataStructure/BinaryTree/NodeInterface.php
index e221a06..c061966 100644
--- a/src/DataStructure/BinaryTree/NodeInterface.php
+++ b/src/DataStructure/BinaryTree/NodeInterface.php
@@ -5,12 +5,11 @@
/**
* Binary tree node interface.
*
- *
- * @property null|NodeInterface $left
- * @property null|NodeInterface $right
+ * @property ?NodeInterface $left
+ * @property ?NodeInterface $right
*/
interface NodeInterface
{
- // public null|NodeInterface $left {get; set;} // @TODO uncomment once php 8.4 minimum is required !
- // public null|NodeInterface $right {get; set;} // @TODO uncomment once php 8.4 minimum is required !
+ // public ?NodeInterface $left {get; set;} // @TODO uncomment once php 8.4 minimum is required !
+ // public ?NodeInterface $right {get; set;} // @TODO uncomment once php 8.4 minimum is required !
}
diff --git a/src/Factory/BinaryTreeFactory.php b/src/Factory/BinaryTreeFactory.php
index 375c8c0..b5315b3 100644
--- a/src/Factory/BinaryTreeFactory.php
+++ b/src/Factory/BinaryTreeFactory.php
@@ -7,24 +7,22 @@
use SplQueue;
use Yoanm\CommonDSA\DataStructure\BinaryTree\NodeInterface as Node;
-/**
- * @template TNode of Node The actual Node class
- * @template TValue of mixed The actual Node value type
- *
- * @phpstan-type TNodeCreator callable(TValue $v): TNode
- */
class BinaryTreeFactory
{
/**
* @TODO write example regarding expecting list format !
*
- * @param array $list โ Must be a 0 indexed list, 0 to n consecutive indexes
+ * @template TNode of Node The actual Node class
+ * @template TValue of mixed The actual Node value type
+ *
+ *
+ * @param mixed[] $list โ Must be a 0 indexed list, 0 to n consecutive indexes
* @phpstan-param list $list
- * @param TNodeCreator $nodeCreator
+ * @phpstan-param callable(TValue $v): TNode $nodeCreator
*
- * @return null|Node|TNode
+ * @phpstan-return ?TNode
*/
- public static function fromLevelOrderList(array $list, callable $nodeCreator): null|Node
+ public static function fromLevelOrderList(array $list, callable $nodeCreator): ?Node
{
$tailIdx = count($list) - 1;
if ($tailIdx < 0 || (0 === $tailIdx && null === $list[0])) {
@@ -33,7 +31,7 @@ public static function fromLevelOrderList(array $list, callable $nodeCreator): n
$queue = new SplQueue();
- $root = call_user_func($nodeCreator, $list[0]);
+ $root = call_user_func($nodeCreator, $list[0]); // @phpstan-ignore argument.type
$queue->enqueue($root);
$idx = 1; // Root value already managed, hence 1 rather 0 !
diff --git a/tests/Technical/Algorithm/TraversalTestTrait.php b/tests/Technical/Algorithm/TraversalTestTrait.php
index ce74f0c..29f7b98 100644
--- a/tests/Technical/Algorithm/TraversalTestTrait.php
+++ b/tests/Technical/Algorithm/TraversalTestTrait.php
@@ -32,11 +32,12 @@ protected static function assertSameTreeNodeValues(array $expected, iterable $ac
protected static function assertLevelOrderSameTreeNodeValues(array $expected, iterable $actual, Assert $assert): void
{
$newActual = [];
- foreach ($actual as $key => $nodeList) {
- $newActual[$key] = [];
+ foreach ($actual as $nodeList) {
+ $tmpList = [];
foreach ($nodeList as $node) {
- $newActual[$key][] = $node->val;
+ $tmpList[] = $node->val;
}
+ $newActual[] = $tmpList;
}
$assert::assertSame($expected, $newActual);
diff --git a/tests/Technical/Factory/BinaryTreeFactoryTest.php b/tests/Technical/Factory/BinaryTreeFactoryTest.php
index 7448e26..477ecef 100644
--- a/tests/Technical/Factory/BinaryTreeFactoryTest.php
+++ b/tests/Technical/Factory/BinaryTreeFactoryTest.php
@@ -25,7 +25,7 @@ public function testFromLevelOrderList(array $data, array $expected): void
$root = Factory::fromLevelOrderList($data, static fn(int $val) => new Node($val));
self::assertNotNull($root);
- self::assertInstanceOf(Node::class, $root);
+ self::assertInstanceOf(Node::class, $root); // @phpstan-ignore staticMethod.alreadyNarrowedType
self::assertLevelOrderSameTreeNodeValues($expected, Helper::levelOrder($root), $this);
}
From ee0261590fc02455d4f27e1e46a5b8291d02775f Mon Sep 17 00:00:00 2001
From: Yoanm <4410697+yoanm@users.noreply.github.com>
Date: Wed, 22 Oct 2025 18:11:03 +0200
Subject: [PATCH 4/5] Fix
---
src/Algorithm/BinaryTree/RecursiveTraversal.php | 2 +-
tests/Technical/Algorithm/TraversalTestTrait.php | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/Algorithm/BinaryTree/RecursiveTraversal.php b/src/Algorithm/BinaryTree/RecursiveTraversal.php
index bac2df0..773d58c 100644
--- a/src/Algorithm/BinaryTree/RecursiveTraversal.php
+++ b/src/Algorithm/BinaryTree/RecursiveTraversal.php
@@ -87,7 +87,7 @@ public static function levelOrder(Node $node): array
/**
* No easy way to tell PHPStan that $res is a list in case level is not provided :/
- * @var list> $res
+ * @phpstan-var list> $res
*/
return $res;
}
diff --git a/tests/Technical/Algorithm/TraversalTestTrait.php b/tests/Technical/Algorithm/TraversalTestTrait.php
index 29f7b98..3ce58f9 100644
--- a/tests/Technical/Algorithm/TraversalTestTrait.php
+++ b/tests/Technical/Algorithm/TraversalTestTrait.php
@@ -5,7 +5,7 @@
namespace Tests\Technical\Algorithm;
use PHPUnit\Framework\Assert;
-use Yoanm\CommonDSA\DataStructure\NAryTree\Node;
+use Yoanm\CommonDSA\DataStructure\BinaryTree\Node;
trait TraversalTestTrait
{
From 1b5005eed829b3f95a46838bbdf578d0f636a8fe Mon Sep 17 00:00:00 2001
From: Yoanm <4410697+yoanm@users.noreply.github.com>
Date: Wed, 22 Oct 2025 18:19:30 +0200
Subject: [PATCH 5/5] Minor typo
---
src/Algorithm/BinaryTree/RecursiveTraversal.php | 6 +++---
src/Algorithm/BinaryTree/ReversedTraversal.php | 6 +++---
src/Algorithm/BinaryTree/Traversal.php | 6 +++---
src/Factory/BinaryTreeFactory.php | 2 +-
4 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/src/Algorithm/BinaryTree/RecursiveTraversal.php b/src/Algorithm/BinaryTree/RecursiveTraversal.php
index 773d58c..114033b 100644
--- a/src/Algorithm/BinaryTree/RecursiveTraversal.php
+++ b/src/Algorithm/BinaryTree/RecursiveTraversal.php
@@ -93,7 +93,7 @@ public static function levelOrder(Node $node): array
}
/**
- * Pre-order: N->L->R => Node, then Left children, then Right children
+ * Pre-order: N->L->R => Node, then Left child, then Right child
*
*
* ### Time/Space complexity
@@ -122,7 +122,7 @@ public static function preOrderGenerator(Node $node): Generator
}
/**
- * In-order (=DFS): L->N->R => Left children, then Node, then Right children
+ * In-order (=DFS): L->N->R => Left child, then Node, then Right child
*
*
* ### Time/Space complexity
@@ -152,7 +152,7 @@ public static function inOrderGenerator(Node $node): Generator
}
/**
- * Post-order: L->R->N => Left children, then Right children, then Node
+ * Post-order: L->R->N => Left child, then Right child, then Node
*
*
* ### Time/Space complexity
diff --git a/src/Algorithm/BinaryTree/ReversedTraversal.php b/src/Algorithm/BinaryTree/ReversedTraversal.php
index 5d2a08f..6428d18 100644
--- a/src/Algorithm/BinaryTree/ReversedTraversal.php
+++ b/src/Algorithm/BinaryTree/ReversedTraversal.php
@@ -99,11 +99,11 @@ public static function BFS(Node $node): array
*/
public static function preOrderGenerator(Node $node): Generator
{
+ /** @var SplStack $stack */
$stack = new SplStack();
$stack->push($node);
while (!$stack->isEmpty()) {
- /** @var Node $currentNode */
$currentNode = $stack->pop();
yield $currentNode;
@@ -137,6 +137,7 @@ public static function preOrderGenerator(Node $node): Generator
*/
public static function inOrderGenerator(Node $node): Generator
{
+ /** @var SplStack $stack */
$stack = new SplStack();
$currentNode = $node;
@@ -148,7 +149,6 @@ public static function inOrderGenerator(Node $node): Generator
// Current node becomes the rightmost leaf found
// (or the same current node in case it doesn't have right node!)
- /** @var Node $currentNode */
$currentNode = $stack->pop();
yield $currentNode;
@@ -178,6 +178,7 @@ public static function inOrderGenerator(Node $node): Generator
*/
public static function postOrderGenerator(Node $node): Generator
{
+ /** @var SplStack $stack */
$stack = new SplStack();
$currentNode = $node;
@@ -190,7 +191,6 @@ public static function postOrderGenerator(Node $node): Generator
$currentNode = $currentNode->right;
}
- /** @var Node $currentNode */
$currentNode = $stack->pop();
if (!$stack->isEmpty() && $currentNode->left === $stack->top()) {
diff --git a/src/Algorithm/BinaryTree/Traversal.php b/src/Algorithm/BinaryTree/Traversal.php
index 9581f27..27e41c9 100644
--- a/src/Algorithm/BinaryTree/Traversal.php
+++ b/src/Algorithm/BinaryTree/Traversal.php
@@ -85,7 +85,7 @@ public static function levelOrder(Node $node): array
}
/**
- * Pre-order: N->L->R => Node, then Left children, then Right children.
+ * Pre-order: N->L->R => Node, then Left child, then Right child.
*
*
*
@@ -124,7 +124,7 @@ public static function preOrderGenerator(Node $node): Generator
}
/**
- * In-order (=DFS): L->N->R => Left children, then Node, then Right children.
+ * In-order (=DFS): L->N->R => Left child, then Node, then Right child.
*
*
*
@@ -164,7 +164,7 @@ public static function inOrderGenerator(Node $node): Generator
}
/**
- * Post-order: L->R->N => Left children, then Right children, then Node.
+ * Post-order: L->R->N => Left child, then Right child, then Node.
*
*
*
diff --git a/src/Factory/BinaryTreeFactory.php b/src/Factory/BinaryTreeFactory.php
index b5315b3..1c4e296 100644
--- a/src/Factory/BinaryTreeFactory.php
+++ b/src/Factory/BinaryTreeFactory.php
@@ -29,6 +29,7 @@ public static function fromLevelOrderList(array $list, callable $nodeCreator): ?
return null;
}
+ /** @var SplQueue $queue */
$queue = new SplQueue();
$root = call_user_func($nodeCreator, $list[0]); // @phpstan-ignore argument.type
@@ -36,7 +37,6 @@ public static function fromLevelOrderList(array $list, callable $nodeCreator): ?
$idx = 1; // Root value already managed, hence 1 rather 0 !
while ($idx <= $tailIdx) {
- /** @var Node|TNode $parentNode */
$parentNode = $queue->dequeue();
// 1. Manage left node value