From 5d65fb2e2997d4529c32b3a44ea7147a9b18c68c Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Sat, 27 Feb 2021 21:25:47 +0100 Subject: [PATCH 1/4] Avoid the performance/memory overheads of "clone on modify" of $args when building the condition set/database for AVERAGEIFS(), MAXIFS() and MINIFS() --- .../Calculation/Statistical/Conditional.php | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Conditional.php b/src/PhpSpreadsheet/Calculation/Statistical/Conditional.php index 02bb0782f3..5a867d596c 100644 --- a/src/PhpSpreadsheet/Calculation/Statistical/Conditional.php +++ b/src/PhpSpreadsheet/Calculation/Statistical/Conditional.php @@ -189,13 +189,11 @@ public static function MINIFS(...$args) private static function buildConditionSet(...$args): array { - array_shift($args); - $conditions = []; $pairCount = 1; - while (count($args) > 0) { - $conditions[] = array_merge([sprintf(self::CONDITIONAL_COLUMN_NAME, $pairCount)], [array_pop($args)]); - array_pop($args); + $argumentCount = count($args); + for ($argument = 2; $argument < $argumentCount; $argument += 2) { + $conditions[] = array_merge([sprintf(self::CONDITIONAL_COLUMN_NAME, $pairCount)], [$args[$argument]]); ++$pairCount; } @@ -216,15 +214,15 @@ private static function buildDatabase(...$args): array $database = []; $database[] = array_merge( [self::VALUE_COLUMN_NAME], - Functions::flattenArray(array_shift($args)) + Functions::flattenArray($args[0]) ); $pairCount = 1; - while (count($args) > 0) { - array_pop($args); + $argumentCount = count($args); + for ($argument = 1; $argument < $argumentCount; $argument += 2) { $database[] = array_merge( [sprintf(self::CONDITIONAL_COLUMN_NAME, $pairCount)], - Functions::flattenArray(array_pop($args)) + Functions::flattenArray($args[$argument]) ); ++$pairCount; } From 8e45e5af5aa641df570043b10e96258ed5f217c7 Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Sat, 27 Feb 2021 21:38:26 +0100 Subject: [PATCH 2/4] Avoid the performance/memory overheads of "clone on modify" of $args when building the condition set/database for COUNTIFS() --- .../Calculation/Statistical/Conditional.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Conditional.php b/src/PhpSpreadsheet/Calculation/Statistical/Conditional.php index 5a867d596c..0bef359372 100644 --- a/src/PhpSpreadsheet/Calculation/Statistical/Conditional.php +++ b/src/PhpSpreadsheet/Calculation/Statistical/Conditional.php @@ -123,16 +123,22 @@ public static function COUNTIFS(...$args) } $conditions = $database = []; + $argumentCount = count($args); $pairCount = 1; - while (count($args) > 0) { - $conditions[] = array_merge([sprintf(self::CONDITIONAL_COLUMN_NAME, $pairCount)], [array_pop($args)]); + for ($argument = 0; $argument < $argumentCount; $argument += 2) { $database[] = array_merge( [sprintf(self::CONDITIONAL_COLUMN_NAME, $pairCount)], - Functions::flattenArray(array_pop($args)) + Functions::flattenArray($args[$argument]) ); ++$pairCount; } + $pairCount = 1; + for ($argument = 1; $argument < $argumentCount; $argument += 2) { + $conditions[] = array_merge([sprintf(self::CONDITIONAL_COLUMN_NAME, $pairCount)], [$args[$argument]]); + ++$pairCount; + } + $conditions = array_map(null, ...$conditions); $database = array_map(null, ...$database); From f21f944b9d25f6a54492c3300cb972c539ec5a7d Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Sat, 27 Feb 2021 22:17:08 +0100 Subject: [PATCH 3/4] Minor refactoring --- .../Calculation/Statistical/Conditional.php | 65 +++++++++++-------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Conditional.php b/src/PhpSpreadsheet/Calculation/Statistical/Conditional.php index 0bef359372..670c5fa3ca 100644 --- a/src/PhpSpreadsheet/Calculation/Statistical/Conditional.php +++ b/src/PhpSpreadsheet/Calculation/Statistical/Conditional.php @@ -67,8 +67,8 @@ public static function AVERAGEIFS(...$args) return self::AVERAGEIF($args[2], $args[1], $args[0]); } - $conditions = self::buildConditionSet(...$args); - $database = self::buildDatabase(...$args); + $conditions = self::buildConditionsForRange(...$args); + $database = self::buildDatabaseWithRange(...$args); return DAverage::evaluate($database, self::VALUE_COLUMN_NAME, $conditions); } @@ -122,25 +122,8 @@ public static function COUNTIFS(...$args) return self::COUNTIF(...$args); } - $conditions = $database = []; - $argumentCount = count($args); - $pairCount = 1; - for ($argument = 0; $argument < $argumentCount; $argument += 2) { - $database[] = array_merge( - [sprintf(self::CONDITIONAL_COLUMN_NAME, $pairCount)], - Functions::flattenArray($args[$argument]) - ); - ++$pairCount; - } - - $pairCount = 1; - for ($argument = 1; $argument < $argumentCount; $argument += 2) { - $conditions[] = array_merge([sprintf(self::CONDITIONAL_COLUMN_NAME, $pairCount)], [$args[$argument]]); - ++$pairCount; - } - - $conditions = array_map(null, ...$conditions); - $database = array_map(null, ...$database); + $database = self::buildDatabase(...$args); + $conditions = self::buildConditions(...$args); return DCount::evaluate($database, null, $conditions); } @@ -163,8 +146,8 @@ public static function MAXIFS(...$args) return 0.0; } - $conditions = self::buildConditionSet(...$args); - $database = self::buildDatabase(...$args); + $conditions = self::buildConditionsForRange(...$args); + $database = self::buildDatabaseWithRange(...$args); return DMax::evaluate($database, self::VALUE_COLUMN_NAME, $conditions); } @@ -187,15 +170,28 @@ public static function MINIFS(...$args) return 0.0; } - $conditions = self::buildConditionSet(...$args); - $database = self::buildDatabase(...$args); + $conditions = self::buildConditionsForRange(...$args); + $database = self::buildDatabaseWithRange(...$args); return DMin::evaluate($database, self::VALUE_COLUMN_NAME, $conditions); } - private static function buildConditionSet(...$args): array + private static function buildConditions(...$args): array + { + $pairCount = 1; + $argumentCount = count($args); + for ($argument = 1; $argument < $argumentCount; $argument += 2) { + $conditions[] = array_merge([sprintf(self::CONDITIONAL_COLUMN_NAME, $pairCount)], [$args[$argument]]); + ++$pairCount; + } + + return array_map(null, ...$conditions); + } + + private static function buildConditionsForRange(...$args): array { $conditions = []; + $pairCount = 1; $argumentCount = count($args); for ($argument = 2; $argument < $argumentCount; $argument += 2) { @@ -216,6 +212,23 @@ function ($value) { } private static function buildDatabase(...$args): array + { + $database = []; + + $pairCount = 1; + $argumentCount = count($args); + for ($argument = 0; $argument < $argumentCount; $argument += 2) { + $database[] = array_merge( + [sprintf(self::CONDITIONAL_COLUMN_NAME, $pairCount)], + Functions::flattenArray($args[$argument]) + ); + ++$pairCount; + } + + return array_map(null, ...$database); + } + + private static function buildDatabaseWithRange(...$args): array { $database = []; $database[] = array_merge( From 1b3d5d3a8130f5d3f70ec55423c53e3fdf791dd8 Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Sat, 27 Feb 2021 22:53:06 +0100 Subject: [PATCH 4/4] More refactoring --- .../Calculation/Statistical/Conditional.php | 55 +++++++++---------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Conditional.php b/src/PhpSpreadsheet/Calculation/Statistical/Conditional.php index 670c5fa3ca..ae33401188 100644 --- a/src/PhpSpreadsheet/Calculation/Statistical/Conditional.php +++ b/src/PhpSpreadsheet/Calculation/Statistical/Conditional.php @@ -67,7 +67,7 @@ public static function AVERAGEIFS(...$args) return self::AVERAGEIF($args[2], $args[1], $args[0]); } - $conditions = self::buildConditionsForRange(...$args); + $conditions = self::buildConditionSetForRange(...$args); $database = self::buildDatabaseWithRange(...$args); return DAverage::evaluate($database, self::VALUE_COLUMN_NAME, $conditions); @@ -123,7 +123,7 @@ public static function COUNTIFS(...$args) } $database = self::buildDatabase(...$args); - $conditions = self::buildConditions(...$args); + $conditions = self::buildConditionSet(...$args); return DCount::evaluate($database, null, $conditions); } @@ -146,7 +146,7 @@ public static function MAXIFS(...$args) return 0.0; } - $conditions = self::buildConditionsForRange(...$args); + $conditions = self::buildConditionSetForRange(...$args); $database = self::buildDatabaseWithRange(...$args); return DMax::evaluate($database, self::VALUE_COLUMN_NAME, $conditions); @@ -170,34 +170,22 @@ public static function MINIFS(...$args) return 0.0; } - $conditions = self::buildConditionsForRange(...$args); + $conditions = self::buildConditionSetForRange(...$args); $database = self::buildDatabaseWithRange(...$args); return DMin::evaluate($database, self::VALUE_COLUMN_NAME, $conditions); } - private static function buildConditions(...$args): array + private static function buildConditionSet(...$args): array { - $pairCount = 1; - $argumentCount = count($args); - for ($argument = 1; $argument < $argumentCount; $argument += 2) { - $conditions[] = array_merge([sprintf(self::CONDITIONAL_COLUMN_NAME, $pairCount)], [$args[$argument]]); - ++$pairCount; - } + $conditions = self::buildConditions(1, ...$args); return array_map(null, ...$conditions); } - private static function buildConditionsForRange(...$args): array + private static function buildConditionSetForRange(...$args): array { - $conditions = []; - - $pairCount = 1; - $argumentCount = count($args); - for ($argument = 2; $argument < $argumentCount; $argument += 2) { - $conditions[] = array_merge([sprintf(self::CONDITIONAL_COLUMN_NAME, $pairCount)], [$args[$argument]]); - ++$pairCount; - } + $conditions = self::buildConditions(2, ...$args); if (count($conditions) === 1) { return array_map( @@ -211,21 +199,25 @@ function ($value) { return array_map(null, ...$conditions); } - private static function buildDatabase(...$args): array + private static function buildConditions(int $startOffset, ...$args): array { - $database = []; + $conditions = []; $pairCount = 1; $argumentCount = count($args); - for ($argument = 0; $argument < $argumentCount; $argument += 2) { - $database[] = array_merge( - [sprintf(self::CONDITIONAL_COLUMN_NAME, $pairCount)], - Functions::flattenArray($args[$argument]) - ); + for ($argument = $startOffset; $argument < $argumentCount; $argument += 2) { + $conditions[] = array_merge([sprintf(self::CONDITIONAL_COLUMN_NAME, $pairCount)], [$args[$argument]]); ++$pairCount; } - return array_map(null, ...$database); + return $conditions; + } + + private static function buildDatabase(...$args): array + { + $database = []; + + return self::buildDataSet(0, $database, ...$args); } private static function buildDatabaseWithRange(...$args): array @@ -236,9 +228,14 @@ private static function buildDatabaseWithRange(...$args): array Functions::flattenArray($args[0]) ); + return self::buildDataSet(1, $database, ...$args); + } + + private static function buildDataSet(int $startOffset, array $database, ...$args): array + { $pairCount = 1; $argumentCount = count($args); - for ($argument = 1; $argument < $argumentCount; $argument += 2) { + for ($argument = $startOffset; $argument < $argumentCount; $argument += 2) { $database[] = array_merge( [sprintf(self::CONDITIONAL_COLUMN_NAME, $pairCount)], Functions::flattenArray($args[$argument])