Skip to content

Commit

Permalink
Additional conditionals from math trig (#1885)
Browse files Browse the repository at this point in the history
* Use our new Conditional logic to implement the SUMIF() and SUMIFS() Mathematical functions
  • Loading branch information
Mark Baker committed Feb 28, 2021
1 parent 761c84a commit ee969fd
Show file tree
Hide file tree
Showing 11 changed files with 151 additions and 99 deletions.
2 changes: 1 addition & 1 deletion samples/Basic/40_Duplicate_style.php
Expand Up @@ -30,7 +30,7 @@
}
}
$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__);
4 changes: 2 additions & 2 deletions src/PhpSpreadsheet/Calculation/Calculation.php
Expand Up @@ -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' => [
Expand Down
4 changes: 4 additions & 0 deletions src/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php
Expand Up @@ -162,6 +162,10 @@ private static function processCondition(string $criterion, array $fields, array
$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;
}

Expand Down
93 changes: 18 additions & 75 deletions src/PhpSpreadsheet/Calculation/MathTrig.php
Expand Up @@ -1284,44 +1284,22 @@ public static function SUM(...$args)
* 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);
}

/**
Expand All @@ -1330,55 +1308,20 @@ public static function SUMIF($aArgs, $condition, $sumArgs = [])
* 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
*
* @return float
*/
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);
}

/**
Expand Down
98 changes: 77 additions & 21 deletions src/PhpSpreadsheet/Calculation/Statistical/Conditional.php
Expand Up @@ -6,6 +6,7 @@
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
Expand All @@ -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);
Expand All @@ -64,11 +54,11 @@ public static function AVERAGEIFS(...$args)
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);
}
Expand Down Expand Up @@ -146,8 +136,8 @@ public static function MAXIFS(...$args)
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);
}
Expand All @@ -170,20 +160,68 @@ public static function MINIFS(...$args)
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);

return array_map(null, ...$conditions);
}

private static function buildConditionSetForRange(...$args): array
private static function buildConditionSetForValueRange(...$args): array
{
$conditions = self::buildConditions(2, ...$args);

Expand Down Expand Up @@ -220,7 +258,7 @@ private static function buildDatabase(...$args): array
return self::buildDataSet(0, $database, ...$args);
}

private static function buildDatabaseWithRange(...$args): array
private static function buildDatabaseWithValueRange(...$args): array
{
$database = [];
$database[] = array_merge(
Expand All @@ -245,4 +283,22 @@ private static function buildDataSet(int $startOffset, array $database, ...$args

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;
}
}
18 changes: 18 additions & 0 deletions tests/data/Calculation/MathTrig/SUMIF.php
Expand Up @@ -120,4 +120,22 @@
[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],
],
];
19 changes: 19 additions & 0 deletions tests/data/Calculation/MathTrig/SUMIFS.php
@@ -1,6 +1,9 @@
<?php

return [
[
0,
],
[
2,
[
Expand Down Expand Up @@ -41,4 +44,20 @@
],
'=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',
],
];
3 changes: 3 additions & 0 deletions tests/data/Calculation/Statistical/AVERAGEIFS.php
@@ -1,6 +1,9 @@
<?php

return [
[
0,
],
[
80.5,
[75, 94, 86, 'incomplete'],
Expand Down
3 changes: 3 additions & 0 deletions tests/data/Calculation/Statistical/COUNTIFS.php
@@ -1,6 +1,9 @@
<?php

return [
[
0,
],
[
2,
['Y', 'Y', 'N'],
Expand Down
3 changes: 3 additions & 0 deletions tests/data/Calculation/Statistical/MAXIFS.php
@@ -1,6 +1,9 @@
<?php

return [
[
0,
],
[
2,
[
Expand Down

0 comments on commit ee969fd

Please sign in to comment.