/wp-content/plugins/quiz-maker/includes/PHPExcel/vendor/markbaker/matrix/classes/src/Matrix.php

https://github.com/livinglab/openlab · PHP · 403 lines · 188 code · 40 blank · 175 comment · 24 complexity · 3ef76844260857762719f9fe6c727f08 MD5 · raw file

  1. <?php
  2. /**
  3. *
  4. * Class for the management of Matrices
  5. *
  6. * @copyright Copyright (c) 2018 Mark Baker (https://github.com/MarkBaker/PHPMatrix)
  7. * @license https://opensource.org/licenses/MIT MIT
  8. */
  9. namespace Matrix;
  10. /**
  11. * Matrix object.
  12. *
  13. * @package Matrix
  14. *
  15. * @property-read int $rows The number of rows in the matrix
  16. * @property-read int $columns The number of columns in the matrix
  17. * @method Matrix antidiagonal()
  18. * @method Matrix adjoint()
  19. * @method Matrix cofactors()
  20. * @method float determinant()
  21. * @method Matrix diagonal()
  22. * @method Matrix identity()
  23. * @method Matrix inverse()
  24. * @method Matrix pseudoInverse()
  25. * @method Matrix minors()
  26. * @method float trace()
  27. * @method Matrix transpose()
  28. * @method Matrix add(...$matrices)
  29. * @method Matrix subtract(...$matrices)
  30. * @method Matrix multiply(...$matrices)
  31. * @method Matrix divideby(...$matrices)
  32. * @method Matrix divideinto(...$matrices)
  33. */
  34. class Matrix
  35. {
  36. protected $rows;
  37. protected $columns;
  38. protected $grid = [];
  39. /*
  40. * Create a new Matrix object from an array of values
  41. *
  42. * @param array $grid
  43. */
  44. public function __construct(array $grid)
  45. {
  46. $this->buildFromArray(array_values($grid));
  47. }
  48. /*
  49. * Create a new Matrix object from an array of values
  50. *
  51. * @param array $grid
  52. */
  53. protected function buildFromArray(array $grid)
  54. {
  55. $this->rows = count($grid);
  56. $columns = array_reduce(
  57. $grid,
  58. function ($carry, $value) {
  59. return max($carry, is_array($value) ? count($value) : 1);
  60. },
  61. 0
  62. );
  63. $this->columns = $columns;
  64. array_walk(
  65. $grid,
  66. function (&$value) use ($columns) {
  67. if (!is_array($value)) {
  68. $value = [$value];
  69. }
  70. $value = array_pad(array_values($value), $columns, null);
  71. }
  72. );
  73. $this->grid = $grid;
  74. }
  75. /**
  76. * Validate that a row number is a positive integer
  77. *
  78. * @param $row
  79. * @return int
  80. * @throws Exception
  81. */
  82. public static function validateRow($row)
  83. {
  84. if ((!is_numeric($row)) || (intval($row) < 1)) {
  85. throw new Exception('Invalid Row');
  86. }
  87. return (int) $row;
  88. }
  89. /**
  90. * Validate that a column number is a positive integer
  91. *
  92. * @param $column
  93. * @return int
  94. * @throws Exception
  95. */
  96. public static function validateColumn($column)
  97. {
  98. if ((!is_numeric($column)) || (intval($column) < 1)) {
  99. throw new Exception('Invalid Column');
  100. }
  101. return (int) $column;
  102. }
  103. /**
  104. * Validate that a row number falls within the set of rows for this matrix
  105. *
  106. * @param $row
  107. * @return int
  108. * @throws Exception
  109. */
  110. protected function validateRowInRange($row)
  111. {
  112. $row = static::validateRow($row);
  113. if ($row > $this->rows) {
  114. throw new Exception('Requested Row exceeds matrix size');
  115. }
  116. return $row;
  117. }
  118. /**
  119. * Validate that a column number falls within the set of columns for this matrix
  120. *
  121. * @param $column
  122. * @return int
  123. * @throws Exception
  124. */
  125. protected function validateColumnInRange($column)
  126. {
  127. $column = static::validateColumn($column);
  128. if ($column > $this->columns) {
  129. throw new Exception('Requested Column exceeds matrix size');
  130. }
  131. return $column;
  132. }
  133. /**
  134. * Return a new matrix as a subset of rows from this matrix, starting at row number $row, and $rowCount rows
  135. * A $rowCount value of 0 will return all rows of the matrix from $row
  136. * A negative $rowCount value will return rows until that many rows from the end of the matrix
  137. *
  138. * Note that row numbers start from 1, not from 0
  139. *
  140. * @param $row
  141. * @param int $rowCount
  142. * @return static
  143. * @throws Exception
  144. */
  145. public function getRows($row, $rowCount = 1)
  146. {
  147. $row = $this->validateRowInRange($row);
  148. if ($rowCount == 0) {
  149. $rowCount = $this->rows - $row + 1;
  150. }
  151. return new static(array_slice($this->grid, $row - 1, $rowCount));
  152. }
  153. /**
  154. * Return a new matrix as a subset of columns from this matrix, starting at column number $column, and $columnCount columns
  155. * A $columnCount value of 0 will return all columns of the matrix from $column
  156. * A negative $columnCount value will return columns until that many columns from the end of the matrix
  157. *
  158. * Note that column numbers start from 1, not from 0
  159. *
  160. * @param $column
  161. * @param int $columnCount
  162. * @return static
  163. * @throws Exception
  164. */
  165. public function getColumns($column, $columnCount = 1)
  166. {
  167. $column = $this->validateColumnInRange($column);
  168. if ($columnCount < 1) {
  169. $columnCount = $this->columns + $columnCount - $column + 1;
  170. }
  171. $grid = [];
  172. for ($i = $column - 1; $i < $column + $columnCount - 1; ++$i) {
  173. $grid[] = array_column($this->grid, $i);
  174. }
  175. return (new static($grid))->transpose();
  176. }
  177. /**
  178. * Return a new matrix as a subset of rows from this matrix, dropping rows starting at row number $row,
  179. * and $rowCount rows
  180. * A negative $rowCount value will drop rows until that many rows from the end of the matrix
  181. * A $rowCount value of 0 will remove all rows of the matrix from $row
  182. *
  183. * Note that row numbers start from 1, not from 0
  184. *
  185. * @param $row
  186. * @param int $rowCount
  187. * @return static
  188. * @throws Exception
  189. */
  190. public function dropRows($row, $rowCount = 1)
  191. {
  192. $this->validateRowInRange($row);
  193. if ($rowCount == 0) {
  194. $rowCount = $this->rows - $row + 1;
  195. }
  196. $grid = $this->grid;
  197. array_splice($grid, $row - 1, $rowCount);
  198. return new static($grid);
  199. }
  200. /**
  201. * Return a new matrix as a subset of columns from this matrix, dropping columns starting at column number $column,
  202. * and $columnCount columns
  203. * A negative $columnCount value will drop columns until that many columns from the end of the matrix
  204. * A $columnCount value of 0 will remove all columns of the matrix from $column
  205. *
  206. * Note that column numbers start from 1, not from 0
  207. *
  208. * @param $column
  209. * @param int $columnCount
  210. * @return static
  211. * @throws Exception
  212. */
  213. public function dropColumns($column, $columnCount = 1)
  214. {
  215. $this->validateColumnInRange($column);
  216. if ($columnCount < 1) {
  217. $columnCount = $this->columns + $columnCount - $column + 1;
  218. }
  219. $grid = $this->grid;
  220. array_walk(
  221. $grid,
  222. function (&$row) use ($column, $columnCount) {
  223. array_splice($row, $column - 1, $columnCount);
  224. }
  225. );
  226. return new static($grid);
  227. }
  228. /**
  229. * Return a value from this matrix, from the "cell" identified by the row and column numbers
  230. * Note that row and column numbers start from 1, not from 0
  231. *
  232. * @param $row
  233. * @param $column
  234. * @return static
  235. * @throws Exception
  236. */
  237. public function getValue($row, $column)
  238. {
  239. $row = $this->validateRowInRange($row);
  240. $column = $this->validateColumnInRange($column);
  241. return $this->grid[$row - 1][$column - 1];
  242. }
  243. /**
  244. * Returns a Generator that will yield each row of the matrix in turn as a vector matrix
  245. * or the value of each cell if the matrix is a vector
  246. *
  247. * @return \Generator|Matrix[]|mixed[]
  248. */
  249. public function rows()
  250. {
  251. foreach ($this->grid as $i => $row) {
  252. yield $i + 1 => ($this->columns == 1)
  253. ? $row[0]
  254. : new static([$row]);
  255. }
  256. }
  257. /**
  258. * Returns a Generator that will yield each column of the matrix in turn as a vector matrix
  259. * or the value of each cell if the matrix is a vector
  260. *
  261. * @return \Generator|Matrix[]|mixed[]
  262. */
  263. public function columns()
  264. {
  265. for ($i = 0; $i < $this->columns; ++$i) {
  266. yield $i + 1 => ($this->rows == 1)
  267. ? $this->grid[0][$i]
  268. : new static(array_column($this->grid, $i));
  269. }
  270. }
  271. /**
  272. * Identify if the row and column dimensions of this matrix are equal,
  273. * i.e. if it is a "square" matrix
  274. *
  275. * @return bool
  276. */
  277. public function isSquare()
  278. {
  279. return $this->rows == $this->columns;
  280. }
  281. /**
  282. * Identify if this matrix is a vector
  283. * i.e. if it comprises only a single row or a single column
  284. *
  285. * @return bool
  286. */
  287. public function isVector()
  288. {
  289. return $this->rows == 1 || $this->columns == 1;
  290. }
  291. /**
  292. * Return the matrix as a 2-dimensional array
  293. *
  294. * @return array
  295. */
  296. public function toArray()
  297. {
  298. return $this->grid;
  299. }
  300. protected static $getters = [
  301. 'rows',
  302. 'columns',
  303. ];
  304. /**
  305. * Access specific properties as read-only (no setters)
  306. *
  307. * @param $propertyName
  308. * @return mixed
  309. * @throws Exception
  310. */
  311. public function __get($propertyName)
  312. {
  313. $propertyName = strtolower($propertyName);
  314. // Test for function calls
  315. if (in_array($propertyName, self::$getters)) {
  316. return $this->$propertyName;
  317. }
  318. throw new Exception('Property does not exist');
  319. }
  320. protected static $functions = [
  321. 'antidiagonal',
  322. 'adjoint',
  323. 'cofactors',
  324. 'determinant',
  325. 'diagonal',
  326. 'identity',
  327. 'inverse',
  328. 'minors',
  329. 'trace',
  330. 'transpose',
  331. ];
  332. protected static $operations = [
  333. 'add',
  334. 'subtract',
  335. 'multiply',
  336. 'divideby',
  337. 'divideinto',
  338. 'directsum',
  339. ];
  340. /**
  341. * Returns the result of the function call or operation
  342. *
  343. * @param string $functionName
  344. * @param mixed[] $arguments
  345. * @return Matrix|float
  346. * @throws Exception|\InvalidArgumentException
  347. */
  348. public function __call($functionName, $arguments)
  349. {
  350. $functionName = strtolower(str_replace('_', '', $functionName));
  351. // Test for function calls
  352. if (in_array($functionName, self::$functions)) {
  353. $functionName = "\\" . __NAMESPACE__ . "\\{$functionName}";
  354. return $functionName($this, ...$arguments);
  355. }
  356. // Test for operation calls
  357. if (in_array($functionName, self::$operations)) {
  358. $functionName = "\\" . __NAMESPACE__ . "\\{$functionName}";
  359. return $functionName($this, ...$arguments);
  360. }
  361. throw new Exception('Function or Operation does not exist');
  362. }
  363. }