Passed
Push — master ( 027a5c...479120 )
by
unknown
18:38 queued 08:13
created

Conditional::AVERAGEIF()   A

Complexity

Conditions 6
Paths 3

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 8
dl 0
loc 14
ccs 9
cts 9
cp 1
rs 9.2222
c 0
b 0
f 0
cc 6
nc 3
nop 3
crap 6
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
4
5
use PhpOffice\PhpSpreadsheet\Calculation\Database\DAverage;
6
use PhpOffice\PhpSpreadsheet\Calculation\Database\DCount;
7
use PhpOffice\PhpSpreadsheet\Calculation\Database\DMax;
8
use PhpOffice\PhpSpreadsheet\Calculation\Database\DMin;
9
use PhpOffice\PhpSpreadsheet\Calculation\Database\DSum;
10
use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcException;
11
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
12
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
13
14
class Conditional
15
{
16
    private const CONDITION_COLUMN_NAME = 'CONDITION';
17
    private const VALUE_COLUMN_NAME = 'VALUE';
18
    private const CONDITIONAL_COLUMN_NAME = 'CONDITIONAL %d';
19
20
    /**
21
     * AVERAGEIF.
22
     *
23
     * Returns the average value from a range of cells that contain numbers within the list of arguments
24
     *
25
     * Excel Function:
26
     *        AVERAGEIF(range,condition[, average_range])
27
     *
28
     * @param mixed $range Data values, expect array
29
     * @param null|array|string $condition the criteria that defines which cells will be checked
30
     * @param mixed $averageRange Data values
31
     */
32 18
    public static function AVERAGEIF(mixed $range, null|array|string $condition, mixed $averageRange = []): null|int|float|string
33
    {
34 18
        if (!is_array($range) || !is_array($averageRange) || array_key_exists(0, $range) || array_key_exists(0, $averageRange)) {
35 2
            $refError = ExcelError::REF();
36 2
            if (in_array($refError, [$range, $averageRange], true)) {
37 1
                return $refError;
38
            }
39
40 2
            throw new CalcException('Must specify range of cells, not any kind of literal');
41
        }
42 18
        $database = self::databaseFromRangeAndValue($range, $averageRange);
43 18
        $condition = [[self::CONDITION_COLUMN_NAME, self::VALUE_COLUMN_NAME], [$condition, null]];
44
45 18
        return DAverage::evaluate($database, self::VALUE_COLUMN_NAME, $condition);
46
    }
47
48
    /**
49
     * AVERAGEIFS.
50
     *
51
     * Counts the number of cells that contain numbers within the list of arguments
52
     *
53
     * Excel Function:
54
     *        AVERAGEIFS(average_range, criteria_range1, criteria1, [criteria_range2, criteria2]…)
55
     *
56
     * @param mixed $args Pairs of Ranges and Criteria
57
     */
58 6
    public static function AVERAGEIFS(mixed ...$args): null|int|float|string
59
    {
60 6
        if (empty($args)) {
61
            return 0.0;
62 6
        } elseif (count($args) === 3) {
63 2
            return self::AVERAGEIF($args[1], $args[2], $args[0]);
64
        }
65 4
        foreach ($args as $arg) {
66 4
            if (is_array($arg) && array_key_exists(0, $arg)) {
67
                throw new CalcException('Must specify range of cells, not any kind of literal');
68
            }
69
        }
70
71 4
        $conditions = self::buildConditionSetForValueRange(...$args);
72 4
        $database = self::buildDatabaseWithValueRange(...$args);
73
74 4
        return DAverage::evaluate($database, self::VALUE_COLUMN_NAME, $conditions);
75
    }
76
77
    /**
78
     * COUNTIF.
79
     *
80
     * Counts the number of cells that contain numbers within the list of arguments
81
     *
82
     * Excel Function:
83
     *        COUNTIF(range,condition)
84
     *
85
     * @param mixed $range Data values, expect array
86
     * @param null|array|string $condition the criteria that defines which cells will be counted
87
     */
88 28
    public static function COUNTIF(mixed $range, null|array|string $condition): string|int
89
    {
90
        if (
91 28
            !is_array($range)
92 28
            || array_key_exists(0, $range)
93
        ) {
94 1
            if ($range === ExcelError::REF()) {
95 1
                return $range;
96
            }
97
98 1
            throw new CalcException('Must specify range of cells, not any kind of literal');
99
        }
100
        // Filter out any empty values that shouldn't be included in a COUNT
101 27
        $range = array_filter(
102 27
            Functions::flattenArray($range),
103 27
            fn ($value): bool => $value !== null && $value !== ''
104 27
        );
105
106 27
        $range = array_merge([[self::CONDITION_COLUMN_NAME]], array_chunk($range, 1));
107 27
        $condition = array_merge([[self::CONDITION_COLUMN_NAME]], [[$condition]]);
108
109 27
        return DCount::evaluate($range, null, $condition, false);
110
    }
111
112
    /**
113
     * COUNTIFS.
114
     *
115
     * Counts the number of cells that contain numbers within the list of arguments
116
     *
117
     * Excel Function:
118
     *        COUNTIFS(criteria_range1, criteria1, [criteria_range2, criteria2]…)
119
     *
120
     * @param mixed $args Pairs of Ranges and Criteria
121
     */
122 6
    public static function COUNTIFS(mixed ...$args): int|string
123
    {
124 6
        if (empty($args)) {
125
            return 0;
126 6
        } elseif (count($args) === 2) {
127 3
            return self::COUNTIF(...$args);
128
        }
129
130 3
        $database = self::buildDatabase(...$args);
131 3
        $conditions = self::buildConditionSet(...$args);
132
133 3
        return DCount::evaluate($database, null, $conditions, false);
134
    }
135
136
    /**
137
     * MAXIFS.
138
     *
139
     * Returns the maximum value within a range of cells that contain numbers within the list of arguments
140
     *
141
     * Excel Function:
142
     *        MAXIFS(max_range, criteria_range1, criteria1, [criteria_range2, criteria2]…)
143
     *
144
     * @param mixed $args Pairs of Ranges and Criteria
145
     */
146 5
    public static function MAXIFS(mixed ...$args): null|float|string
147
    {
148 5
        if (empty($args)) {
149
            return 0.0;
150
        }
151
152 5
        $conditions = self::buildConditionSetForValueRange(...$args);
153 5
        $database = self::buildDatabaseWithValueRange(...$args);
154
155 5
        return DMax::evaluate($database, self::VALUE_COLUMN_NAME, $conditions, false);
156
    }
157
158
    /**
159
     * MINIFS.
160
     *
161
     * Returns the minimum value within a range of cells that contain numbers within the list of arguments
162
     *
163
     * Excel Function:
164
     *        MINIFS(min_range, criteria_range1, criteria1, [criteria_range2, criteria2]…)
165
     *
166
     * @param mixed $args Pairs of Ranges and Criteria
167
     */
168 5
    public static function MINIFS(mixed ...$args): null|float|string
169
    {
170 5
        if (empty($args)) {
171
            return 0.0;
172
        }
173
174 5
        $conditions = self::buildConditionSetForValueRange(...$args);
175 5
        $database = self::buildDatabaseWithValueRange(...$args);
176
177 5
        return DMin::evaluate($database, self::VALUE_COLUMN_NAME, $conditions, false);
178
    }
179
180
    /**
181
     * SUMIF.
182
     *
183
     * Totals the values of cells that contain numbers within the list of arguments
184
     *
185
     * Excel Function:
186
     *        SUMIF(range, criteria, [sum_range])
187
     *
188
     * @param mixed $range Data values, expecting array
189
     * @param mixed $sumRange Data values, expecting array
190
     */
191 23
    public static function SUMIF(mixed $range, mixed $condition, mixed $sumRange = []): null|float|string
192
    {
193
        if (
194 23
            !is_array($range)
195 23
            || array_key_exists(0, $range)
196 23
            || !is_array($sumRange)
197 23
            || array_key_exists(0, $sumRange)
198
        ) {
199 1
            $refError = ExcelError::REF();
200 1
            if (in_array($refError, [$range, $sumRange], true)) {
201 1
                return $refError;
202
            }
203
204 1
            throw new CalcException('Must specify range of cells, not any kind of literal');
205
        }
206 22
        $database = self::databaseFromRangeAndValue($range, $sumRange);
207 22
        $condition = [[self::CONDITION_COLUMN_NAME, self::VALUE_COLUMN_NAME], [$condition, null]];
208
209 22
        return DSum::evaluate($database, self::VALUE_COLUMN_NAME, $condition);
210
    }
211
212
    /**
213
     * SUMIFS.
214
     *
215
     * Counts the number of cells that contain numbers within the list of arguments
216
     *
217
     * Excel Function:
218
     *        SUMIFS(average_range, criteria_range1, criteria1, [criteria_range2, criteria2]…)
219
     *
220
     * @param mixed $args Pairs of Ranges and Criteria
221
     */
222 6
    public static function SUMIFS(mixed ...$args): null|float|string
223
    {
224 6
        if (empty($args)) {
225 1
            return 0.0;
226 5
        } elseif (count($args) === 3) {
227 1
            return self::SUMIF($args[1], $args[2], $args[0]);
228
        }
229
230 4
        $conditions = self::buildConditionSetForValueRange(...$args);
231 4
        $database = self::buildDatabaseWithValueRange(...$args);
232
233 4
        return DSum::evaluate($database, self::VALUE_COLUMN_NAME, $conditions);
234
    }
235
236
    /** @param array $args */
237 3
    private static function buildConditionSet(...$args): array
238
    {
239 3
        $conditions = self::buildConditions(1, ...$args);
240
241 3
        return array_map(null, ...$conditions);
242
    }
243
244
    /** @param array $args */
245 18
    private static function buildConditionSetForValueRange(...$args): array
246
    {
247 18
        $conditions = self::buildConditions(2, ...$args);
248
249 18
        if (count($conditions) === 1) {
250 4
            return array_map(
251 4
                fn ($value): array => [$value],
252 4
                $conditions[0]
253 4
            );
254
        }
255
256 14
        return array_map(null, ...$conditions);
257
    }
258
259
    /** @param array $args */
260 21
    private static function buildConditions(int $startOffset, ...$args): array
261
    {
262 21
        $conditions = [];
263
264 21
        $pairCount = 1;
265 21
        $argumentCount = count($args);
266 21
        for ($argument = $startOffset; $argument < $argumentCount; $argument += 2) {
267 21
            $conditions[] = array_merge([sprintf(self::CONDITIONAL_COLUMN_NAME, $pairCount)], [$args[$argument]]);
268 21
            ++$pairCount;
269
        }
270
271 21
        return $conditions;
272
    }
273
274
    /** @param array $args */
275 3
    private static function buildDatabase(...$args): array
276
    {
277 3
        $database = [];
278
279 3
        return self::buildDataSet(0, $database, ...$args);
280
    }
281
282
    /** @param array $args */
283 18
    private static function buildDatabaseWithValueRange(...$args): array
284
    {
285 18
        $database = [];
286 18
        $database[] = array_merge(
287 18
            [self::VALUE_COLUMN_NAME],
288 18
            Functions::flattenArray($args[0])
289 18
        );
290
291 18
        return self::buildDataSet(1, $database, ...$args);
292
    }
293
294
    /** @param array $args */
295 21
    private static function buildDataSet(int $startOffset, array $database, ...$args): array
296
    {
297 21
        $pairCount = 1;
298 21
        $argumentCount = count($args);
299 21
        for ($argument = $startOffset; $argument < $argumentCount; $argument += 2) {
300 21
            $database[] = array_merge(
301 21
                [sprintf(self::CONDITIONAL_COLUMN_NAME, $pairCount)],
302 21
                Functions::flattenArray($args[$argument])
303 21
            );
304 21
            ++$pairCount;
305
        }
306
307 21
        return array_map(null, ...$database);
308
    }
309
310 40
    private static function databaseFromRangeAndValue(array $range, array $valueRange = []): array
311
    {
312 40
        $range = Functions::flattenArray($range);
313
314 40
        $valueRange = Functions::flattenArray($valueRange);
315 40
        if (empty($valueRange)) {
316 7
            $valueRange = $range;
317
        }
318
319 40
        $database = array_map(null, array_merge([self::CONDITION_COLUMN_NAME], $range), array_merge([self::VALUE_COLUMN_NAME], $valueRange));
320
321 40
        return $database;
322
    }
323
}
324