Completed
Push — master ( b6f537...796734 )
by Siro Díaz
05:27 queued 02:59
created

BinarySearchTree::put()   C

Complexity

Conditions 7
Paths 9

Size

Total Lines 34
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 34
c 0
b 0
f 0
rs 6.7272
cc 7
eloc 24
nc 9
nop 3
1
<?php
2
3
namespace DataStructures\Trees;
4
5
use DataStructures\Trees\Interfaces\TreeInterface;
6
use DataStructures\Trees\Nodes\BSTNode as Node;
7
/**
8
 * BinarySearchTree class. Represents a BST actions that can be realized.
9
 * At the beginning root is null and it can grow up to a O(n) in search,
10
 * delete and insert (in worst case). In best cases it will be O(log n).
11
 *
12
 * @author Siro Diaz Palazon <[email protected]>
13
 */
14
class BinarySearchTree implements TreeInterface {
15
    private $root;
16
    private $size;
17
18
    public function __construct() {
19
        $this->root = null;
20
        $this->size = 0;
21
    }
22
23
    /**
24
     * Checks if the tree is empty.
25
     *
26
     * @return boolean true if is empty, else false.
27
     */
28
    public function empty() : bool {
29
        return $this->root === null;
30
    }
31
32
    /**
33
     * Returns the tree size.
34
     *
35
     * @return int the length
36
     */
37
    public function size() : int {
38
        return $this->size;
39
    }
40
41
    /**
42
     * Inserts data in the correct position.
43
     *
44
     * @param integer|string $key the key used to store.
45
     * @param mixed $data the data to store.
46
     * @param bool $update if false the node isn't updated
47
     *  else if the key matches is updated.
48
     */
49
    public function put($key, $data, $update = false) {
50
        $newNode = new Node($key, $data);
51
        
52
        if($this->root === null) {
53
            $this->root = &$newNode;
54
            $this->size++;
55
            return;
56
        }
57
58
        $parentNode = null;
59
        $current = &$this->root;
60
        while($current !== null) {
61
            $parentNode = &$current;
62
            if($key < $current->key) {
63
                $current = &$current->left;
64
            } else if($key > $current->key) {
65
                $current = &$current->right;
66
            } else {
67
                if($update) {
68
                    $current->data = $data;
69
                }
70
                return;
71
            }
72
        }
73
74
        $newNode->parent = &$parentNode;
75
        if($key < $parentNode->key) {
76
            $parentNode->left = &$newNode;
77
        } else {
78
            $parentNode->right = &$newNode;
79
        }
80
81
        $this->size++;
82
    }
83
    
84
    /**
85
     * Creates a new node or updates it if already exists.
86
     *
87
     * @param int|string $key the key.
88
     * @param mixed $data the data to be stored. 
89
     */
90
    public function putOrUpdate($key, $data) {
91
        $this->put($key, $data, true);
92
    }
93
94
    /**
95
     * Retrieve the data stored in the tree.
96
     *
97
     * @param int|string $key the key to identify the data.
98
     * @return mixed
99
     */
100
    public function get($key) {
101
        $node = $this->root;
102
        if($node === null) {
103
            return null;
104
        }
105
106 View Code Duplication
        while($node !== null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
107
            if($key < $node->key) {
108
                $node = $node->left;
109
            } else if($key > $node->key) {
110
                $node = $node->right;
111
            } else {
112
                return $node->data;
113
            }
114
        }
115
116
        if($node === null) {
117
            return null;
118
        }
119
120
        return $node->data;
121
    }
122
123
    /**
124
     * Returns the root node.
125
     *
126
     * @return DataStructures\Trees\Nodes\BSTNode|null the root node.
127
     */
128
    public function getRoot() {
129
        return $this->root;
130
    }
131
132
    /**
133
     * Looks for the node with the given key.
134
     *
135
     * @param int|string $key the key used to look for.
136
     * @return bool true if was found.
137
     */
138
    public function exists($key) : bool {
139
        return $this->_exists($this->root, $key);
0 ignored issues
show
Bug introduced by
It seems like $this->root can also be of type object<DataStructures\Trees\Nodes\BSTNode>; however, DataStructures\Trees\BinarySearchTree::_exists() does only seem to accept object<DataStructures\Tr...ees\Nodes\BSTNode>|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
140
    }
141
142
    /**
143
     * Method that retrieves true if found a node with the specified key.
144
     * It's the recursive version of exists method and it's used internally
145
     * for traverse through a root node.
146
     *
147
     * @param DataStructures\Trees\Nodes\BSTNode|null $node the root node.
148
     * @param int|string $key the key used to look for.
149
     * @return bool true if exists a node with that key.
150
     */
151
    private function _exists($node, $key) : bool {
152
        if($node === null) {
153
            return false;
154
        }
155
156
        if($node->key === $key) {
157
            return true;
158
        } else if($key < $node->key) {
159
            return $this->_exists($node->left, $key);
160
        } else if($key > $node->key) {
161
            return $this->_exists($node->right, $key);
162
        }
163
    }
164
165
    public function floor($key) {
166
167
    }
168
    
169
    public function ceil($key) {
170
171
    }
172
    
173
    /**
174
     * Gets the node with the minimum key. The most left and more bottom.
175
     * 
176
     * @return DataStructures\Trees\Nodes\BSTNode|null the minum node or
177
     *  null if the tree is empty.
178
     */
179
    public function min() {
180
        if($this->root === null) {
181
            return null;
182
        }
183
        
184
        if($this->root->left === null) {
185
            return $this->root;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->root; (DataStructures\Trees\Nodes\BSTNode) is incompatible with the return type documented by DataStructures\Trees\BinarySearchTree::min of type DataStructures\Trees\Dat...rees\Nodes\BSTNode|null.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
186
        }
187
188
        $current = $this->root;
189
        while($current->left !== null) {
190
            $current = $current->left;
191
        }
192
193
        return $current;
194
    }
195
196
    /**
197
     * Returns the minimum node from a given node in position X.
198
     *
199
     * @param DataStructures\Trees\Nodes\BSTNode $node the start point.
200
     * @return DataStructures\Trees\Nodes\BSTNode|null the minimum node.
201
     */
202 View Code Duplication
    private function getMinNode(Node $node = null) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
203
        if($node === null) {
204
            return null;
205
        }
206
207
        while($node->left !== null) {
208
            $node = $node->left;
209
        }
210
211
        return $node;
212
    }
