Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1441 ariadna 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
    public static function AVERAGEIF(mixed $range, null|array|string $condition, mixed $averageRange = []): null|int|float|string
33
    {
34
        if (!is_array($range) || !is_array($averageRange) || array_key_exists(0, $range) || array_key_exists(0, $averageRange)) {
35
            $refError = ExcelError::REF();
36
            if (in_array($refError, [$range, $averageRange], true)) {
37
                return $refError;
38
            }
39
 
40
            throw new CalcException('Must specify range of cells, not any kind of literal');
41
        }
42
        $database = self::databaseFromRangeAndValue($range, $averageRange);
43
        $condition = [[self::CONDITION_COLUMN_NAME, self::VALUE_COLUMN_NAME], [$condition, null]];
44
 
45
        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
    public static function AVERAGEIFS(mixed ...$args): null|int|float|string
59
    {
60
        if (empty($args)) {
61
            return 0.0;
62
        } elseif (count($args) === 3) {
63
            return self::AVERAGEIF($args[1], $args[2], $args[0]);
64
        }
65
        foreach ($args as $arg) {
66
            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
        $conditions = self::buildConditionSetForValueRange(...$args);
72
        $database = self::buildDatabaseWithValueRange(...$args);
73
 
74
        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
    public static function COUNTIF(mixed $range, null|array|string $condition): string|int
89
    {
90
        if (
91
            !is_array($range)
92
            || array_key_exists(0, $range)
93
        ) {
94
            if ($range === ExcelError::REF()) {
95
                return $range;
96
            }
97
 
98
            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
        $range = array_filter(
102
            Functions::flattenArray($range),
103
            fn ($value): bool => $value !== null && $value !== ''
104
        );
105
 
106
        $range = array_merge([[self::CONDITION_COLUMN_NAME]], array_chunk($range, 1));
107
        $condition = array_merge([[self::CONDITION_COLUMN_NAME]], [[$condition]]);
108
 
109
        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
    public static function COUNTIFS(mixed ...$args): int|string
123
    {
124
        if (empty($args)) {
125
            return 0;
126
        } elseif (count($args) === 2) {
127
            return self::COUNTIF(...$args);
128
        }
129
 
130
        $database = self::buildDatabase(...$args);
131
        $conditions = self::buildConditionSet(...$args);
132
 
133
        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
    public static function MAXIFS(mixed ...$args): null|float|string
147
    {
148
        if (empty($args)) {
149
            return 0.0;
150
        }
151
 
152
        $conditions = self::buildConditionSetForValueRange(...$args);
153
        $database = self::buildDatabaseWithValueRange(...$args);
154
 
155
        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
    public static function MINIFS(mixed ...$args): null|float|string
169
    {
170
        if (empty($args)) {
171
            return 0.0;
172
        }
173
 
174
        $conditions = self::buildConditionSetForValueRange(...$args);
175
        $database = self::buildDatabaseWithValueRange(...$args);
176
 
177
        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
    public static function SUMIF(mixed $range, mixed $condition, mixed $sumRange = []): null|float|string
192
    {
193
        if (
194
            !is_array($range)
195
            || array_key_exists(0, $range)
196
            || !is_array($sumRange)
197
            || array_key_exists(0, $sumRange)
198
        ) {
199
            $refError = ExcelError::REF();
200
            if (in_array($refError, [$range, $sumRange], true)) {
201
                return $refError;
202
            }
203
 
204
            throw new CalcException('Must specify range of cells, not any kind of literal');
205
        }
206
        $database = self::databaseFromRangeAndValue($range, $sumRange);
207
        $condition = [[self::CONDITION_COLUMN_NAME, self::VALUE_COLUMN_NAME], [$condition, null]];
208
 
209
        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
    public static function SUMIFS(mixed ...$args): null|float|string
223
    {
224
        if (empty($args)) {
225
            return 0.0;
226
        } elseif (count($args) === 3) {
227
            return self::SUMIF($args[1], $args[2], $args[0]);
228
        }
229
 
230
        $conditions = self::buildConditionSetForValueRange(...$args);
231
        $database = self::buildDatabaseWithValueRange(...$args);
232
 
233
        return DSum::evaluate($database, self::VALUE_COLUMN_NAME, $conditions);
234
    }
235
 
236
    /** @param array $args */
237
    private static function buildConditionSet(...$args): array
238
    {
239
        $conditions = self::buildConditions(1, ...$args);
240
 
241
        return array_map(null, ...$conditions);
242
    }
243
 
244
    /** @param array $args */
245
    private static function buildConditionSetForValueRange(...$args): array
246
    {
247
        $conditions = self::buildConditions(2, ...$args);
248
 
249
        if (count($conditions) === 1) {
250
            return array_map(
251
                fn ($value): array => [$value],
252
                $conditions[0]
253
            );
254
        }
255
 
256
        return array_map(null, ...$conditions);
257
    }
258
 
259
    /** @param array $args */
260
    private static function buildConditions(int $startOffset, ...$args): array
261
    {
262
        $conditions = [];
263
 
264
        $pairCount = 1;
265
        $argumentCount = count($args);
266
        for ($argument = $startOffset; $argument < $argumentCount; $argument += 2) {
267
            $conditions[] = array_merge([sprintf(self::CONDITIONAL_COLUMN_NAME, $pairCount)], [$args[$argument]]);
268
            ++$pairCount;
269
        }
270
 
271
        return $conditions;
272
    }
273
 
274
    /** @param array $args */
275
    private static function buildDatabase(...$args): array
276
    {
277
        $database = [];
278
 
279
        return self::buildDataSet(0, $database, ...$args);
280
    }
281
 
282
    /** @param array $args */
283
    private static function buildDatabaseWithValueRange(...$args): array
284
    {
285
        $database = [];
286
        $database[] = array_merge(
287
            [self::VALUE_COLUMN_NAME],
288
            Functions::flattenArray($args[0])
289
        );
290
 
291
        return self::buildDataSet(1, $database, ...$args);
292
    }
293
 
294
    /** @param array $args */
295
    private static function buildDataSet(int $startOffset, array $database, ...$args): array
296
    {
297
        $pairCount = 1;
298
        $argumentCount = count($args);
299
        for ($argument = $startOffset; $argument < $argumentCount; $argument += 2) {
300
            $database[] = array_merge(
301
                [sprintf(self::CONDITIONAL_COLUMN_NAME, $pairCount)],
302
                Functions::flattenArray($args[$argument])
303
            );
304
            ++$pairCount;
305
        }
306
 
307
        return array_map(null, ...$database);
308
    }
309
 
310
    private static function databaseFromRangeAndValue(array $range, array $valueRange = []): array
311
    {
312
        $range = Functions::flattenArray($range);
313
 
314
        $valueRange = Functions::flattenArray($valueRange);
315
        if (empty($valueRange)) {
316
            $valueRange = $range;
317
        }
318
 
319
        $database = array_map(null, array_merge([self::CONDITION_COLUMN_NAME], $range), array_merge([self::VALUE_COLUMN_NAME], $valueRange));
320
 
321
        return $database;
322
    }
323
}