ArrayArgumentProcessor.php 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. <?php
  2. namespace PhpOffice\PhpSpreadsheet\Calculation\Engine;
  3. use PhpOffice\PhpSpreadsheet\Calculation\Functions;
  4. class ArrayArgumentProcessor
  5. {
  6. /**
  7. * @var ArrayArgumentHelper
  8. */
  9. private static $arrayArgumentHelper;
  10. /**
  11. * @param mixed ...$arguments
  12. */
  13. public static function processArguments(
  14. ArrayArgumentHelper $arrayArgumentHelper,
  15. callable $method,
  16. ...$arguments
  17. ): array {
  18. self::$arrayArgumentHelper = $arrayArgumentHelper;
  19. if (self::$arrayArgumentHelper->hasArrayArgument() === false) {
  20. return [$method(...$arguments)];
  21. }
  22. if (self::$arrayArgumentHelper->arrayArguments() === 1) {
  23. $nthArgument = self::$arrayArgumentHelper->getFirstArrayArgumentNumber();
  24. return self::evaluateNthArgumentAsArray($method, $nthArgument, ...$arguments);
  25. }
  26. $singleRowVectorIndex = self::$arrayArgumentHelper->getSingleRowVector();
  27. $singleColumnVectorIndex = self::$arrayArgumentHelper->getSingleColumnVector();
  28. if ($singleRowVectorIndex !== null && $singleColumnVectorIndex !== null) {
  29. // Basic logic for a single row vector and a single column vector
  30. return self::evaluateVectorPair($method, $singleRowVectorIndex, $singleColumnVectorIndex, ...$arguments);
  31. }
  32. $matrixPair = self::$arrayArgumentHelper->getMatrixPair();
  33. if ($matrixPair !== []) {
  34. if (
  35. (self::$arrayArgumentHelper->isVector($matrixPair[0]) === true &&
  36. self::$arrayArgumentHelper->isVector($matrixPair[1]) === false) ||
  37. (self::$arrayArgumentHelper->isVector($matrixPair[0]) === false &&
  38. self::$arrayArgumentHelper->isVector($matrixPair[1]) === true)
  39. ) {
  40. // Logic for a matrix and a vector (row or column)
  41. return self::evaluateVectorMatrixPair($method, $matrixPair, ...$arguments);
  42. }
  43. // Logic for matrix/matrix, column vector/column vector or row vector/row vector
  44. return self::evaluateMatrixPair($method, $matrixPair, ...$arguments);
  45. }
  46. // Still need to work out the logic for more than two array arguments,
  47. // For the moment, we're throwing an Exception when we initialise the ArrayArgumentHelper
  48. return ['#VALUE!'];
  49. }
  50. /**
  51. * @param mixed ...$arguments
  52. */
  53. private static function evaluateVectorMatrixPair(callable $method, array $matrixIndexes, ...$arguments): array
  54. {
  55. $matrix2 = array_pop($matrixIndexes);
  56. /** @var array $matrixValues2 */
  57. $matrixValues2 = $arguments[$matrix2];
  58. $matrix1 = array_pop($matrixIndexes);
  59. /** @var array $matrixValues1 */
  60. $matrixValues1 = $arguments[$matrix1];
  61. $rows = min(array_map([self::$arrayArgumentHelper, 'rowCount'], [$matrix1, $matrix2]));
  62. $columns = min(array_map([self::$arrayArgumentHelper, 'columnCount'], [$matrix1, $matrix2]));
  63. if ($rows === 1) {
  64. $rows = max(array_map([self::$arrayArgumentHelper, 'rowCount'], [$matrix1, $matrix2]));
  65. }
  66. if ($columns === 1) {
  67. $columns = max(array_map([self::$arrayArgumentHelper, 'columnCount'], [$matrix1, $matrix2]));
  68. }
  69. $result = [];
  70. for ($rowIndex = 0; $rowIndex < $rows; ++$rowIndex) {
  71. for ($columnIndex = 0; $columnIndex < $columns; ++$columnIndex) {
  72. $rowIndex1 = self::$arrayArgumentHelper->isRowVector($matrix1) ? 0 : $rowIndex;
  73. $columnIndex1 = self::$arrayArgumentHelper->isColumnVector($matrix1) ? 0 : $columnIndex;
  74. $value1 = $matrixValues1[$rowIndex1][$columnIndex1];
  75. $rowIndex2 = self::$arrayArgumentHelper->isRowVector($matrix2) ? 0 : $rowIndex;
  76. $columnIndex2 = self::$arrayArgumentHelper->isColumnVector($matrix2) ? 0 : $columnIndex;
  77. $value2 = $matrixValues2[$rowIndex2][$columnIndex2];
  78. $arguments[$matrix1] = $value1;
  79. $arguments[$matrix2] = $value2;
  80. $result[$rowIndex][$columnIndex] = $method(...$arguments);
  81. }
  82. }
  83. return $result;
  84. }
  85. /**
  86. * @param mixed ...$arguments
  87. */
  88. private static function evaluateMatrixPair(callable $method, array $matrixIndexes, ...$arguments): array
  89. {
  90. $matrix2 = array_pop($matrixIndexes);
  91. /** @var array $matrixValues2 */
  92. $matrixValues2 = $arguments[$matrix2];
  93. $matrix1 = array_pop($matrixIndexes);
  94. /** @var array $matrixValues1 */
  95. $matrixValues1 = $arguments[$matrix1];
  96. $result = [];
  97. foreach ($matrixValues1 as $rowIndex => $row) {
  98. foreach ($row as $columnIndex => $value1) {
  99. if (isset($matrixValues2[$rowIndex][$columnIndex]) === false) {
  100. continue;
  101. }
  102. $value2 = $matrixValues2[$rowIndex][$columnIndex];
  103. $arguments[$matrix1] = $value1;
  104. $arguments[$matrix2] = $value2;
  105. $result[$rowIndex][$columnIndex] = $method(...$arguments);
  106. }
  107. }
  108. return $result;
  109. }
  110. /**
  111. * @param mixed ...$arguments
  112. */
  113. private static function evaluateVectorPair(callable $method, int $rowIndex, int $columnIndex, ...$arguments): array
  114. {
  115. $rowVector = Functions::flattenArray($arguments[$rowIndex]);
  116. $columnVector = Functions::flattenArray($arguments[$columnIndex]);
  117. $result = [];
  118. foreach ($columnVector as $column) {
  119. $rowResults = [];
  120. foreach ($rowVector as $row) {
  121. $arguments[$rowIndex] = $row;
  122. $arguments[$columnIndex] = $column;
  123. $rowResults[] = $method(...$arguments);
  124. }
  125. $result[] = $rowResults;
  126. }
  127. return $result;
  128. }
  129. /**
  130. * Note, offset is from 1 (for the first argument) rather than from 0.
  131. *
  132. * @param mixed ...$arguments
  133. */
  134. private static function evaluateNthArgumentAsArray(callable $method, int $nthArgument, ...$arguments): array
  135. {
  136. $values = array_slice($arguments, $nthArgument - 1, 1);
  137. /** @var array $values */
  138. $values = array_pop($values);
  139. $result = [];
  140. foreach ($values as $value) {
  141. $arguments[$nthArgument - 1] = $value;
  142. $result[] = $method(...$arguments);
  143. }
  144. return $result;
  145. }
  146. }