213
214
    /**
215
     * Gets the node with the maximum key. The most right and more bottom.
216
     * 
217
     * @return DataStructures\Trees\Nodes\BSTNode|null the maximum node or
218
     *  null if the tree is empty.
219
     */
220
    public function max() {
221
        if($this->root === null) {
222
            return null;
223
        }
224
225
        if($this->root->right === null) {
226
            return $this->root;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->root; (DataStructures\Trees\Nodes\BSTNode) is incompatible with the return type documented by DataStructures\Trees\BinarySearchTree::max of type DataStructures\Trees\Dat...rees\Nodes\BSTNode|null.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
227
        }
228
229
        $current = $this->root;
230
        while($current->right !== null) {
231
            $current = $current->right;
232
        }
233
234
        return $current;
235
    }
236
237
    /**
238
     * Returns the maximum node from a given node in position X.
239
     *
240
     * @param DataStructures\Trees\Nodes\BSTNode $node the start point.
241
     * @return DataStructures\Trees\Nodes\BSTNode|null the maximum node.
242
     */
243 View Code Duplication
    private function getMaxNode(Node $node = null) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
244
        if($node === null) {
245
            return null;
246
        }
247
        
248
        while($node->right !== null) {
249
            $node = $node->right;
250
        }
251
252
        return $node;
253
    }
254
255
    /**
256
     * Deletes the node with the minimum key and returns it. The most left and more bottom.
257
     * 
258
     * @param DataStructures\Trees\Nodes\BSTNode|null if null takes the root.
259
     * @return DataStructures\Trees\Nodes\BSTNode|null the minimum node or
260
     *  null if the tree is empty.
261
     */
262 View Code Duplication
    public function deleteMin(Node $node = null) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
263
        $node = $this->getMinNode($node ?? $this->root);
264
        if($node !== null) {
265
            $this->_delete($node);
266
        }
