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;
$helper->log('Add data (end) . time: ' . round((string) ($d . 2)) . ' s');
$helper->log('Add data (end) . time: ' . (string) round($d, 2) . ' s');
// Save
$helper->write($spreadsheet, __FILE__);

View File

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

View File

@ -162,6 +162,10 @@ abstract class DatabaseAbstract
$dataValue = ($dataValues[$key]) ? 'TRUE' : 'FALSE';
} elseif ($dataValues[$key] !== null) {
$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;
}

View File

@ -1284,44 +1284,22 @@ class MathTrig
* Totals the values of cells that contain numbers within the list of arguments
*
* Excel Function:
* SUMIF(value1[,value2[, ...]],condition)
* SUMIF(range, criteria, [sum_range])
*
* @param mixed $aArgs Data values
* @param string $condition the criteria that defines which cells will be summed
* @param mixed $sumArgs
* @Deprecated 1.17.0
*
* @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
*/
public static function SUMIF($aArgs, $condition, $sumArgs = [])
public static function SUMIF($range, $criteria, $sumRange = [])
{
$returnValue = 0;
$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;
return Statistical\Conditional::SUMIF($range, $criteria, $sumRange);
}
/**
@ -1330,7 +1308,12 @@ class MathTrig
* Totals the values of cells that contain numbers within the list of arguments
*
* 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
*
@ -1338,47 +1321,7 @@ class MathTrig
*/
public static function SUMIFS(...$args)
{
$arrayList = $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;
return Statistical\Conditional::SUMIFS(...$args);
}
/**

View File

@ -6,6 +6,7 @@ use PhpOffice\PhpSpreadsheet\Calculation\Database\DAverage;
use PhpOffice\PhpSpreadsheet\Calculation\Database\DCount;
use PhpOffice\PhpSpreadsheet\Calculation\Database\DMax;
use PhpOffice\PhpSpreadsheet\Calculation\Database\DMin;
use PhpOffice\PhpSpreadsheet\Calculation\Database\DSum;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class Conditional
@ -30,18 +31,7 @@ class Conditional
*/
public static function AVERAGEIF($range, $condition, $averageRange = [])
{
$range = Functions::flattenArray($range);
$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)
);
$database = self::databaseFromRangeAndValue($range, $averageRange);
$condition = [[self::CONDITION_COLUMN_NAME, self::VALUE_COLUMN_NAME], [$condition, null]];
return DAverage::evaluate($database, self::VALUE_COLUMN_NAME, $condition);
@ -64,11 +54,11 @@ class Conditional
if (empty($args)) {
return 0.0;
} 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);
$database = self::buildDatabaseWithRange(...$args);
$conditions = self::buildConditionSetForValueRange(...$args);
$database = self::buildDatabaseWithValueRange(...$args);
return DAverage::evaluate($database, self::VALUE_COLUMN_NAME, $conditions);
}
@ -146,8 +136,8 @@ class Conditional
return 0.0;
}
$conditions = self::buildConditionSetForRange(...$args);
$database = self::buildDatabaseWithRange(...$args);
$conditions = self::buildConditionSetForValueRange(...$args);
$database = self::buildDatabaseWithValueRange(...$args);
return DMax::evaluate($database, self::VALUE_COLUMN_NAME, $conditions);
}
@ -170,12 +160,60 @@ class Conditional
return 0.0;
}
$conditions = self::buildConditionSetForRange(...$args);
$database = self::buildDatabaseWithRange(...$args);
$conditions = self::buildConditionSetForValueRange(...$args);
$database = self::buildDatabaseWithValueRange(...$args);
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
{
$conditions = self::buildConditions(1, ...$args);
@ -183,7 +221,7 @@ class Conditional
return array_map(null, ...$conditions);
}
private static function buildConditionSetForRange(...$args): array
private static function buildConditionSetForValueRange(...$args): array
{
$conditions = self::buildConditions(2, ...$args);
@ -220,7 +258,7 @@ class Conditional
return self::buildDataSet(0, $database, ...$args);
}
private static function buildDatabaseWithRange(...$args): array
private static function buildDatabaseWithValueRange(...$args): array
{
$database = [];
$database[] = array_merge(
@ -245,4 +283,22 @@ class Conditional
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],
],
],
[
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
return [
[
0,
],
[
2,
[
@ -41,4 +44,20 @@ return [
],
'=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
return [
[
0,
],
[
80.5,
[75, 94, 86, 'incomplete'],

View File

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

View File

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

View File

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