Issues (15)

src/BinaryTree.php (3 issues)

Labels
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
The method getItem() 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 ignore-call  annotation

203
        $cmp = $node->/** @scrutinizer ignore-call */ getItem()->compareTo($item);

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.

Loading history...
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 ignore-type  annotation

218
            $this->reassignSubtree(/** @scrutinizer ignore-type */ $child);
Loading history...
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 ignore-call  annotation

219
            ($cmp > 0) ? $tmp->/** @scrutinizer ignore-call */ setLeft($child) : $tmp->setRight($child);

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.

Loading history...
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