267
        
268
        return $node;
269
    }
270
    
271
    /**
272
     * Deletes the node with the maximum key and returns it. The most right and more bottom.
273
     * 
274
     * @param DataStructures\Trees\Nodes\BSTNode|null if null takes the root.
275
     * @return DataStructures\Trees\Nodes\BSTNode|null the maximum node or
276
     *  null if the tree is empty.
277
     */
278 View Code Duplication
    public function deleteMax(Node $node = null) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
279
        $node = $this->getMaxNode($node ?? $this->root);
280
        if($node !== null) {
281
            $this->_delete($node);
282
        }
283
284
        return $node;
285
    }
286
287
    /**
288
     * Deletes the node with the maximum key and returns it. The most right and more bottom.
289
     * 
290
     * @param DataStructures\Trees\Nodes\BSTNode|null if null takes the root.
291
     * @return DataStructures\Trees\Nodes\BSTNode|null the maximum node or
292
     *  null if the tree is empty.
293
     */
294
    public function delete($key) {
295
        $deleteNode = $this->search($key);
296
        if($deleteNode !== null) {
297
            $this->_delete($deleteNode);
298
            return $deleteNode;
299
        }
300
301
        return null;
302
    }
303
304
    /**
305
     * Replaces the node n to remove a new one k and links k with the parent
306
     * of n.
307
     *
308
     * @param DataStructures\Trees\Nodes\BSTNode $nodeToReplace the node to remove.
309
     * @param DataStructures\Trees\Nodes\BSTNode|null $newNode the newNode
310
     *  to link with the $nodeToReplace parent.
311
     * @return DataStructures\Trees\Nodes\BSTNode the new linked node.
312
     */
313
    private function replace(&$nodeToReplace, &$newNode) {
314
        if($nodeToReplace->parent === null) {
315
            $this->root = &$newNode;
316
        } else if($nodeToReplace === $nodeToReplace->parent->left) {
317
            $nodeToReplace->parent->left = &$newNode;
318
        } else if($nodeToReplace === $nodeToReplace->parent->right) {
319
            $nodeToReplace->parent->right = &$newNode;
320
        }
321
322
        if($newNode !== null) {
323
            $newNode->parent = &$nodeToReplace->parent;
324
        }
325
326
        return $newNode;
327
    }
328
329
    /**
330
     * Deletes the selected node if is not null and returns the node
331
     * that replaces the deleted node. Also decrease the size of tree.
332
     *
333
     * @param DataStructures\Trees\Nodes\BSTNode|null The node to be deleted.
334
     * @return the node that replaces the deleted.
335
     */
336
    private function _delete(Node &$node) {
337
        if($node !== null) {
338
            $nodeToReturn = null;
339
            if($node->left === null) {
340
                $nodeToReturn = $this->replace($node, $node->right);
0 ignored issues
show
Documentation introduced by
$node is of type object<DataStructures\Trees\Nodes\BSTNode>, but the function expects a object<DataStructures\Tr...es\Trees\Nodes\BSTNode>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
341
            } else if($node->right === null) {
342
                $nodeToReturn = $this->replace($node, $node->left);
0 ignored issues
show
Documentation introduced by
$node is of type object<DataStructures\Trees\Nodes\BSTNode>, but the function expects a object<DataStructures\Tr...es\Trees\Nodes\BSTNode>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
343
            } else {
344
                $successorNode = $this->getMinNode($node->right);
345
                if($successorNode->parent !== $node) {
346
                    $this->replace($successorNode, $successorNode->right);
347
                    $successorNode->right = &$node->right;
348
                    $successorNode->right->parent = &$successorNode;
349
                }
350
351
                $this->replace($node, $successorNode);
352
                $successorNode->left = &$node->left;
353
                $successorNode->left->parent = &$successorNode;
354
                $nodeToReturn = &$successorNode;
355
            }
356
357
            $this->size--;
358
            return $nodeToReturn;
359
        }
360
361
        return null;
362
    }
363
364
    /**
365
     * Retrieves the node with the specified key.
366
     *
367
     * @param int|string $key the key used to store.
368
     * @return DataStructures\Trees\Nodes\BSTNode|null the node or null.
369
     */
