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\LookupRef;
4
 
5
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
6
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
7
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
8
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
9
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
10
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
11
 
12
class Sort extends LookupRefValidations
13
{
14
    public const ORDER_ASCENDING = 1;
15
    public const ORDER_DESCENDING = -1;
16
 
17
    /**
18
     * SORT
19
     * The SORT function returns a sorted array of the elements in an array.
20
     * The returned array is the same shape as the provided array argument.
21
     * Both $sortIndex and $sortOrder can be arrays, to provide multi-level sorting.
22
     *
23
     * @param mixed $sortArray The range of cells being sorted
24
     * @param mixed $sortIndex The column or row number within the sortArray to sort on
25
     * @param mixed $sortOrder Flag indicating whether to sort ascending or descending
26
     *                          Ascending = 1 (self::ORDER_ASCENDING)
27
     *                          Descending = -1 (self::ORDER_DESCENDING)
28
     * @param mixed $byColumn Whether the sort should be determined by row (the default) or by column
29
     *
30
     * @return mixed The sorted values from the sort range
31
     */
32
    public static function sort(mixed $sortArray, mixed $sortIndex = 1, mixed $sortOrder = self::ORDER_ASCENDING, mixed $byColumn = false): mixed
33
    {
34
        if (!is_array($sortArray)) {
35
            // Scalars are always returned "as is"
36
            return $sortArray;
37
        }
38
 
39
        $sortArray = self::enumerateArrayKeys($sortArray);
40
 
41
        $byColumn = (bool) $byColumn;
42
        $lookupIndexSize = $byColumn ? count($sortArray) : count($sortArray[0]);
43
 
44
        try {
45
            // If $sortIndex and $sortOrder are scalars, then convert them into arrays
46
            if (is_scalar($sortIndex)) {
47
                $sortIndex = [$sortIndex];
48
                $sortOrder = is_scalar($sortOrder) ? [$sortOrder] : $sortOrder;
49
            }
50
            // but the values of those array arguments still need validation
51
            $sortOrder = (empty($sortOrder) ? [self::ORDER_ASCENDING] : $sortOrder);
52
            self::validateArrayArgumentsForSort($sortIndex, $sortOrder, $lookupIndexSize);
53
        } catch (Exception $e) {
54
            return $e->getMessage();
55
        }
56
 
57
        // We want a simple, enumrated array of arrays where we can reference column by its index number.
58
        $sortArray = array_values(array_map('array_values', $sortArray));
59
 
60
        return ($byColumn === true)
61
            ? self::sortByColumn($sortArray, $sortIndex, $sortOrder)
62
            : self::sortByRow($sortArray, $sortIndex, $sortOrder);
63
    }
64
 
65
    /**
66
     * SORTBY
67
     * The SORTBY function sorts the contents of a range or array based on the values in a corresponding range or array.
68
     * The returned array is the same shape as the provided array argument.
69
     * Both $sortIndex and $sortOrder can be arrays, to provide multi-level sorting.
70
     *
71
     * @param mixed $sortArray The range of cells being sorted
72
     * @param mixed $args
73
     *              At least one additional argument must be provided, The vector or range to sort on
74
     *              After that, arguments are passed as pairs:
75
     *                    sort order: ascending or descending
76
     *                         Ascending = 1 (self::ORDER_ASCENDING)
77
     *                         Descending = -1 (self::ORDER_DESCENDING)
78
     *                    additional arrays or ranges for multi-level sorting
79
     *
80
     * @return mixed The sorted values from the sort range
81
     */
82
    public static function sortBy(mixed $sortArray, mixed ...$args): mixed
83
    {
84
        if (!is_array($sortArray)) {
85
            // Scalars are always returned "as is"
86
            return $sortArray;
87
        }
88
 
89
        $sortArray = self::enumerateArrayKeys($sortArray);
90
 
91
        $lookupArraySize = count($sortArray);
92
        $argumentCount = count($args);
93
 
94
        try {
95
            $sortBy = $sortOrder = [];
96
            for ($i = 0; $i < $argumentCount; $i += 2) {
97
                $sortBy[] = self::validateSortVector($args[$i], $lookupArraySize);
98
                $sortOrder[] = self::validateSortOrder($args[$i + 1] ?? self::ORDER_ASCENDING);
99
            }
100
        } catch (Exception $e) {
101
            return $e->getMessage();
102
        }
103
 
104
        return self::processSortBy($sortArray, $sortBy, $sortOrder);
105
    }
106
 
107
    private static function enumerateArrayKeys(array $sortArray): array
108
    {
109
        array_walk(
110
            $sortArray,
111
            function (&$columns): void {
112
                if (is_array($columns)) {
113
                    $columns = array_values($columns);
114
                }
115
            }
116
        );
117
 
118
        return array_values($sortArray);
119
    }
120
 
121
    private static function validateScalarArgumentsForSort(mixed &$sortIndex, mixed &$sortOrder, int $sortArraySize): void
122
    {
123
        if (is_array($sortIndex) || is_array($sortOrder)) {
124
            throw new Exception(ExcelError::VALUE());
125
        }
126
 
127
        $sortIndex = self::validatePositiveInt($sortIndex, false);
128
 
129
        if ($sortIndex > $sortArraySize) {
130
            throw new Exception(ExcelError::VALUE());
131
        }
132
 
133
        $sortOrder = self::validateSortOrder($sortOrder);
134
    }
135
 
136
    private static function validateSortVector(mixed $sortVector, int $sortArraySize): array
137
    {
138
        if (!is_array($sortVector)) {
139
            throw new Exception(ExcelError::VALUE());
140
        }
141
 
142
        // It doesn't matter if it's a row or a column vectors, it works either way
143
        $sortVector = Functions::flattenArray($sortVector);
144
        if (count($sortVector) !== $sortArraySize) {
145
            throw new Exception(ExcelError::VALUE());
146
        }
147
 
148
        return $sortVector;
149
    }
150
 
151
    private static function validateSortOrder(mixed $sortOrder): int
152
    {
153
        $sortOrder = self::validateInt($sortOrder);
154
        if (($sortOrder == self::ORDER_ASCENDING || $sortOrder === self::ORDER_DESCENDING) === false) {
155
            throw new Exception(ExcelError::VALUE());
156
        }
157
 
158
        return $sortOrder;
159
    }
160
 
161
    private static function validateArrayArgumentsForSort(array &$sortIndex, mixed &$sortOrder, int $sortArraySize): void
162
    {
163
        // It doesn't matter if they're row or column vectors, it works either way
164
        $sortIndex = Functions::flattenArray($sortIndex);
165
        $sortOrder = Functions::flattenArray($sortOrder);
166
 
167
        if (
168
            count($sortOrder) === 0 || count($sortOrder) > $sortArraySize
169
            || (count($sortOrder) > count($sortIndex))
170
        ) {
171
            throw new Exception(ExcelError::VALUE());
172
        }
173
 
174
        if (count($sortIndex) > count($sortOrder)) {
175
            // If $sortOrder has fewer elements than $sortIndex, then the last order element is repeated.
176
            $sortOrder = array_merge(
177
                $sortOrder,
178
                array_fill(0, count($sortIndex) - count($sortOrder), array_pop($sortOrder))
179
            );
180
        }
181
 
182
        foreach ($sortIndex as $key => &$value) {
183
            self::validateScalarArgumentsForSort($value, $sortOrder[$key], $sortArraySize);
184
        }
185
    }
186
 
187
    private static function prepareSortVectorValues(array $sortVector): array
188
    {
189
        // Strings should be sorted case-insensitive; with booleans converted to locale-strings
190
        return array_map(
191
            function ($value) {
192
                if (is_bool($value)) {
193
                    return ($value) ? Calculation::getTRUE() : Calculation::getFALSE();
194
                } elseif (is_string($value)) {
195
                    return StringHelper::strToLower($value);
196
                }
197
 
198
                return $value;
199
            },
200
            $sortVector
201
        );
202
    }
203
 
204
    /**
205
     * @param array[] $sortIndex
206
     * @param int[] $sortOrder
207
     */
208
    private static function processSortBy(array $sortArray, array $sortIndex, array $sortOrder): array
209
    {
210
        $sortArguments = [];
211
        $sortData = [];
212
        foreach ($sortIndex as $index => $sortValues) {
213
            $sortData[] = $sortValues;
214
            $sortArguments[] = self::prepareSortVectorValues($sortValues);
215
            $sortArguments[] = $sortOrder[$index] === self::ORDER_ASCENDING ? SORT_ASC : SORT_DESC;
216
        }
217
 
218
        $sortVector = self::executeVectorSortQuery($sortData, $sortArguments);
219
 
220
        return self::sortLookupArrayFromVector($sortArray, $sortVector);
221
    }
222
 
223
    /**
224
     * @param int[] $sortIndex
225
     * @param int[] $sortOrder
226
     */
227
    private static function sortByRow(array $sortArray, array $sortIndex, array $sortOrder): array
228
    {
229
        $sortVector = self::buildVectorForSort($sortArray, $sortIndex, $sortOrder);
230
 
231
        return self::sortLookupArrayFromVector($sortArray, $sortVector);
232
    }
233
 
234
    /**
235
     * @param int[] $sortIndex
236
     * @param int[] $sortOrder
237
     */
238
    private static function sortByColumn(array $sortArray, array $sortIndex, array $sortOrder): array
239
    {
240
        $sortArray = Matrix::transpose($sortArray);
241
        $result = self::sortByRow($sortArray, $sortIndex, $sortOrder);
242
 
243
        return Matrix::transpose($result);
244
    }
245
 
246
    /**
247
     * @param int[] $sortIndex
248
     * @param int[] $sortOrder
249
     */
250
    private static function buildVectorForSort(array $sortArray, array $sortIndex, array $sortOrder): array
251
    {
252
        $sortArguments = [];
253
        $sortData = [];
254
        foreach ($sortIndex as $index => $sortIndexValue) {
255
            $sortValues = array_column($sortArray, $sortIndexValue - 1);
256
            $sortData[] = $sortValues;
257
            $sortArguments[] = self::prepareSortVectorValues($sortValues);
258
            $sortArguments[] = $sortOrder[$index] === self::ORDER_ASCENDING ? SORT_ASC : SORT_DESC;
259
        }
260
 
261
        $sortData = self::executeVectorSortQuery($sortData, $sortArguments);
262
 
263
        return $sortData;
264
    }
265
 
266
    private static function executeVectorSortQuery(array $sortData, array $sortArguments): array
267
    {
268
        $sortData = Matrix::transpose($sortData);
269
 
270
        // We need to set an index that can be retained, as array_multisort doesn't maintain numeric keys.
271
        $sortDataIndexed = [];
272
        foreach ($sortData as $key => $value) {
273
            $sortDataIndexed[Coordinate::stringFromColumnIndex($key + 1)] = $value;
274
        }
275
        unset($sortData);
276
 
277
        $sortArguments[] = &$sortDataIndexed;
278
 
279
        array_multisort(...$sortArguments);
280
 
281
        // After the sort, we restore the numeric keys that will now be in the correct, sorted order
282
        $sortedData = [];
283
        foreach (array_keys($sortDataIndexed) as $key) {
284
            $sortedData[] = Coordinate::columnIndexFromString($key) - 1;
285
        }
286
 
287
        return $sortedData;
288
    }
289
 
290
    private static function sortLookupArrayFromVector(array $sortArray, array $sortVector): array
291
    {
292
        // Building a new array in the correct (sorted) order works; but may be memory heavy for larger arrays
293
        $sortedArray = [];
294
        foreach ($sortVector as $index) {
295
            $sortedArray[] = $sortArray[$index];
296
        }
297
 
298
        return $sortedArray;
299
 
300
//        uksort(
301
//            $lookupArray,
302
//            function (int $a, int $b) use (array $sortVector) {
303
//                return $sortVector[$a] <=> $sortVector[$b];
304
//            }
305
//        );
306
//
307
//        return $lookupArray;
308
    }
309
}