Total Complexity | 216 |
Total Lines | 1191 |
Duplicated Lines | 28.13 % |
Changes | 0 |
Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like Matrix 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 Matrix, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
19 | class Matrix |
||
20 | { |
||
21 | const POLYMORPHIC_ARGUMENT_EXCEPTION = 'Invalid argument pattern for polymorphic function.'; |
||
22 | const ARGUMENT_TYPE_EXCEPTION = 'Invalid argument type.'; |
||
23 | const ARGUMENT_BOUNDS_EXCEPTION = 'Invalid argument range.'; |
||
24 | const MATRIX_DIMENSION_EXCEPTION = 'Matrix dimensions are not equal.'; |
||
25 | const ARRAY_LENGTH_EXCEPTION = 'Array length must be a multiple of m.'; |
||
26 | const MATRIX_SPD_EXCEPTION = 'Can only perform operation on symmetric positive definite matrix.'; |
||
27 | |||
28 | /** |
||
29 | * Matrix storage. |
||
30 | * |
||
31 | * @var array |
||
32 | */ |
||
33 | public $A = []; |
||
34 | |||
35 | /** |
||
36 | * Matrix row dimension. |
||
37 | * |
||
38 | * @var int |
||
39 | */ |
||
40 | private $m; |
||
41 | |||
42 | /** |
||
43 | * Matrix column dimension. |
||
44 | * |
||
45 | * @var int |
||
46 | */ |
||
47 | private $n; |
||
48 | |||
49 | /** |
||
50 | * Polymorphic constructor. |
||
51 | * |
||
52 | * As PHP has no support for polymorphic constructors, we use tricks to make our own sort of polymorphism using func_num_args, func_get_arg, and gettype. In essence, we're just implementing a simple RTTI filter and calling the appropriate constructor. |
||
53 | */ |
||
54 | public function __construct(...$args) |
||
55 | { |
||
56 | if (count($args) > 0) { |
||
57 | $match = implode(',', array_map('gettype', $args)); |
||
58 | |||
59 | switch ($match) { |
||
60 | //Rectangular matrix - m x n initialized from 2D array |
||
61 | case 'array': |
||
62 | $this->m = count($args[0]); |
||
63 | $this->n = count($args[0][0]); |
||
64 | $this->A = $args[0]; |
||
65 | |||
66 | break; |
||
67 | //Square matrix - n x n |
||
68 | View Code Duplication | case 'integer': |
|
|
|||
69 | $this->m = $args[0]; |
||
70 | $this->n = $args[0]; |
||
71 | $this->A = array_fill(0, $this->m, array_fill(0, $this->n, 0)); |
||
72 | |||
73 | break; |
||
74 | //Rectangular matrix - m x n |
||
75 | View Code Duplication | case 'integer,integer': |
|
76 | $this->m = $args[0]; |
||
77 | $this->n = $args[1]; |
||
78 | $this->A = array_fill(0, $this->m, array_fill(0, $this->n, 0)); |
||
79 | |||
80 | break; |
||
81 | //Rectangular matrix - m x n initialized from packed array |
||
82 | case 'array,integer': |
||
83 | $this->m = $args[1]; |
||
84 | if ($this->m != 0) { |
||
85 | $this->n = count($args[0]) / $this->m; |
||
86 | } else { |
||
87 | $this->n = 0; |
||
88 | } |
||
89 | if (($this->m * $this->n) == count($args[0])) { |
||
90 | for ($i = 0; $i < $this->m; ++$i) { |
||
91 | for ($j = 0; $j < $this->n; ++$j) { |
||
92 | $this->A[$i][$j] = $args[0][$i + $j * $this->m]; |
||
93 | } |
||
94 | } |
||
95 | } else { |
||
96 | throw new CalculationException(self::ARRAY_LENGTH_EXCEPTION); |
||
97 | } |
||
98 | |||
99 | break; |
||
100 | default: |
||
101 | throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); |
||
102 | break; |
||
103 | } |
||
104 | } else { |
||
105 | throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); |
||
106 | } |
||
107 | } |
||
108 | |||
109 | /** |
||
110 | * getArray. |
||
111 | * |
||
112 | * @return array Matrix array |
||
113 | */ |
||
114 | public function getArray() |
||
115 | { |
||
116 | return $this->A; |
||
117 | } |
||
118 | |||
119 | /** |
||
120 | * getRowDimension. |
||
121 | * |
||
122 | * @return int Row dimension |
||
123 | */ |
||
124 | public function getRowDimension() |
||
125 | { |
||
126 | return $this->m; |
||
127 | } |
||
128 | |||
129 | /** |
||
130 | * getColumnDimension. |
||
131 | * |
||
132 | * @return int Column dimension |
||
133 | */ |
||
134 | public function getColumnDimension() |
||
135 | { |
||
136 | return $this->n; |
||
137 | } |
||
138 | |||
139 | /** |
||
140 | * get. |
||
141 | * |
||
142 | * Get the i,j-th element of the matrix. |
||
143 | * |
||
144 | * @param int $i Row position |
||
145 | * @param int $j Column position |
||
146 | * |
||
147 | * @return mixed Element (int/float/double) |
||
148 | */ |
||
149 | public function get($i = null, $j = null) |
||
150 | { |
||
151 | return $this->A[$i][$j]; |
||
152 | } |
||
153 | |||
154 | /** |
||
155 | * getMatrix. |
||
156 | * |
||
157 | * Get a submatrix |
||
158 | * |
||
159 | * @param int $i0 Initial row index |
||
160 | * @param int $iF Final row index |
||
161 | * @param int $j0 Initial column index |
||
162 | * @param int $jF Final column index |
||
163 | * |
||
164 | * @return Matrix Submatrix |
||
165 | */ |
||
166 | public function getMatrix(...$args) |
||
167 | { |
||
168 | if (count($args) > 0) { |
||
169 | $match = implode(',', array_map('gettype', $args)); |
||
170 | |||
171 | switch ($match) { |
||
172 | //A($i0...; $j0...) |
||
173 | case 'integer,integer': |
||
174 | list($i0, $j0) = $args; |
||
175 | if ($i0 >= 0) { |
||
176 | $m = $this->m - $i0; |
||
177 | } else { |
||
178 | throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION); |
||
179 | } |
||
180 | if ($j0 >= 0) { |
||
181 | $n = $this->n - $j0; |
||
182 | } else { |
||
183 | throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION); |
||
184 | } |
||
185 | $R = new self($m, $n); |
||
186 | for ($i = $i0; $i < $this->m; ++$i) { |
||
187 | for ($j = $j0; $j < $this->n; ++$j) { |
||
188 | $R->set($i, $j, $this->A[$i][$j]); |
||
189 | } |
||
190 | } |
||
191 | |||
192 | return $R; |
||
193 | break; |
||
194 | //A($i0...$iF; $j0...$jF) |
||
195 | case 'integer,integer,integer,integer': |
||
196 | list($i0, $iF, $j0, $jF) = $args; |
||
197 | if (($iF > $i0) && ($this->m >= $iF) && ($i0 >= 0)) { |
||
198 | $m = $iF - $i0; |
||
199 | } else { |
||
200 | throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION); |
||
201 | } |
||
202 | if (($jF > $j0) && ($this->n >= $jF) && ($j0 >= 0)) { |
||
203 | $n = $jF - $j0; |
||
204 | } else { |
||
205 | throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION); |
||
206 | } |
||
207 | $R = new self($m + 1, $n + 1); |
||
208 | for ($i = $i0; $i <= $iF; ++$i) { |
||
209 | for ($j = $j0; $j <= $jF; ++$j) { |
||
210 | $R->set($i - $i0, $j - $j0, $this->A[$i][$j]); |
||
211 | } |
||
212 | } |
||
213 | |||
214 | return $R; |
||
215 | break; |
||
216 | //$R = array of row indices; $C = array of column indices |
||
217 | case 'array,array': |
||
218 | list($RL, $CL) = $args; |
||
219 | if (count($RL) > 0) { |
||
220 | $m = count($RL); |
||
221 | } else { |
||
222 | throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION); |
||
223 | } |
||
224 | if (count($CL) > 0) { |
||
225 | $n = count($CL); |
||
226 | } else { |
||
227 | throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION); |
||
228 | } |
||
229 | $R = new self($m, $n); |
||
230 | for ($i = 0; $i < $m; ++$i) { |
||
231 | for ($j = 0; $j < $n; ++$j) { |
||
232 | $R->set($i - $i0, $j - $j0, $this->A[$RL[$i]][$CL[$j]]); |
||
233 | } |
||
234 | } |
||
235 | |||
236 | return $R; |
||
237 | break; |
||
238 | //A($i0...$iF); $CL = array of column indices |
||
239 | View Code Duplication | case 'integer,integer,array': |
|
240 | list($i0, $iF, $CL) = $args; |
||
241 | if (($iF > $i0) && ($this->m >= $iF) && ($i0 >= 0)) { |
||
242 | $m = $iF - $i0; |
||
243 | } else { |
||
244 | throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION); |
||
245 | } |
||
246 | if (count($CL) > 0) { |
||
247 | $n = count($CL); |
||
248 | } else { |
||
249 | throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION); |
||
250 | } |
||
251 | $R = new self($m, $n); |
||
252 | for ($i = $i0; $i < $iF; ++$i) { |
||
253 | for ($j = 0; $j < $n; ++$j) { |
||
254 | $R->set($i - $i0, $j, $this->A[$RL[$i]][$j]); |
||
255 | } |
||
256 | } |
||
257 | |||
258 | return $R; |
||
259 | break; |
||
260 | //$RL = array of row indices |
||
261 | View Code Duplication | case 'array,integer,integer': |
|
262 | list($RL, $j0, $jF) = $args; |
||
263 | if (count($RL) > 0) { |
||
264 | $m = count($RL); |
||
265 | } else { |
||
266 | throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION); |
||
267 | } |
||
268 | if (($jF >= $j0) && ($this->n >= $jF) && ($j0 >= 0)) { |
||
269 | $n = $jF - $j0; |
||
270 | } else { |
||
271 | throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION); |
||
272 | } |
||
273 | $R = new self($m, $n + 1); |
||
274 | for ($i = 0; $i < $m; ++$i) { |
||
275 | for ($j = $j0; $j <= $jF; ++$j) { |
||
276 | $R->set($i, $j - $j0, $this->A[$RL[$i]][$j]); |
||
277 | } |
||
278 | } |
||
279 | |||
280 | return $R; |
||
281 | break; |
||
282 | default: |
||
283 | throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); |
||
284 | break; |
||
285 | } |
||
286 | } else { |
||
287 | throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); |
||
288 | } |
||
289 | } |
||
290 | |||
291 | /** |
||
292 | * checkMatrixDimensions. |
||
293 | * |
||
294 | * Is matrix B the same size? |
||
295 | * |
||
296 | * @param Matrix $B Matrix B |
||
297 | * |
||
298 | * @return bool |
||
299 | */ |
||
300 | public function checkMatrixDimensions($B = null) |
||
301 | { |
||
302 | if ($B instanceof self) { |
||
303 | if (($this->m == $B->getRowDimension()) && ($this->n == $B->getColumnDimension())) { |
||
304 | return true; |
||
305 | } |
||
306 | |||
307 | throw new CalculationException(self::MATRIX_DIMENSION_EXCEPTION); |
||
308 | } |
||
309 | |||
310 | throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); |
||
311 | } |
||
312 | |||
313 | // function checkMatrixDimensions() |
||
314 | |||
315 | /** |
||
316 | * set. |
||
317 | * |
||
318 | * Set the i,j-th element of the matrix. |
||
319 | * |
||
320 | * @param int $i Row position |
||
321 | * @param int $j Column position |
||
322 | * @param mixed $c Int/float/double value |
||
323 | * |
||
324 | * @return mixed Element (int/float/double) |
||
325 | */ |
||
326 | public function set($i = null, $j = null, $c = null) |
||
327 | { |
||
328 | // Optimized set version just has this |
||
329 | $this->A[$i][$j] = $c; |
||
330 | } |
||
331 | |||
332 | // function set() |
||
333 | |||
334 | /** |
||
335 | * identity. |
||
336 | * |
||
337 | * Generate an identity matrix. |
||
338 | * |
||
339 | * @param int $m Row dimension |
||
340 | * @param int $n Column dimension |
||
341 | * |
||
342 | * @return Matrix Identity matrix |
||
343 | */ |
||
344 | public function identity($m = null, $n = null) |
||
345 | { |
||
346 | return $this->diagonal($m, $n, 1); |
||
347 | } |
||
348 | |||
349 | /** |
||
350 | * diagonal. |
||
351 | * |
||
352 | * Generate a diagonal matrix |
||
353 | * |
||
354 | * @param int $m Row dimension |
||
355 | * @param int $n Column dimension |
||
356 | * @param mixed $c Diagonal value |
||
357 | * |
||
358 | * @return Matrix Diagonal matrix |
||
359 | */ |
||
360 | public function diagonal($m = null, $n = null, $c = 1) |
||
361 | { |
||
362 | $R = new self($m, $n); |
||
363 | for ($i = 0; $i < $m; ++$i) { |
||
364 | $R->set($i, $i, $c); |
||
365 | } |
||
366 | |||
367 | return $R; |
||
368 | } |
||
369 | |||
370 | /** |
||
371 | * getMatrixByRow. |
||
372 | * |
||
373 | * Get a submatrix by row index/range |
||
374 | * |
||
375 | * @param int $i0 Initial row index |
||
376 | * @param int $iF Final row index |
||
377 | * |
||
378 | * @return Matrix Submatrix |
||
379 | */ |
||
380 | View Code Duplication | public function getMatrixByRow($i0 = null, $iF = null) |
|
381 | { |
||
382 | if (is_int($i0)) { |
||
383 | if (is_int($iF)) { |
||
384 | return $this->getMatrix($i0, 0, $iF + 1, $this->n); |
||
385 | } |
||
386 | |||
387 | return $this->getMatrix($i0, 0, $i0 + 1, $this->n); |
||
388 | } |
||
389 | |||
390 | throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); |
||
391 | } |
||
392 | |||
393 | /** |
||
394 | * getMatrixByCol. |
||
395 | * |
||
396 | * Get a submatrix by column index/range |
||
397 | * |
||
398 | * @param int $j0 Initial column index |
||
399 | * @param int $jF Final column index |
||
400 | * |
||
401 | * @return Matrix Submatrix |
||
402 | */ |
||
403 | View Code Duplication | public function getMatrixByCol($j0 = null, $jF = null) |
|
404 | { |
||
405 | if (is_int($j0)) { |
||
406 | if (is_int($jF)) { |
||
407 | return $this->getMatrix(0, $j0, $this->m, $jF + 1); |
||
408 | } |
||
409 | |||
410 | return $this->getMatrix(0, $j0, $this->m, $j0 + 1); |
||
411 | } |
||
412 | |||
413 | throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); |
||
414 | } |
||
415 | |||
416 | /** |
||
417 | * transpose. |
||
418 | * |
||
419 | * Tranpose matrix |
||
420 | * |
||
421 | * @return Matrix Transposed matrix |
||
422 | */ |
||
423 | public function transpose() |
||
424 | { |
||
425 | $R = new self($this->n, $this->m); |
||
426 | for ($i = 0; $i < $this->m; ++$i) { |
||
427 | for ($j = 0; $j < $this->n; ++$j) { |
||
428 | $R->set($j, $i, $this->A[$i][$j]); |
||
429 | } |
||
430 | } |
||
431 | |||
432 | return $R; |
||
433 | } |
||
434 | |||
435 | // function transpose() |
||
436 | |||
437 | /** |
||
438 | * trace. |
||
439 | * |
||
440 | * Sum of diagonal elements |
||
441 | * |
||
442 | * @return float Sum of diagonal elements |
||
443 | */ |
||
444 | public function trace() |
||
445 | { |
||
446 | $s = 0; |
||
447 | $n = min($this->m, $this->n); |
||
448 | for ($i = 0; $i < $n; ++$i) { |
||
449 | $s += $this->A[$i][$i]; |
||
450 | } |
||
451 | |||
452 | return $s; |
||
453 | } |
||
454 | |||
455 | /** |
||
456 | * uminus. |
||
457 | * |
||
458 | * Unary minus matrix -A |
||
459 | * |
||
460 | * @return Matrix Unary minus matrix |
||
461 | */ |
||
462 | public function uminus() |
||
463 | { |
||
464 | } |
||
465 | |||
466 | /** |
||
467 | * plus. |
||
468 | * |
||
469 | * A + B |
||
470 | * |
||
471 | * @param mixed $B Matrix/Array |
||
472 | * |
||
473 | * @return Matrix Sum |
||
474 | */ |
||
475 | public function plus(...$args) |
||
476 | { |
||
477 | if (count($args) > 0) { |
||
478 | $match = implode(',', array_map('gettype', $args)); |
||
479 | |||
480 | switch ($match) { |
||
481 | case 'object': |
||
482 | if ($args[0] instanceof self) { |
||
483 | $M = $args[0]; |
||
484 | } else { |
||
485 | throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); |
||
486 | } |
||
487 | |||
488 | break; |
||
489 | case 'array': |
||
490 | $M = new self($args[0]); |
||
491 | |||
492 | break; |
||
493 | default: |
||
494 | throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); |
||
495 | break; |
||
496 | } |
||
497 | $this->checkMatrixDimensions($M); |
||
498 | for ($i = 0; $i < $this->m; ++$i) { |
||
499 | for ($j = 0; $j < $this->n; ++$j) { |
||
500 | $M->set($i, $j, $M->get($i, $j) + $this->A[$i][$j]); |
||
501 | } |
||
502 | } |
||
503 | |||
504 | return $M; |
||
505 | } |
||
506 | |||
507 | throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); |
||
508 | } |
||
509 | |||
510 | /** |
||
511 | * plusEquals. |
||
512 | * |
||
513 | * A = A + B |
||
514 | * |
||
515 | * @param mixed $B Matrix/Array |
||
516 | * |
||
517 | * @return Matrix Sum |
||
518 | */ |
||
519 | View Code Duplication | public function plusEquals(...$args) |
|
520 | { |
||
521 | if (count($args) > 0) { |
||
522 | $match = implode(',', array_map('gettype', $args)); |
||
523 | |||
524 | switch ($match) { |
||
525 | case 'object': |
||
526 | if ($args[0] instanceof self) { |
||
527 | $M = $args[0]; |
||
528 | } else { |
||
529 | throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); |
||
530 | } |
||
531 | |||
532 | break; |
||
533 | case 'array': |
||
534 | $M = new self($args[0]); |
||
535 | |||
536 | break; |
||
537 | default: |
||
538 | throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); |
||
539 | break; |
||
540 | } |
||
541 | $this->checkMatrixDimensions($M); |
||
542 | for ($i = 0; $i < $this->m; ++$i) { |
||
543 | for ($j = 0; $j < $this->n; ++$j) { |
||
544 | $validValues = true; |
||
545 | $value = $M->get($i, $j); |
||
546 | if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) { |
||
547 | $this->A[$i][$j] = trim($this->A[$i][$j], '"'); |
||
548 | $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]); |
||
549 | } |
||
550 | if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) { |
||
551 | $value = trim($value, '"'); |
||
552 | $validValues &= StringHelper::convertToNumberIfFraction($value); |
||
553 | } |
||
554 | if ($validValues) { |
||
555 | $this->A[$i][$j] += $value; |
||
556 | } else { |
||
557 | $this->A[$i][$j] = Functions::NAN(); |
||
558 | } |
||
559 | } |
||
560 | } |
||
561 | |||
562 | return $this; |
||
563 | } |
||
564 | |||
565 | throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); |
||
566 | } |
||
567 | |||
568 | /** |
||
569 | * minus. |
||
570 | * |
||
571 | * A - B |
||
572 | * |
||
573 | * @param mixed $B Matrix/Array |
||
574 | * |
||
575 | * @return Matrix Sum |
||
576 | */ |
||
577 | public function minus(...$args) |
||
578 | { |
||
579 | if (count($args) > 0) { |
||
580 | $match = implode(',', array_map('gettype', $args)); |
||
581 | |||
582 | switch ($match) { |
||
583 | case 'object': |
||
584 | if ($args[0] instanceof self) { |
||
585 | $M = $args[0]; |
||
586 | } else { |
||
587 | throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); |
||
588 | } |
||
589 | |||
590 | break; |
||
591 | case 'array': |
||
592 | $M = new self($args[0]); |
||
593 | |||
594 | break; |
||
595 | default: |
||
596 | throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); |
||
597 | break; |
||
598 | } |
||
599 | $this->checkMatrixDimensions($M); |
||
600 | for ($i = 0; $i < $this->m; ++$i) { |
||
601 | for ($j = 0; $j < $this->n; ++$j) { |
||
602 | $M->set($i, $j, $M->get($i, $j) - $this->A[$i][$j]); |
||
603 | } |
||
604 | } |
||
605 | |||
606 | return $M; |
||
607 | } |
||
608 | |||
609 | throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); |
||
610 | } |
||
611 | |||
612 | /** |
||
613 | * minusEquals. |
||
614 | * |
||
615 | * A = A - B |
||
616 | * |
||
617 | * @param mixed $B Matrix/Array |
||
618 | * |
||
619 | * @return Matrix Sum |
||
620 | */ |
||
621 | View Code Duplication | public function minusEquals(...$args) |
|
622 | { |
||
623 | if (count($args) > 0) { |
||
624 | $match = implode(',', array_map('gettype', $args)); |
||
625 | |||
626 | switch ($match) { |
||
627 | case 'object': |
||
628 | if ($args[0] instanceof self) { |
||
629 | $M = $args[0]; |
||
630 | } else { |
||
631 | throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); |
||
632 | } |
||
633 | |||
634 | break; |
||
635 | case 'array': |
||
636 | $M = new self($args[0]); |
||
637 | |||
638 | break; |
||
639 | default: |
||
640 | throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); |
||
641 | break; |
||
642 | } |
||
643 | $this->checkMatrixDimensions($M); |
||
644 | for ($i = 0; $i < $this->m; ++$i) { |
||
645 | for ($j = 0; $j < $this->n; ++$j) { |
||
646 | $validValues = true; |
||
647 | $value = $M->get($i, $j); |
||
648 | if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) { |
||
649 | $this->A[$i][$j] = trim($this->A[$i][$j], '"'); |
||
650 | $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]); |
||
651 | } |
||
652 | if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) { |
||
653 | $value = trim($value, '"'); |
||
654 | $validValues &= StringHelper::convertToNumberIfFraction($value); |
||
655 | } |
||
656 | if ($validValues) { |
||
657 | $this->A[$i][$j] -= $value; |
||
658 | } else { |
||
659 | $this->A[$i][$j] = Functions::NAN(); |
||
660 | } |
||
661 | } |
||
662 | } |
||
663 | |||
664 | return $this; |
||
665 | } |
||
666 | |||
667 | throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); |
||
668 | } |
||
669 | |||
670 | /** |
||
671 | * arrayTimes. |
||
672 | * |
||
673 | * Element-by-element multiplication |
||
674 | * Cij = Aij * Bij |
||
675 | * |
||
676 | * @param mixed $B Matrix/Array |
||
677 | * |
||
678 | * @return Matrix Matrix Cij |
||
679 | */ |
||
680 | public function arrayTimes(...$args) |
||
681 | { |
||
682 | if (count($args) > 0) { |
||
683 | $match = implode(',', array_map('gettype', $args)); |
||
684 | |||
685 | switch ($match) { |
||
686 | case 'object': |
||
687 | if ($args[0] instanceof self) { |
||
688 | $M = $args[0]; |
||
689 | } else { |
||
690 | throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); |
||
691 | } |
||
692 | |||
693 | break; |
||
694 | case 'array': |
||
695 | $M = new self($args[0]); |
||
696 | |||
697 | break; |
||
698 | default: |
||
699 | throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); |
||
700 | break; |
||
701 | } |
||
702 | $this->checkMatrixDimensions($M); |
||
703 | for ($i = 0; $i < $this->m; ++$i) { |
||
704 | for ($j = 0; $j < $this->n; ++$j) { |
||
705 | $M->set($i, $j, $M->get($i, $j) * $this->A[$i][$j]); |
||
706 | } |
||
707 | } |
||
708 | |||
709 | return $M; |
||
710 | } |
||
711 | |||
712 | throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); |
||
713 | } |
||
714 | |||
715 | /** |
||
716 | * arrayTimesEquals. |
||
717 | * |
||
718 | * Element-by-element multiplication |
||
719 | * Aij = Aij * Bij |
||
720 | * |
||
721 | * @param mixed $B Matrix/Array |
||
722 | * |
||
723 | * @return Matrix Matrix Aij |
||
724 | */ |
||
725 | View Code Duplication | public function arrayTimesEquals(...$args) |
|
726 | { |
||
727 | if (count($args) > 0) { |
||
728 | $match = implode(',', array_map('gettype', $args)); |
||
729 | |||
730 | switch ($match) { |
||
731 | case 'object': |
||
732 | if ($args[0] instanceof self) { |
||
733 | $M = $args[0]; |
||
734 | } else { |
||
735 | throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); |
||
736 | } |
||
737 | |||
738 | break; |
||
739 | case 'array': |
||
740 | $M = new self($args[0]); |
||
741 | |||
742 | break; |
||
743 | default: |
||
744 | throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); |
||
745 | break; |
||
746 | } |
||
747 | $this->checkMatrixDimensions($M); |
||
748 | for ($i = 0; $i < $this->m; ++$i) { |
||
749 | for ($j = 0; $j < $this->n; ++$j) { |
||
750 | $validValues = true; |
||
751 | $value = $M->get($i, $j); |
||
752 | if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) { |
||
753 | $this->A[$i][$j] = trim($this->A[$i][$j], '"'); |
||
754 | $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]); |
||
755 | } |
||
756 | if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) { |
||
757 | $value = trim($value, '"'); |
||
758 | $validValues &= StringHelper::convertToNumberIfFraction($value); |
||
759 | } |
||
760 | if ($validValues) { |
||
761 | $this->A[$i][$j] *= $value; |
||
762 | } else { |
||
763 | $this->A[$i][$j] = Functions::NAN(); |
||
764 | } |
||
765 | } |
||
766 | } |
||
767 | |||
768 | return $this; |
||
769 | } |
||
770 | |||
771 | throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); |
||
772 | } |
||
773 | |||
774 | /** |
||
775 | * arrayRightDivide. |
||
776 | * |
||
777 | * Element-by-element right division |
||
778 | * A / B |
||
779 | * |
||
780 | * @param Matrix $B Matrix B |
||
781 | * |
||
782 | * @return Matrix Division result |
||
783 | */ |
||
784 | public function arrayRightDivide(...$args) |
||
785 | { |
||
786 | if (count($args) > 0) { |
||
787 | $match = implode(',', array_map('gettype', $args)); |
||
788 | |||
789 | switch ($match) { |
||
790 | case 'object': |
||
791 | if ($args[0] instanceof self) { |
||
792 | $M = $args[0]; |
||
793 | } else { |
||
794 | throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); |
||
795 | } |
||
796 | |||
797 | break; |
||
798 | case 'array': |
||
799 | $M = new self($args[0]); |
||
800 | |||
801 | break; |
||
802 | default: |
||
803 | throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); |
||
804 | break; |
||
805 | } |
||
806 | $this->checkMatrixDimensions($M); |
||
807 | for ($i = 0; $i < $this->m; ++$i) { |
||
808 | for ($j = 0; $j < $this->n; ++$j) { |
||
809 | $validValues = true; |
||
810 | $value = $M->get($i, $j); |
||
811 | if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) { |
||
812 | $this->A[$i][$j] = trim($this->A[$i][$j], '"'); |
||
813 | $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]); |
||
814 | } |
||
815 | if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) { |
||
816 | $value = trim($value, '"'); |
||
817 | $validValues &= StringHelper::convertToNumberIfFraction($value); |
||
818 | } |
||
819 | if ($validValues) { |
||
820 | if ($value == 0) { |
||
821 | // Trap for Divide by Zero error |
||
822 | $M->set($i, $j, '#DIV/0!'); |
||
823 | } else { |
||
824 | $M->set($i, $j, $this->A[$i][$j] / $value); |
||
825 | } |
||
826 | } else { |
||
827 | $M->set($i, $j, Functions::NAN()); |
||
828 | } |
||
829 | } |
||
830 | } |
||
831 | |||
832 | return $M; |
||
833 | } |
||
834 | |||
835 | throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); |
||
836 | } |
||
837 | |||
838 | /** |
||
839 | * arrayRightDivideEquals. |
||
840 | * |
||
841 | * Element-by-element right division |
||
842 | * Aij = Aij / Bij |
||
843 | * |
||
844 | * @param mixed $B Matrix/Array |
||
845 | * |
||
846 | * @return Matrix Matrix Aij |
||
847 | */ |
||
848 | View Code Duplication | public function arrayRightDivideEquals(...$args) |
|
849 | { |
||
850 | if (count($args) > 0) { |
||
851 | $match = implode(',', array_map('gettype', $args)); |
||
852 | |||
853 | switch ($match) { |
||
854 | case 'object': |
||
855 | if ($args[0] instanceof self) { |
||
856 | $M = $args[0]; |
||
857 | } else { |
||
858 | throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); |
||
859 | } |
||
860 | |||
861 | break; |
||
862 | case 'array': |
||
863 | $M = new self($args[0]); |
||
864 | |||
865 | break; |
||
866 | default: |
||
867 | throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); |
||
868 | break; |
||
869 | } |
||
870 | $this->checkMatrixDimensions($M); |
||
871 | for ($i = 0; $i < $this->m; ++$i) { |
||
872 | for ($j = 0; $j < $this->n; ++$j) { |
||
873 | $this->A[$i][$j] = $this->A[$i][$j] / $M->get($i, $j); |
||
874 | } |
||
875 | } |
||
876 | |||
877 | return $M; |
||
878 | } |
||
879 | |||
880 | throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); |
||
881 | } |
||
882 | |||
883 | /** |
||
884 | * arrayLeftDivide. |
||
885 | * |
||
886 | * Element-by-element Left division |
||
887 | * A / B |
||
888 | * |
||
889 | * @param Matrix $B Matrix B |
||
890 | * |
||
891 | * @return Matrix Division result |
||
892 | */ |
||
893 | View Code Duplication | public function arrayLeftDivide(...$args) |
|
926 | } |
||
927 | |||
928 | /** |
||
929 | * arrayLeftDivideEquals. |
||
930 | * |
||
931 | * Element-by-element Left division |
||
932 | * Aij = Aij / Bij |
||
933 | * |
||
934 | * @param mixed $B Matrix/Array |
||
935 | * |
||
936 | * @return Matrix Matrix Aij |
||
937 | */ |
||
938 | View Code Duplication | public function arrayLeftDivideEquals(...$args) |
|
939 | { |
||
940 | if (count($args) > 0) { |
||
941 | $match = implode(',', array_map('gettype', $args)); |
||
942 | |||
943 | switch ($match) { |
||
944 | case 'object': |
||
945 | if ($args[0] instanceof self) { |
||
946 | $M = $args[0]; |
||
947 | } else { |
||
948 | throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); |
||
949 | } |
||
950 | |||
951 | break; |
||
952 | case 'array': |
||
953 | $M = new self($args[0]); |
||
954 | |||
955 | break; |
||
956 | default: |
||
957 | throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); |
||
958 | break; |
||
959 | } |
||
960 | $this->checkMatrixDimensions($M); |
||
961 | for ($i = 0; $i < $this->m; ++$i) { |
||
962 | for ($j = 0; $j < $this->n; ++$j) { |
||
963 | $this->A[$i][$j] = $M->get($i, $j) / $this->A[$i][$j]; |
||
964 | } |
||
965 | } |
||
966 | |||
967 | return $M; |
||
968 | } |
||
969 | |||
970 | throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); |
||
971 | } |
||
972 | |||
973 | /** |
||
974 | * times. |
||
975 | * |
||
976 | * Matrix multiplication |
||
977 | * |
||
978 | * @param mixed $n Matrix/Array/Scalar |
||
979 | * |
||
980 | * @return Matrix Product |
||
981 | */ |
||
982 | public function times(...$args) |
||
983 | { |
||
984 | if (count() > 0) { |
||
985 | $match = implode(',', array_map('gettype', $args)); |
||
986 | |||
987 | switch ($match) { |
||
988 | case 'object': |
||
989 | if ($args[0] instanceof self) { |
||
990 | $B = $args[0]; |
||
991 | } else { |
||
992 | throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); |
||
993 | } |
||
994 | if ($this->n == $B->m) { |
||
995 | $C = new self($this->m, $B->n); |
||
996 | for ($j = 0; $j < $B->n; ++$j) { |
||
997 | for ($k = 0; $k < $this->n; ++$k) { |
||
998 | $Bcolj[$k] = $B->A[$k][$j]; |
||
999 | } |
||
1000 | View Code Duplication | for ($i = 0; $i < $this->m; ++$i) { |
|
1001 | $Arowi = $this->A[$i]; |
||
1002 | $s = 0; |
||
1003 | for ($k = 0; $k < $this->n; ++$k) { |
||
1004 | $s += $Arowi[$k] * $Bcolj[$k]; |
||
1005 | } |
||
1006 | $C->A[$i][$j] = $s; |
||
1007 | } |
||
1008 | } |
||
1009 | |||
1010 | return $C; |
||
1011 | } |
||
1012 | |||
1013 | throw new CalculationException(self::MATRIX_DIMENSION_EXCEPTION); |
||
1014 | case 'array': |
||
1015 | $B = new self($args[0]); |
||
1016 | if ($this->n == $B->m) { |
||
1017 | $C = new self($this->m, $B->n); |
||
1018 | for ($i = 0; $i < $C->m; ++$i) { |
||
1019 | View Code Duplication | for ($j = 0; $j < $C->n; ++$j) { |
|
1020 | $s = '0'; |
||
1021 | for ($k = 0; $k < $C->n; ++$k) { |
||
1022 | $s += $this->A[$i][$k] * $B->A[$k][$j]; |
||
1023 | } |
||
1024 | $C->A[$i][$j] = $s; |
||
1025 | } |
||
1026 | } |
||
1027 | |||
1028 | return $C; |
||
1029 | } |
||
1030 | |||
1031 | throw new CalculationException(self::MATRIX_DIMENSION_EXCEPTION); |
||
1032 | View Code Duplication | case 'integer': |
|
1033 | $C = new self($this->A); |
||
1034 | for ($i = 0; $i < $C->m; ++$i) { |
||
1035 | for ($j = 0; $j < $C->n; ++$j) { |
||
1036 | $C->A[$i][$j] *= $args[0]; |
||
1037 | } |
||
1038 | } |
||
1039 | |||
1040 | return $C; |
||
1041 | case 'double': |
||
1042 | $C = new self($this->m, $this->n); |
||
1043 | for ($i = 0; $i < $C->m; ++$i) { |
||
1044 | for ($j = 0; $j < $C->n; ++$j) { |
||
1045 | $C->A[$i][$j] = $args[0] * $this->A[$i][$j]; |
||
1046 | } |
||
1047 | } |
||
1048 | |||
1049 | return $C; |
||
1050 | View Code Duplication | case 'float': |
|
1051 | $C = new self($this->A); |
||
1052 | for ($i = 0; $i < $C->m; ++$i) { |
||
1053 | for ($j = 0; $j < $C->n; ++$j) { |
||
1054 | $C->A[$i][$j] *= $args[0]; |
||
1055 | } |
||
1056 | } |
||
1057 | |||
1058 | return $C; |
||
1059 | default: |
||
1060 | throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); |
||
1061 | } |
||
1062 | } else { |
||
1063 | throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); |
||
1064 | } |
||
1065 | } |
||
1066 | |||
1067 | /** |
||
1068 | * power. |
||
1069 | * |
||
1070 | * A = A ^ B |
||
1071 | * |
||
1072 | * @param mixed $B Matrix/Array |
||
1073 | * |
||
1074 | * @return Matrix Sum |
||
1075 | */ |
||
1076 | public function power(...$args) |
||
1077 | { |
||
1078 | if (count() > 0) { |
||
1079 | $match = implode(',', array_map('gettype', $args)); |
||
1080 | |||
1081 | switch ($match) { |
||
1082 | case 'object': |
||
1083 | if ($args[0] instanceof self) { |
||
1084 | $M = $args[0]; |
||
1085 | } else { |
||
1086 | throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); |
||
1087 | } |
||
1088 | |||
1089 | break; |
||
1090 | case 'array': |
||
1091 | $M = new self($args[0]); |
||
1092 | |||
1093 | break; |
||
1094 | default: |
||
1095 | throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); |
||
1096 | break; |
||
1097 | } |
||
1098 | $this->checkMatrixDimensions($M); |
||
1099 | for ($i = 0; $i < $this->m; ++$i) { |
||
1100 | for ($j = 0; $j < $this->n; ++$j) { |
||
1101 | $validValues = true; |
||
1102 | $value = $M->get($i, $j); |
||
1103 | if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) { |
||
1104 | $this->A[$i][$j] = trim($this->A[$i][$j], '"'); |
||
1105 | $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]); |
||
1106 | } |
||
1107 | if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) { |
||
1108 | $value = trim($value, '"'); |
||
1109 | $validValues &= StringHelper::convertToNumberIfFraction($value); |
||
1110 | } |
||
1111 | if ($validValues) { |
||
1112 | $this->A[$i][$j] = pow($this->A[$i][$j], $value); |
||
1 ignored issue
–
show
|
|||
1113 | } else { |
||
1114 | $this->A[$i][$j] = Functions::NAN(); |
||
1115 | } |
||
1116 | } |
||
1117 | } |
||
1118 | |||
1119 | return $this; |
||
1120 | } |
||
1121 | |||
1122 | throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); |
||
1123 | } |
||
1124 | |||
1125 | /** |
||
1126 | * concat. |
||
1127 | * |
||
1128 | * A = A & B |
||
1129 | * |
||
1130 | * @param mixed $B Matrix/Array |
||
1131 | * |
||
1132 | * @return Matrix Sum |
||
1133 | */ |
||
1134 | public function concat(...$args) |
||
1135 | { |
||
1136 | if (count($args) > 0) { |
||
1137 | $match = implode(',', array_map('gettype', $args)); |
||
1138 | |||
1139 | switch ($match) { |
||
1140 | case 'object': |
||
1141 | if ($args[0] instanceof self) { |
||
1142 | $M = $args[0]; |
||
1143 | } else { |
||
1144 | throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); |
||
1145 | } |
||
1146 | |||
1147 | break; |
||
1148 | case 'array': |
||
1149 | $M = new self($args[0]); |
||
1150 | |||
1151 | break; |
||
1152 | default: |
||
1153 | throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); |
||
1154 | break; |
||
1155 | } |
||
1156 | $this->checkMatrixDimensions($M); |
||
1157 | for ($i = 0; $i < $this->m; ++$i) { |
||
1158 | for ($j = 0; $j < $this->n; ++$j) { |
||
1159 | $this->A[$i][$j] = trim($this->A[$i][$j], '"') . trim($M->get($i, $j), '"'); |
||
1160 | } |
||
1161 | } |
||
1162 | |||
1163 | return $this; |
||
1164 | } |
||
1165 | |||
1166 | throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); |
||
1167 | } |
||
1168 | |||
1169 | /** |
||
1170 | * Solve A*X = B. |
||
1171 | * |
||
1172 | * @param Matrix $B Right hand side |
||
1173 | * |
||
1174 | * @return Matrix ... Solution if A is square, least squares solution otherwise |
||
1175 | */ |
||
1176 | public function solve($B) |
||
1177 | { |
||
1178 | if ($this->m == $this->n) { |
||
1179 | $LU = new LUDecomposition($this); |
||
1180 | |||
1181 | return $LU->solve($B); |
||
1182 | } |
||
1183 | $QR = new QRDecomposition($this); |
||
1184 | |||
1185 | return $QR->solve($B); |
||
1186 | } |
||
1187 | |||
1188 | /** |
||
1189 | * Matrix inverse or pseudoinverse. |
||
1190 | * |
||
1191 | * @return Matrix ... Inverse(A) if A is square, pseudoinverse otherwise. |
||
1192 | */ |
||
1193 | public function inverse() |
||
1194 | { |
||
1195 | return $this->solve($this->identity($this->m, $this->m)); |
||
1196 | } |
||
1197 | |||
1198 | /** |
||
1199 | * det. |
||
1200 | * |
||
1201 | * Calculate determinant |
||
1202 | * |
||
1203 | * @return float Determinant |
||
1204 | */ |
||
1205 | public function det() |
||
1210 | } |
||
1211 | } |
||
1212 |
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.