370
    public function search($key) {
371
        if($this->root === null) {
372
            return null;
373
        }
374
375
        if($this->root->key === $key) {
376
            return $this->root;
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->root; of type DataStructures\Trees\Nod...res\Trees\Nodes\BSTNode adds the type DataStructures\Trees\Nodes\BSTNode to the return on line 376 which is incompatible with the return type documented by DataStructures\Trees\BinarySearchTree::search of type DataStructures\Trees\Dat...rees\Nodes\BSTNode|null.
Loading history...
377
        } else {
378
            $node = $this->root;
379 View Code Duplication
            while($node !== null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
380
                if($key < $node->key) {
381
                    $node = $node->left;
382
                } else if($key > $node->key) {
383
                    $node = $node->right;
384
                } else {
385
                    return $node;
386
                }
387
            }
388
        }
389
390
        return null;
391
    }
392
393
    /**
394
     * Returns true if is leaf the node.
395
     *
396
     * @param DataStructures\Trees\Nodes\BSTNode|null $node default to null.
397
     * @return true if is leaf the node, is not null and their subtrees has no
398
     *  pointers to successors.
399
     */
400
    public function isLeaf($node) {
401
        return $node !== null && $node->left === null && $node->right === null;
402
    }
403
404
    /**
405
     * Checks if a node is root. New nodes that does not point to any other node
406
     * also are called a root node.
407
     *
408
     * @param DataStructures\Trees\Nodes\BSTNode|null $node default to null.
409
     * @return true if is root the node, is not null and their subtrees has no
410
     *  pointers to successors.
411
     */
412
    public function isRoot($node) {
413
        return $node !== null && $node->parent === null;
414
    }
415
416
    /**
417
     * Traverse in preorder. This is: first visit the root, then
418
     * the left subtree and finally the right subtree.
419
     */
420
    public function preorder(Callable $callback = null) {
421
        $this->_preorder($this->root, $callback);
422
    }
423
424 View Code Duplication
    private function _preorder($node, Callable $callback = null) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
425
        if($node === null) {
426
            return;
427
        }
428
        if($callback !== null) {
429
            call_user_func($callback, $node);
430
        }
431
        $this->_preorder($node->left, $callback);
432
        $this->_preorder($node->right, $callback);
433
    }
434
435
    /**
436
     * Traverse in inorder. This is: first visit the left subtree,
437
     * then the root and finally the right subtree.
438
     */
439
    public function inorder(Callable $callback = null) {
0 ignored issues
show
Unused Code introduced by
The parameter $callback is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
440
        $this->_inorder($this->root);
441
    }
442
443 View Code Duplication
    private function _inorder($node, Callable $callback = null) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
444
        if($node === null) {
445
            return;
446
        }
447
448
        $this->_inorder($node->left, $callback);
449
        if($callback !== null) {
450
            call_user_func($callback, $node);
451
        }
452
        $this->_inorder($node->right, $callback);
453
    }
454
455
    /**
456
     * Traverse in postorder. This is: first visit the left subtree,
457
     * then the right subtree and finally the root.
458
     */
459
    public function postorder(Callable $callback = null) {
460
        $this->_postorder($this->root, $callback);
461
    }
462
463
    /**
464
     * Private postorder traverse method that is recursive and is called by
465
     * the public postorder method.
466
     *
467
     * @param DataStructures\Trees\Nodes\BSTNode|null $node.
0 ignored issues
show
Bug introduced by
There is no parameter named $node.. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
468
     * @param Callable|null $callback the callback function to apply to each
469
     *  node.
470
     */
471 View Code Duplication
    private function _postorder($node, Callable $callback = null) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
472
        if($node === null) {
473
            return;
474
        }
475
        $this->_postorder($node->left, $callback);
476
        $this->_postorder($node->right, $callback);
477
        if($callback !== null) {
478
            call_user_func($callback, $node);
479
        }
480
    }
481
482
    /**
483
     * Binds to count() method. This is equal to make $this->tree->size().
484
     *
485
     * @return integer the tree size. 0 if it is empty.
486
     */
487
    public function count() {
488
        return $this->size;
489
    }
490
}