Additional conditionals from math trig (#1885)

* Use our new Conditional logic to implement the SUMIF() and SUMIFS() Mathematical functions
This commit is contained in:
Mark Baker 2021-02-28 10:24:33 +01:00 committed by GitHub
parent 761c84a946
commit ee969fdcfe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 151 additions and 99 deletions

View File

@ -30,7 +30,7 @@ for ($col = 1; $col <= 50; ++$col) {
} }
} }
$d = microtime(true) - $t; $d = microtime(true) - $t;
$helper->log('Add data (end) . time: ' . round((string) ($d . 2)) . ' s'); $helper->log('Add data (end) . time: ' . (string) round($d, 2) . ' s');
// Save // Save
$helper->write($spreadsheet, __FILE__); $helper->write($spreadsheet, __FILE__);

View File

@ -2314,12 +2314,12 @@ class Calculation
], ],
'SUMIF' => [ 'SUMIF' => [
'category' => Category::CATEGORY_MATH_AND_TRIG, 'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig::class, 'SUMIF'], 'functionCall' => [Statistical\Conditional::class, 'SUMIF'],
'argumentCount' => '2,3', 'argumentCount' => '2,3',
], ],
'SUMIFS' => [ 'SUMIFS' => [
'category' => Category::CATEGORY_MATH_AND_TRIG, 'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig::class, 'SUMIFS'], 'functionCall' => [Statistical\Conditional::class, 'SUMIFS'],
'argumentCount' => '3+', 'argumentCount' => '3+',
], ],
'SUMPRODUCT' => [ 'SUMPRODUCT' => [

View File

@ -162,6 +162,10 @@ abstract class DatabaseAbstract
$dataValue = ($dataValues[$key]) ? 'TRUE' : 'FALSE'; $dataValue = ($dataValues[$key]) ? 'TRUE' : 'FALSE';
} elseif ($dataValues[$key] !== null) { } elseif ($dataValues[$key] !== null) {
$dataValue = $dataValues[$key]; $dataValue = $dataValues[$key];
// escape quotes if we have a string containing quotes
if (is_string($dataValue) && strpos($dataValue, '"') !== false) {
$dataValue = str_replace('"', '""', $dataValue);
}
$dataValue = (is_string($dataValue)) ? Calculation::wrapResult(strtoupper($dataValue)) : $dataValue; $dataValue = (is_string($dataValue)) ? Calculation::wrapResult(strtoupper($dataValue)) : $dataValue;
} }

View File

