Completed
Push — develop ( bb9e1a...abd3b3 )
by Arkadiusz
02:58
created

Space::getDimension()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
declare (strict_types = 1);
4
5
namespace Phpml\Clustering\KMeans;
6
7
use Phpml\Clustering\KMeans;
8
use SplObjectStorage;
9
use LogicException;
10
use InvalidArgumentException;
11
12
class Space extends SplObjectStorage
13
{
14
    /**
15
     * @var int
16
     */
17
    protected $dimension;
18
19
    /**
20
     * @param $dimension
21
     */
22
    public function __construct($dimension)
23
    {
24
        if ($dimension < 1) {
25
            throw new LogicException('a space dimension cannot be null or negative');
26
        }
27
28
        $this->dimension = $dimension;
29
    }
30
31
    /**
32
     * @return array
33
     */
34
    public function toArray()
35
    {
36
        $points = [];
37
        foreach ($this as $point) {
38
            $points[] = $point->toArray();
39
        }
40
41
        return ['points' => $points];
42
    }
43
44
    /**
45
     * @param array $coordinates
46
     *
47
     * @return Point
48
     */
49
    public function newPoint(array $coordinates)
50
    {
51
        if (count($coordinates) != $this->dimension) {
52
            throw new LogicException('('.implode(',', $coordinates).') is not a point of this space');
53
        }
54
55
        return new Point($coordinates);
56
    }
57
58
    /**
59
     * @param array $coordinates
60
     * @param null  $data
61
     */
62
    public function addPoint(array $coordinates, $data = null)
63
    {
64
        return $this->attach($this->newPoint($coordinates), $data);
65
    }
66
67
    /**
68
     * @param object $point
69
     * @param null   $data
70
     */
71
    public function attach($point, $data = null)
72
    {
73
        if (!$point instanceof Point) {
74
            throw new InvalidArgumentException('can only attach points to spaces');
75
        }
76
77
        return parent::attach($point, $data);
78
    }
79
80
    /**
81
     * @return int
82
     */
83
    public function getDimension()
84
    {
85
        return $this->dimension;
86
    }
87
88
    /**
89
     * @return array|bool
90
     */
91
    public function getBoundaries()
92
    {
93
        if (!count($this)) {
94
            return false;
95
        }
96
97
        $min = $this->newPoint(array_fill(0, $this->dimension, null));
98
        $max = $this->newPoint(array_fill(0, $this->dimension, null));
99
100
        foreach ($this as $point) {
101
            for ($n = 0; $n < $this->dimension; ++$n) {
102
                ($min[$n] > $point[$n] || $min[$n] === null) && $min[$n] = $point[$n];
103
                ($max[$n] < $point[$n] || $max[$n] === null) && $max[$n] = $point[$n];
104
            }
105
        }
106
107
        return array($min, $max);
108
    }
109
110
    /**
111
     * @param Point $min
112
     * @param Point $max
113
     *
114
     * @return Point
115
     */
116
    public function getRandomPoint(Point $min, Point $max)
117
    {
118
        $point = $this->newPoint(array_fill(0, $this->dimension, null));
119
120
        for ($n = 0; $n < $this->dimension; ++$n) {
121
            $point[$n] = rand($min[$n], $max[$n]);
122
        }
123
124
        return $point;
125
    }
126
127
    /**
128
     * @param int $clustersNumber
129
     * @param int $initMethod
130
     *
131
     * @return array|Cluster[]
132
     */
133
    public function cluster(int $clustersNumber, int $initMethod = KMeans::INIT_RANDOM)
134
    {
135
        $clusters = $this->initializeClusters($clustersNumber, $initMethod);
136
137
        do {
0 ignored issues
show
Unused Code introduced by
This do loop is empty and can be removed.

This check looks for do loops that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

Consider removing the loop.

Loading history...
138
        } while (!$this->iterate($clusters));
139
140
        return $clusters;
141
    }
142
143
    /**
144
     * @param $clustersNumber
145
     * @param $initMethod
146
     *
147
     * @return array|Cluster[]
148
     */
149
    protected function initializeClusters(int $clustersNumber, int $initMethod)
150
    {
151
        switch ($initMethod) {
152
            case KMeans::INIT_RANDOM:
153
                list($min, $max) = $this->getBoundaries();
154
                for ($n = 0; $n < $clustersNumber; ++$n) {
155
                    $clusters[] = new Cluster($this, $this->getRandomPoint($min, $max)->getCoordinates());
0 ignored issues
show
Coding Style Comprehensibility introduced by
$clusters was never initialized. Although not strictly required by PHP, it is generally a good practice to add $clusters = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
156
                }
157
                break;
158
159
            case KMeans::INIT_KMEANS_PLUS_PLUS:
160
                $position = rand(1, count($this));
161
                for ($i = 1, $this->rewind(); $i < $position && $this->valid(); $i++, $this->next());
162
                $clusters[] = new Cluster($this, $this->current()->getCoordinates());
0 ignored issues
show
Coding Style Comprehensibility introduced by
$clusters was never initialized. Although not strictly required by PHP, it is generally a good practice to add $clusters = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
163
164
                $distances = new SplObjectStorage();
165
166
                for ($i = 1; $i < $clustersNumber; ++$i) {
167
                    $sum = 0;
168
                    foreach ($this as $point) {
169
                        $distance = $point->getDistanceWith($point->getClosest($clusters));
170
                        $sum += $distances[$point] = $distance;
171
                    }
172
173
                    $sum = rand(0, (int) $sum);
174
                    foreach ($this as $point) {
175
                        if (($sum -= $distances[$point]) > 0) {
176
                            continue;
177
                        }
178
179
                        $clusters[] = new Cluster($this, $point->getCoordinates());
180
                        break;
181
                    }
182
                }
183
184
                break;
185
        }
186
        $clusters[0]->attachAll($this);
0 ignored issues
show
Bug introduced by
The variable $clusters does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
187
188
        return $clusters;
189
    }
190
191
    /**
192
     * @param $clusters
193
     *
194
     * @return bool
195
     */
196
    protected function iterate($clusters)
197
    {
198
        $convergence = true;
199
200
        $attach = new SplObjectStorage();
201
        $detach = new SplObjectStorage();
202
203
        foreach ($clusters as $cluster) {
204
            foreach ($cluster as $point) {
205
                $closest = $point->getClosest($clusters);
206
207
                if ($closest !== $cluster) {
208
                    isset($attach[$closest]) || $attach[$closest] = new SplObjectStorage();
209
                    isset($detach[$cluster]) || $detach[$cluster] = new SplObjectStorage();
210
211
                    $attach[$closest]->attach($point);
212
                    $detach[$cluster]->attach($point);
213
214
                    $convergence = false;
215
                }
216
            }
217
        }
218
219
        foreach ($attach as $cluster) {
220
            $cluster->attachAll($attach[$cluster]);
221
        }
222
223
        foreach ($detach as $cluster) {
224
            $cluster->detachAll($detach[$cluster]);
225
        }
226
227
        foreach ($clusters as $cluster) {
228
            $cluster->updateCentroid();
229
        }
230
231
        return $convergence;
232
    }
233
}
234