I have an array that contains other associative arrays as its values. Each one of these "sub" arrays is mapped with key-value pairs:
$items = [
['type' => 1, 'text' => 'A'],
['type' => 2, 'text' => 'B'],
['type' => 2, 'text' => 'C'],
['type' => 3, 'text' => 'D'],
['type' => 2, 'text' => 'E'],
['type' => 1, 'text' => 'F'],
['type' => 4, 'text' => 'G'],
['type' => 2, 'text' => 'H'],
['type' => 1, 'text' => 'I'],
['type' => 4, 'text' => 'J'],
['type' => 2, 'text' => 'K'],
['type' => 4, 'text' => 'L'],
['type' => 2, 'text' => 'M'],
['type' => 3, 'text' => 'N'],
['type' => 3, 'text' => 'O'],
['type' => 1, 'text' => 'P'],
['type' => 1, 'text' => 'Q'],
['type' => 2, 'text' => 'R'],
['type' => 3, 'text' => 'S'],
['type' => 2, 'text' => 'T'],
['type' => 3, 'text' => 'U'],
['type' => 2, 'text' => 'V'],
['type' => 3, 'text' => 'W'],
['type' => 2, 'text' => 'X'],
['type' => 4, 'text' => 'Y'],
['type' => 2, 'text' => 'Z'],
];
How can I sort this array and return as another array, with the following requirements?
type
.$items
array, and then moving on to the next type, which is 2
, and doing the same. The max amount of types
there are in my code is 8
, but theoretically it should work with any number.1
again.Essentially you should get another array which -- from top to bottom -- has values with their types in alternated orders, something like this:
[
['type' => 1, 'text' => 'A'],
['type' => 1, 'text' => 'F'],
['type' => 1, 'text' => 'I'],
['type' => 2, 'text' => 'B'],
['type' => 2, 'text' => 'C'],
['type' => 2, 'text' => 'E'],
['type' => 3, 'text' => 'D'],
['type' => 3, 'text' => 'N'],
['type' => 3, 'text' => 'O'],
['type' => 4, 'text' => 'G'],
['type' => 4, 'text' => 'J'],
['type' => 4, 'text' => 'L'],
['type' => 1, 'text' => 'P'],
['type' => 1, 'text' => 'Q'],
['type' => 2, 'text' => 'H'],
['type' => 2, 'text' => 'K'],
['type' => 2, 'text' => 'M'],
['type' => 3, 'text' => 'S'],
['type' => 3, 'text' => 'U'],
['type' => 3, 'text' => 'W'],
['type' => 4, 'text' => 'Y'],
['type' => 2, 'text' => 'R'],
['type' => 2, 'text' => 'T'],
['type' => 2, 'text' => 'V'],
['type' => 2, 'text' => 'X'],
['type' => 2, 'text' => 'Z'],
]
The text
does not matter to the sorting.
I've actually gone as far as to make a YouTube video to better explain what should happen: Video
My current thought process was to use a separate array called $groups
, use a foreach
loop eight different times, and in each one, continue the loop if it's not for the specific type. For example:
// My above $items array example here
// Initialize $groups
$groups = [];
// foreach for type 1
foreach ($items as $item) {
// If any of the items are not of type 1, ignore and continue
if ($item["type"] !== 1) {
continue;
}
// Otherwise, push to $groups
array_push($groups, $item["type"];
}
// foreach for type 2
foreach ($items as $item) {
// If any of the items are not of type 2, ignore and continue
if ($item["type"] !== 2) {
continue;
}
// Otherwise, push to $groups
array_push($groups, $item["type"];
}
// Do this for each possible type, up to type 8
This not only does this seem extremely inefficient and backwards, I just feel there is a better way to go about it or another angle I'm missing. I don't believe the above code is even close to the solution.
I think setting up array_multisort()
to do this job makes logical sense (since this is a sorting task).
Note that my demonstration will sort the actual input array (make yourself a copy if you need to preserve the original).
array_multisort()
to sort on $grouper
, then $column
, then $items
(then you can access $items
).$maxConsecutive = 3;
$grouper = [];
$column = [];
foreach ($items as $row) {
$column[] = $row['type']; // preserve isolated type values
$encountered[$row['type']] ??= 0; // declare the default value of 0 for the first encounter of a given type value
$grouper[] = intdiv($encountered[$row['type']]++, $maxConsecutive); // access the cached counter for the given type, divide it by 3, omit any decimal values; then increment the counter (post-incrementation `++` only increases the value AFTER it is used)
}
array_multisort($grouper, $column, $items); // sort by grouper ASC, column ASC, then items ASC
var_export($items);
Somewhat related is Sort a flat array in recurring ascending sequences which has a hardcoded grouping N of 1 (instead of 3) while sorting data in ascending recurring sequences.
Alternatively, you can group the data into subsets, then sort, then consume the grouped data while populating the new result.
Though, I will state that this doesn't feel as professional as the array_multisort()
approach for a sorting task.
type
value.foreach()
loop (notice the &
in the signature) to push only the first N rows per group into the result array. If the current group still has elements, push the whole group onto the end of the array.Code: (Demo)
$maxConsecutive = 3;
$grouped = [];
foreach ($items as $row) {
$grouped[$row['type']][] = $row;
}
ksort($grouped);
$result = [];
foreach ($grouped as &$group) {
array_push($result, ...array_splice($group, 0, $maxConsecutive));
if ($group) {
$grouped[] = $group;
}
}
var_export($result);
As a microoptimization, you can extract the entire payload of the last remaining group with: (Demo)
array_push($result, ...array_splice($group, 0, count($grouped) === 1 ? null : $maxConsecutive));