@ -1284,44 +1284,22 @@ class MathTrig
* Totals the values of cells that contain numbers within the list of arguments * Totals the values of cells that contain numbers within the list of arguments
* *
* Excel Function: * Excel Function:
* SUMIF(value1[,value2[, ...]],condition) * SUMIF(range, criteria, [sum_range])
* *
* @param mixed $aArgs Data values * @Deprecated 1.17.0
* @param string $condition the criteria that defines which cells will be summed *
* @param mixed $sumArgs * @see Statistical\Conditional::SUMIF()
* Use the SUMIF() method in the Statistical\Conditional class instead
*
* @param mixed $range Data values
* @param string $criteria the criteria that defines which cells will be summed
* @param mixed $sumRange
* *
* @return float * @return float
*/ */
public static function SUMIF($aArgs, $condition, $sumArgs = []) public static function SUMIF($range, $criteria, $sumRange = [])
{ {
$returnValue = 0; return Statistical\Conditional::SUMIF($range, $criteria, $sumRange);
$aArgs = Functions::flattenArray($aArgs);
$sumArgs = Functions::flattenArray($sumArgs);
if (empty($sumArgs)) {
$sumArgs = $aArgs;
}
$condition = Functions::ifCondition($condition);
// Loop through arguments
foreach ($aArgs as $key => $arg) {
if (!is_numeric($arg)) {
$arg = str_replace('"', '""', $arg);
$arg = Calculation::wrapResult(strtoupper($arg));
}
$testCondition = '=' . $arg . $condition;
$sumValue = array_key_exists($key, $sumArgs) ? $sumArgs[$key] : 0;
if (
is_numeric($sumValue) &&
Calculation::getInstance()->_calculateFormulaValue($testCondition)
) {
// Is it a value within our criteria and only numeric can be added to the result
$returnValue += $sumValue;
}
}
return $returnValue;
} }
/** /**
@ -1330,7 +1308,12 @@ class MathTrig
* Totals the values of cells that contain numbers within the list of arguments * Totals the values of cells that contain numbers within the list of arguments
* *
* Excel Function: * Excel Function:
* SUMIFS(value1[,value2[, ...]],condition) * SUMIFS(sum_range, criteria_range1, criteria1, [criteria_range2, criteria2], ...)
*
* @Deprecated 1.17.0
*
* @see Statistical\Conditional::SUMIFS()
* Use the SUMIFS() method in the Statistical\Conditional class instead
* *
* @param mixed $args Data values * @param mixed $args Data values
* *
@ -1338,47 +1321,7 @@ class MathTrig
*/ */
public static function SUMIFS(...$args) public static function SUMIFS(...$args)
{ {
$arrayList = $args; return Statistical\Conditional::SUMIFS(...$args);
// Return value
$returnValue = 0;
$sumArgs = Functions::flattenArray(array_shift($arrayList));
$aArgsArray = [];
$conditions = [];
while (count($arrayList) > 0) {
$aArgsArray[] = Functions::flattenArray(array_shift($arrayList));
$conditions[] = Functions::ifCondition(array_shift($arrayList));
}
// Loop through each sum and see if arguments and conditions are true
foreach ($sumArgs as $index => $value) {
$valid = true;
foreach ($conditions as $cidx => $condition) {
$arg = $aArgsArray[$cidx][$index];
// Loop through arguments
if (!is_numeric($arg)) {
$arg = Calculation::wrapResult(strtoupper($arg));
}
$testCondition = '=' . $arg . $condition;
if (!Calculation::getInstance()->_calculateFormulaValue($testCondition)) {
// Is not a value within our criteria
$valid = false;
break; // if false found, don't need to check other conditions
}
}
if ($valid) {
$returnValue += $value;
}
}
// Return
return $returnValue;
} }
/** /**

View File

@ -6,6 +6,7 @@ use PhpOffice\PhpSpreadsheet\Calculation\Database\DAverage;
use PhpOffice\PhpSpreadsheet\Calculation\Database\DCount; use PhpOffice\PhpSpreadsheet\Calculation\Database\DCount;
use PhpOffice\PhpSpreadsheet\Calculation\Database\DMax; use PhpOffice\PhpSpreadsheet\Calculation\Database\DMax;
use PhpOffice\PhpSpreadsheet\Calculation\Database\DMin; use PhpOffice\PhpSpreadsheet\Calculation\Database\DMin;
use PhpOffice\PhpSpreadsheet\Calculation\Database\DSum;
use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class Conditional class Conditional
@ -30,18 +31,7 @@ class Conditional
*/ */
public static function AVERAGEIF($range, $condition, $averageRange = []) public static function AVERAGEIF($range, $condition, $averageRange = [])
{ {
$range = Functions::flattenArray($range); $database = self::databaseFromRangeAndValue($range, $averageRange);
$averageRange = Functions::flattenArray($averageRange);
if (empty($averageRange)) {
$averageRange = $range;
}
$database = array_map(
null,
array_merge([self::CONDITION_COLUMN_NAME], $range),
array_merge([self::VALUE_COLUMN_NAME], $averageRange)
);
$condition = [[self::CONDITION_COLUMN_NAME, self::VALUE_COLUMN_NAME], [$condition, null]]; $condition = [[self::CONDITION_COLUMN_NAME, self::VALUE_COLUMN_NAME], [$condition, null]];
return DAverage::evaluate($database, self::VALUE_COLUMN_NAME, $condition); return DAverage::evaluate($database, self::VALUE_COLUMN_NAME, $condition);
@ -64,11 +54,11 @@ class Conditional
if (empty($args)) { if (empty($args)) {
return 0.0; return 0.0;
} elseif (count($args) === 3) { } elseif (count($args) === 3) {
return self::AVERAGEIF($args[2], $args[1], $args[0]); return self::AVERAGEIF($args[1], $args[2], $args[0]);
} }
$conditions = self::buildConditionSetForRange(...$args); $conditions = self::buildConditionSetForValueRange(...$args);
$database = self::buildDatabaseWithRange(...$args); $database = self::buildDatabaseWithValueRange(...$args);
return DAverage::evaluate($database, self::VALUE_COLUMN_NAME, $conditions); return DAverage::evaluate($database, self::VALUE_COLUMN_NAME, $conditions);
} }
@ -146,8 +136,8 @@ class Conditional
return 0.0; return 0.0;
} }
$conditions = self::buildConditionSetForRange(...$args); $conditions = self::buildConditionSetForValueRange(...$args);
$database = self::buildDatabaseWithRange(...$args); $database = self::buildDatabaseWithValueRange(...$args);
return DMax::evaluate($database, self::VALUE_COLUMN_NAME, $conditions); return DMax::evaluate($database, self::VALUE_COLUMN_NAME, $conditions);
} }
@ -170,12 +160,60 @@ class Conditional
return 0.0; return 0.0;
} }
$conditions = self::buildConditionSetForRange(...$args); $conditions = self::buildConditionSetForValueRange(...$args);
$database = self::buildDatabaseWithRange(...$args); $database = self::buildDatabaseWithValueRange(...$args);
return DMin::evaluate($database, self::VALUE_COLUMN_NAME, $conditions); return DMin::evaluate($database, self::VALUE_COLUMN_NAME, $conditions);
} }
/**
* SUMIF.
*
* Totals the values of cells that contain numbers within the list of arguments
*
* Excel Function:
* SUMIF(range, criteria, [sum_range])
*
* @param mixed $range Data values
* @param mixed $sumRange
* @param mixed $condition
*
* @return float
*/
public static function SUMIF($range, $condition, $sumRange = [])
{
$database = self::databaseFromRangeAndValue($range, $sumRange);
$condition = [[self::CONDITION_COLUMN_NAME, self::VALUE_COLUMN_NAME], [$condition, null]];
return DSum::evaluate($database, self::VALUE_COLUMN_NAME, $condition);
}
/**
* SUMIFS.
*
* Counts the number of cells that contain numbers within the list of arguments
*
* Excel Function:
* SUMIFS(average_range, criteria_range1, criteria1, [criteria_range2, criteria2])
*
* @param mixed $args Pairs of Ranges and Criteria
*
* @return null|float|string
*/
public static function SUMIFS(...$args)
{
if (empty($args)) {
return 0.0;
} elseif (count($args) === 3) {
return self::SUMIF($args[1], $args[2], $args[0]);
}
$conditions = self::buildConditionSetForValueRange(...$args);
$database = self::buildDatabaseWithValueRange(...$args);
return DSum::evaluate($database, self::VALUE_COLUMN_NAME, $conditions);
}
private static function buildConditionSet(...$args): array private static function buildConditionSet(...$args): array
{ {
$conditions = self::buildConditions(1, ...$args); $conditions = self::buildConditions(1, ...$args);
@ -183,7 +221,7 @@ class Conditional
return array_map(null, ...$conditions); return array_map(null, ...$conditions);
} }
private static function buildConditionSetForRange(...$args): array private static function buildConditionSetForValueRange(...$args): array
{ {
$conditions = self::buildConditions(2, ...$args); $conditions = self::buildConditions(2, ...$args);
@ -220,7 +258,7 @@ class Conditional
return self::buildDataSet(0, $database, ...$args); return self::buildDataSet(0, $database, ...$args);
} }
private static function buildDatabaseWithRange(...$args): array private static function buildDatabaseWithValueRange(...$args): array
{ {
$database = []; $database = [];
$database[] = array_merge( $database[] = array_merge(
@ -245,4 +283,22 @@ class Conditional
return array_map(null, ...$database); return array_map(null, ...$database);
} }
private static function databaseFromRangeAndValue(array $range, array $valueRange = []): array
{
$range = Functions::flattenArray($range);
$valueRange = Functions::flattenArray($valueRange);
if (empty($valueRange)) {
$valueRange = $range;
}
$database = array_map(
null,
array_merge([self::CONDITION_COLUMN_NAME], $range),
array_merge([self::VALUE_COLUMN_NAME], $valueRange)
);
return $database;
}
} }

