### Space   A last analyzed 2019-06-22 20:56 UTC

#### Complexity

 Total Complexity 41

#### Size/Duplication

 Total Lines 250 Duplicated Lines 0 %

#### Importance

 Changes 0
Metric Value
wmc 41
eloc 94
dl 0
loc 250
rs 9.1199
c 0
b 0
f 0

#### 13 Methods

Rating   Name   Duplication   Size   Complexity
A __construct() 0 7 2
A getRandomPoint() 0 9 2
A getDimension() 0 3 1
A cluster() 0 8 2
A initializeRandomClusters() 0 10 2
A newPoint() 0 7 2
B getBoundaries() 0 23 8
A toArray() 0 10 2
B iterate() 0 42 8
B initializeKMPPClusters() 0 41 6
A attach() 0 7 2
A initializeClusters() 0 20 3

#### Complex Class

Complex classes like Space often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Space, and based on these observations, apply Extract Interface, too.

 1 dimension = \$dimension; 26 } 27 28 public function toArray(): array 29 { 30 \$points = []; 31 32 /** @var Point \$point */ 33 foreach (\$this as \$point) { 34 \$points[] = \$point->toArray(); 35 } 36 37 return ['points' => \$points]; 38 } 39 40 /** 41 * @param mixed \$label 42 */ 43 public function newPoint(array \$coordinates, \$label = null): Point 44 { 45 if (count(\$coordinates) !== \$this->dimension) { 46 throw new LogicException('('.implode(',', \$coordinates).') is not a point of this space'); 47 } 48 49 return new Point(\$coordinates, \$label); 50 } 51 52 /** 53 * @param mixed \$label 54 * @param mixed \$data 55 */ 56 public function addPoint(array \$coordinates, \$label = null, \$data = null): void 57 { 58 \$this->attach(\$this->newPoint(\$coordinates, \$label), \$data); 59 } 60 61 /** 62 * @param object \$point 63 * @param mixed \$data 64 */ 65 public function attach(\$point, \$data = null): void 66 { 67 if (!\$point instanceof Point) { 68 throw new InvalidArgumentException('can only attach points to spaces'); 69 } 70 71 parent::attach(\$point, \$data); 72 } 73 74 public function getDimension(): int 75 { 76 return \$this->dimension; 77 } 78 79 /** 80 * @return array|bool 81 */ 82 public function getBoundaries() 83 { 84 if (count(\$this) === 0) { 85 return false; 86 } 87 88 \$min = \$this->newPoint(array_fill(0, \$this->dimension, null)); 89 \$max = \$this->newPoint(array_fill(0, \$this->dimension, null)); 90 91 /** @var self \$point */ 92 foreach (\$this as \$point) { 93 for (\$n = 0; \$n < \$this->dimension; ++\$n) { 94 if (\$min[\$n] === null || \$min[\$n] > \$point[\$n]) { 95 \$min[\$n] = \$point[\$n]; 96 } 97 98 if (\$max[\$n] === null || \$max[\$n] < \$point[\$n]) { 99 \$max[\$n] = \$point[\$n]; 100 } 101 } 102 } 103 104 return [\$min, \$max]; 105 } 106 107 public function getRandomPoint(Point \$min, Point \$max): Point 108 { 109 \$point = \$this->newPoint(array_fill(0, \$this->dimension, null)); 110 111 for (\$n = 0; \$n < \$this->dimension; ++\$n) { 112 \$point[\$n] = random_int(\$min[\$n], \$max[\$n]); 113 } 114 115 return \$point; 116 } 117 118 /** 119 * @return Cluster[] 120 */ 121 public function cluster(int \$clustersNumber, int \$initMethod = KMeans::INIT_RANDOM): array 122 { 123 \$clusters = \$this->initializeClusters(\$clustersNumber, \$initMethod); 124 125 do { 126 } while (!\$this->iterate(\$clusters)); 127 128 return \$clusters; 129 } 130 131 /** 132 * @return Cluster[] 133 */ 134 protected function initializeClusters(int \$clustersNumber, int \$initMethod): array 135 { 136 switch (\$initMethod) { 137 case KMeans::INIT_RANDOM: 138 \$clusters = \$this->initializeRandomClusters(\$clustersNumber); 139 140 break; 141 142 case KMeans::INIT_KMEANS_PLUS_PLUS: 143 \$clusters = \$this->initializeKMPPClusters(\$clustersNumber); 144 145 break; 146 147 default: 148 return []; 149 } 150 151 \$clusters[0]->attachAll(\$this); 152 153 return \$clusters; 154 } 155 156 /** 157 * @param Cluster[] \$clusters 158 */ 159 protected function iterate(array \$clusters): bool 160 { 161 \$convergence = true; 162 163 \$attach = new SplObjectStorage(); 164 \$detach = new SplObjectStorage(); 165 166 foreach (\$clusters as \$cluster) { 167 foreach (\$cluster as \$point) { 168 \$closest = \$point->getClosest(\$clusters); 169 170 if (\$closest === null) { 171 continue; 172 } 173 174 if (\$closest !== \$cluster) { 175 \$attach[\$closest] ?? \$attach[\$closest] = new SplObjectStorage(); 176 \$detach[\$cluster] ?? \$detach[\$cluster] = new SplObjectStorage(); 177 178 \$attach[\$closest]->attach(\$point); 179 \$detach[\$cluster]->attach(\$point); 180 181 \$convergence = false; 182 } 183 } 184 } 185 186 /** @var Cluster \$cluster */ 187 foreach (\$attach as \$cluster) { 188 \$cluster->attachAll(\$attach[\$cluster]); 189 } 190 191 /** @var Cluster \$cluster */ 192 foreach (\$detach as \$cluster) { 193 \$cluster->detachAll(\$detach[\$cluster]); 194 } 195 196 foreach (\$clusters as \$cluster) { 197 \$cluster->updateCentroid(); 198 } 199 200 return \$convergence; 201 } 202 203 /** 204 * @return Cluster[] 205 */ 206 protected function initializeKMPPClusters(int \$clustersNumber): array 207 { 208 \$clusters = []; 209 \$this->rewind(); 210 211 /** @var Point \$current */ 212 \$current = \$this->current(); 213 214 \$clusters[] = new Cluster(\$this, \$current->getCoordinates()); 215 216 \$distances = new SplObjectStorage(); 217 218 for (\$i = 1; \$i < \$clustersNumber; ++\$i) { 219 \$sum = 0; 220 /** @var Point \$point */ 221 foreach (\$this as \$point) { 222 \$closest = \$point->getClosest(\$clusters); 223 if (\$closest === null) { 224 continue; 225 } 226 227 \$distance = \$point->getDistanceWith(\$closest); 228 \$sum += \$distances[\$point] = \$distance; 229 } 230 231 \$sum = random_int(0, (int) \$sum); 232 /** @var Point \$point */ 233 foreach (\$this as \$point) { 234 \$sum -= \$distances[\$point]; 235 236 if (\$sum > 0) { 237 continue; 238 } 239 240 \$clusters[] = new Cluster(\$this, \$point->getCoordinates()); 241 242 break; 243 } 244 } 245 246 return \$clusters; 247 } 248 249 /** 250 * @return Cluster[] 251 */ 252 private function initializeRandomClusters(int \$clustersNumber): array 253 { 254 \$clusters = []; 255 [\$min, \$max] = \$this->getBoundaries(); 256 257 for (\$n = 0; \$n < \$clustersNumber; ++\$n) { 258 \$clusters[] = new Cluster(\$this, \$this->getRandomPoint(\$min, \$max)->getCoordinates()); 259 } 260 261 return \$clusters; 262 } 263 } 264