1 | <?php |
||||
2 | declare(strict_types=1); |
||||
3 | /* |
||||
4 | * Copyright (C) 2019 Sebastian Böttger <[email protected]> |
||||
5 | * You may use, distribute and modify this code under the |
||||
6 | * terms of the MIT license. |
||||
7 | * |
||||
8 | * You should have received a copy of the MIT license with |
||||
9 | * this file. If not, please visit: https://opensource.org/licenses/mit-license.php |
||||
10 | */ |
||||
11 | |||||
12 | namespace Seboettg\Forest; |
||||
13 | |||||
14 | use Seboettg\Forest\BinaryTree\BinaryNode; |
||||
15 | use Seboettg\Forest\BinaryTree\BinaryNodeInterface; |
||||
16 | use Seboettg\Forest\BinaryTree\BinaryTreeInterface; |
||||
17 | use Seboettg\Forest\Item\ItemInterface; |
||||
18 | use Seboettg\Forest\General\TreeTraversalTrait; |
||||
19 | |||||
20 | /** |
||||
21 | * Class BinaryTree |
||||
22 | * A binary tree is a recursive data structure where each node can have 2 children at most. |
||||
23 | * |
||||
24 | * @package Seboettg\Forest |
||||
25 | */ |
||||
26 | class BinaryTree implements BinaryTree\BinaryTreeInterface |
||||
27 | { |
||||
28 | use TreeTraversalTrait; |
||||
29 | |||||
30 | /** |
||||
31 | * @var BinaryNodeInterface |
||||
32 | */ |
||||
33 | protected $root; |
||||
34 | |||||
35 | /** |
||||
36 | * @var int |
||||
37 | */ |
||||
38 | protected $elementCount = 0; |
||||
39 | |||||
40 | protected $itemType; |
||||
41 | |||||
42 | 37 | public function __construct(?string $itemType = null) |
|||
43 | { |
||||
44 | 37 | $this->itemType = $itemType; |
|||
45 | 37 | } |
|||
46 | |||||
47 | |||||
48 | /** |
||||
49 | * @param ItemInterface $value |
||||
50 | * |
||||
51 | * @return BinaryNodeInterface |
||||
52 | */ |
||||
53 | 5 | final public function search(ItemInterface $value): ?BinaryNodeInterface |
|||
54 | { |
||||
55 | 5 | return $this->searchRecursive($value, $this->root); |
|||
56 | } |
||||
57 | |||||
58 | /** |
||||
59 | * @param ItemInterface $value |
||||
60 | * @param BinaryNodeInterface $node |
||||
61 | * @return BinaryNodeInterface |
||||
62 | */ |
||||
63 | 5 | final protected function searchRecursive(ItemInterface $value, BinaryNodeInterface $node = null): ?BinaryNodeInterface |
|||
64 | { |
||||
65 | 5 | if ($node === null) { |
|||
66 | 3 | return null; |
|||
67 | 5 | } else if ($node->getItem()->compareTo($value) === 0) { |
|||
68 | 3 | return $node; |
|||
69 | 5 | } else if ($node->getItem()->compareTo($value) > 0) { |
|||
70 | 5 | return $this->searchRecursive($value, $node->getLeft()); |
|||
71 | } else { |
||||
72 | 5 | return $this->searchRecursive($value, $node->getRight()); |
|||
73 | } |
||||
74 | } |
||||
75 | |||||
76 | /** |
||||
77 | * @param mixed $value |
||||
78 | * @return BinaryTree |
||||
79 | */ |
||||
80 | 37 | public function insert($value): BinaryTreeInterface |
|||
81 | { |
||||
82 | 37 | ++$this->elementCount; |
|||
83 | 37 | if (!empty($this->itemType) && is_object($value) && get_class($value) === $this->itemType) { |
|||
84 | 12 | $this->insertItem($value); |
|||
85 | } else { |
||||
86 | 25 | if (empty($this->itemType)) { |
|||
87 | 25 | $this->insertItem($value); |
|||
88 | } else { |
||||
89 | $item = new $this->itemType($value); |
||||
90 | $this->insertItem($item); |
||||
91 | } |
||||
92 | } |
||||
93 | 37 | return $this; |
|||
94 | } |
||||
95 | |||||
96 | 12 | public function remove($value): BinaryTreeInterface |
|||
97 | { |
||||
98 | |||||
99 | 12 | if (!empty($this->itemType) && is_object($value) && get_class($value) === $this->itemType) { |
|||
100 | $this->removeItem($value); |
||||
101 | } else { |
||||
102 | 12 | if (empty($this->itemType)) { |
|||
103 | $this->removeItem($value); |
||||
104 | } else { |
||||
105 | 12 | $item = new $this->itemType($value); |
|||
106 | 12 | $this->removeItem($item); |
|||
107 | } |
||||
108 | } |
||||
109 | 12 | --$this->elementCount; |
|||
110 | 12 | return $this; |
|||
111 | } |
||||
112 | |||||
113 | /** |
||||
114 | * @param $value |
||||
115 | */ |
||||
116 | 23 | protected function insertItem($value): void |
|||
117 | { |
||||
118 | 23 | if ($this->root === null) { |
|||
119 | 23 | $this->root = new BinaryNode($value); |
|||
120 | } else { |
||||
121 | 23 | $this->insertRecursive($this->root, $value); |
|||
122 | } |
||||
123 | 23 | } |
|||
124 | |||||
125 | /** |
||||
126 | * @param BinaryNodeInterface $node |
||||
127 | * @param ItemInterface $value |
||||
128 | * @return void |
||||
129 | */ |
||||
130 | 23 | final private function insertRecursive(BinaryNodeInterface $node, ItemInterface $value): void |
|||
131 | { |
||||
132 | 23 | if ($node->getItem()->compareTo($value) >= 0) { |
|||
133 | 23 | if ($node->getLeft() === null) { |
|||
134 | 23 | $node->setLeft(new BinaryNode($value)); |
|||
135 | } else { |
||||
136 | 23 | $this->insertRecursive($node->getLeft(), $value); |
|||
137 | } |
||||
138 | } else { |
||||
139 | 23 | if ($node->getRight() === null) { |
|||
140 | 23 | $node->setRight(new BinaryNode($value)); |
|||
141 | } else { |
||||
142 | 23 | $this->insertRecursive($node->getRight(), $value); |
|||
143 | } |
||||
144 | } |
||||
145 | 23 | } |
|||
146 | |||||
147 | /** |
||||
148 | * @return int number of nodes in the tree |
||||
149 | */ |
||||
150 | 4 | final public function count(): int |
|||
151 | { |
||||
152 | 4 | return $this->elementCount; |
|||
153 | } |
||||
154 | |||||
155 | /** |
||||
156 | * @return int height of the tree |
||||
157 | */ |
||||
158 | 4 | final public function getHeight(): int |
|||
159 | { |
||||
160 | 4 | return $this->root->getHeight(); |
|||
161 | } |
||||
162 | |||||
163 | /** |
||||
164 | * @return BinaryNodeInterface |
||||
165 | */ |
||||
166 | 13 | final public function getRootNode() |
|||
167 | { |
||||
168 | 13 | return $this->root; |
|||
169 | } |
||||
170 | |||||
171 | /** |
||||
172 | * @param BinaryNodeInterface $node |
||||
173 | * @return BinaryNodeInterface |
||||
174 | */ |
||||
175 | 6 | protected function getLeftMostLeaf(BinaryNodeInterface $node): BinaryNodeInterface |
|||
176 | { |
||||
177 | 6 | $current = $node; |
|||
178 | 6 | while (null !== $current->getLeft()) { |
|||
179 | 4 | $current = $current->getLeft(); |
|||
180 | } |
||||
181 | 6 | return $current; |
|||
182 | } |
||||
183 | |||||
184 | /** |
||||
185 | * @param ItemInterface $item |
||||
186 | */ |
||||
187 | 12 | protected function removeItem(ItemInterface $item): void |
|||
188 | { |
||||
189 | 12 | if ($this->root->getItem()->compareTo($item) !== 0) { |
|||
190 | 11 | $this->removeItemRecursive($item, $this->root); |
|||
191 | 11 | return; |
|||
192 | } |
||||
193 | 1 | $this->reassignSubtree($this->root); |
|||
194 | 1 | } |
|||
195 | |||||
196 | /** |
||||
197 | * @param ItemInterface $item |
||||
198 | * @param BinaryNodeInterface|null $node |
||||
199 | */ |
||||
200 | 11 | protected function removeItemRecursive(ItemInterface $item, ?BinaryNodeInterface &$node = null): void |
|||
201 | { |
||||
202 | 11 | $tmp = $node; |
|||
203 | 11 | $cmp = $node->getItem()->compareTo($item); |
|||
0 ignored issues
–
show
|
|||||
204 | 11 | $child = null; |
|||
205 | switch($cmp) { |
||||
206 | 11 | case -1: |
|||
207 | 9 | $child = $node->getRight(); |
|||
208 | 9 | break; |
|||
209 | 10 | case 0: |
|||
210 | return; |
||||
211 | 10 | case 1: |
|||
212 | 10 | $child = $node->getLeft(); |
|||
213 | } |
||||
214 | |||||
215 | 11 | if ($child->getItem()->compareTo($item) !== 0) { |
|||
216 | 10 | $this->removeItemRecursive($item, $child); |
|||
217 | } else { |
||||
218 | 11 | $this->reassignSubtree($child); |
|||
0 ignored issues
–
show
It seems like
$child can also be of type null ; however, parameter $node of Seboettg\Forest\BinaryTree::reassignSubtree() does only seem to accept Seboettg\Forest\BinaryTree\BinaryNodeInterface , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
219 | 11 | ($cmp > 0) ? $tmp->setLeft($child) : $tmp->setRight($child); |
|||
0 ignored issues
–
show
The method
setLeft() does not exist on null .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. ![]() |
|||||
220 | 11 | $node = $tmp; |
|||
221 | } |
||||
222 | 11 | } |
|||
223 | |||||
224 | /** |
||||
225 | * @param BinaryNodeInterface $node |
||||
226 | */ |
||||
227 | 5 | protected function reassignSubtree(BinaryNodeInterface &$node): void |
|||
228 | { |
||||
229 | 5 | if (empty($node->getLeft()) && empty($node->getRight())) { //no children? |
|||
230 | 1 | $node = null; // just unset the node and return |
|||
231 | 1 | return; |
|||
232 | } |
||||
233 | 4 | if (empty($node->getRight())) { // is there something on the right? |
|||
234 | 1 | $tmp = $node->getLeft(); // if not, return left node |
|||
235 | 1 | $tmp->setParent(null); |
|||
236 | 1 | $node = $tmp; |
|||
237 | } else { |
||||
238 | 3 | if (empty($node->getLeft())) { // is there something on the left? |
|||
239 | 1 | $tmp = $node->getRight(); // if not, return right node |
|||
240 | 1 | $tmp->setParent(null); |
|||
241 | 1 | $node = $tmp; |
|||
242 | } else { // left and right? |
||||
243 | 2 | $tmp = $node->getRight(); //keep right in mind |
|||
244 | 2 | $leftMostLeaf = $this->getLeftMostLeaf($tmp); // get the leftmost leaf of the right subtree |
|||
245 | 2 | $leftMostLeaf->setLeft($node->getLeft()); // append the left subtree to the leftmost leaf of the right subtree |
|||
246 | 2 | $node = $tmp; |
|||
247 | } |
||||
248 | } |
||||
249 | 4 | } |
|||
250 | } |
||||
251 |
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.
This is most likely a typographical error or the method has been renamed.