View File

@ -120,4 +120,22 @@ return [
[5], [5],
], ],
], ],
[
157559,
['Jan', 'Jan', 'Jan', 'Jan', 'Feb', 'Feb', 'Feb', 'Feb'],
'Feb',
[36693, 22100, 53321, 34440, 29889, 50090, 32080, 45500],
],
[
66582,
['North 1', 'North 2', 'South 1', 'South 2', 'North 1', 'North 2', 'South 1', 'South 2,'],
'North 1',
[36693, 22100, 53321, 34440, 29889, 50090, 32080, 45500],
],
[
138772,
['North 1', 'North 2', 'South 1', 'South 2', 'North 1', 'North 2', 'South 1', 'South 2,'],
'North ?',
[36693, 22100, 53321, 34440, 29889, 50090, 32080, 45500],
],
]; ];

View File

@ -1,6 +1,9 @@
<?php <?php
return [ return [
[
0,
],
[ [
2, 2,
[ [
@ -41,4 +44,20 @@ return [
], ],
'=B', '=B',
], ],
[
348000,
[223000, 125000, 456000, 322000, 340000, 198000, 310000, 250000, 460000, 261000, 389000, 305000],
[1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4],
1,
['North', 'North', 'South', 'North', 'North', 'South', 'North', 'North', 'South', 'North', 'North', 'South'],
'North',
],
[
571000,
[223000, 125000, 456000, 322000, 340000, 198000, 310000, 250000, 460000, 261000, 389000, 305000],
[1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4],
'>2',
['Jeff', 'Chris', 'Carol', 'Jeff', 'Chris', 'Carol', 'Jeff', 'Chris', 'Carol', 'Jeff', 'Chris', 'Carol'],
'Jeff',
],
]; ];

View File

@ -1,6 +1,9 @@
<?php <?php
return [ return [
[
0,
],
[ [
80.5, 80.5,
[75, 94, 86, 'incomplete'], [75, 94, 86, 'incomplete'],

View File

@ -1,6 +1,9 @@
<?php <?php
return [ return [
[
0,
],
[ [
2, 2,
['Y', 'Y', 'N'], ['Y', 'Y', 'N'],

View File

@ -1,6 +1,9 @@
<?php <?php
return [ return [
[
0,
],
[ [
2, 2,
[ [

View File

@ -1,6 +1,9 @@
<?php <?php
return [ return [
[
0,
],
[ [
1, 1,
[ [