Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Additional conditionals from math trig #1885

Merged
merged 10 commits into from Feb 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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