Reputation: 12138
I'm receiving an array from a database - I have no control over what pieces of data are sent and in what order. This is what it currently looks like:
Array
(
[itemCode] => Array
(
[0] => Array
(
[code] => P
[descShort] => Pepperoni
)
[1] => Array
(
[code] => G
[descShort] => Green Peppers
)
[2] => Array
(
[code] => n
[descShort] => No Sauce
)
[3] => Array
(
[code] => x
[descShort] => No Cheese
)
[4] => Array
(
[code] =>
[descShort] => Regular Cheese
)
[5] => Array
(
[code] =>
[descShort] => Regular Sauce
)
)
)
In actual practice, there can be any number of elements before the No Sauce option (currently at index 3, but not always that way.) What the client wants is for the Cheese and Sauce items to always be at the end of the list and ordered this way: Regular Cheese, No Cheese, Regular Sauce, No Sauce.
Again, keeping in mind that I have no control over how the array is initially created, and that there could be any number of other elements both before and between the elements in question, how can I make this happen? Something else to worry about is that at some point, there may be other options they want to include in this re-ordering (they may add an option for Extra Cheese and Extra Sauce, for example, and want them to be in specific positions as well.)
Added var_export
array (
'itemCode' =>
array (
0 =>
array (
'code' => 'P',
'descShort' => 'Pepperoni',
),
1 =>
array (
'code' => 'G',
'descShort' => 'Green Peppers',
),
2 =>
array (
'code' => 'n',
'descShort' => 'No Sauce',
),
3 =>
array (
'code' => 'x',
'descShort' => 'No Cheese',
),
4 =>
array (
'code' => '',
'descShort' => 'Regular Cheese',
),
5 =>
array (
'code' => '',
'descShort' => 'Regular Sauce',
),
),
)
Upvotes: 2
Views: 2132
Reputation: 197787
As it has been already commented, the usort
function can be used for that. But this is only the start as it needs the compare function.
That is also not that hard, because we can just create one. However, it's crucial to understand how it works:
The comparison function must return an integer less than, equal to, or greater than zero if the first argument is considered to be respectively less than, equal to, or greater than the second.
Okay, that's not a blocker but turns the actual question, how to find out which one is above which one? And how to do that extensible?
There are two type of values: Either the ones that do not need any ordering or the ones that need ordering. Let's first define an array for those that need ordering. And to make it clear for what that ordering is, name the key:
$order = 'descShort';
$ordering = ['Regular Cheese', 'No Cheese', 'Regular Sauce', 'No Sauce'];
Now inside a compare function, you can look up if for the A/B values an entry exists within the ordering. If it does not exists, then this needs no ordering. If it exists, this needs ordering.
These two cases are extended because you also can have an existing sort order with a non-existing one. So there are four cases to cover:
Thanks to function support in PHP, we can pass along the order-key and the ordering values into the comparison function easily for a quick example. The function then only has to do what has been outlined for the four cases.
The example:
$order = 'descShort';
$ordering = ['Regular Cheese', 'No Cheese', 'Regular Sauce', 'No Sauce'];
$compare = function($a, $b) use ($order, $ordering) {
$hasA = array_search($a[$order], $ordering);
$hasB = array_search($b[$order], $ordering);
// nothing to sort
if ($hasA === $hasB && $hasA === FALSE) {
return 0;
}
// if both are found, sort
if ($hasA !== FALSE && $hasB !== FALSE) {
return $hasA - $hasB;
}
// as one of them is in there, put it to end
return $hasA === FALSE ? -1 : 1;
};
usort($array['itemCode'], $compare);
So now there is one caveat: usort
is not stable. That means, when returning 0, items do not stay at their position. You can work around that by sorting once again the same with usort
.
usort($array['itemCode'], $compare);
Then the final sort order is (Demo):
Array
(
[itemCode] => Array
(
[0] => Array
(
[code] => P
[descShort] => Pepperoni
)
[1] => Array
(
[code] => G
[descShort] => Green Peppers
)
[2] => Array
(
[code] =>
[descShort] => Regular Cheese
)
[3] => Array
(
[code] => x
[descShort] => No Cheese
)
[4] => Array
(
[code] =>
[descShort] => Regular Sauce
)
[5] => Array
(
[code] => n
[descShort] => No Sauce
)
)
)
As there was a problem with stable sort and I'm also not that well with it (the suggested function looks a bit like an overhead to me in your case here), there is just good old foreach
.
And as there are no duplicate values for those to be sorted later on, well, this leaves some nifty room:
$order = 'descShort';
$ordering = ['Regular Cheese', 'No Cheese', 'Regular Sauce', 'No Sauce'];
$result = []; // the final result
$later = []; // to be sorted later
foreach($array as $element)
{
$has = array_search($element[$order], $ordering);
if ($has !== FALSE) {
$later[$has] = $element;
continue;
}
$result[] = $element;
}
It is just straight ahead for-eaching over the array, putting all values into $result
already that are not part of the ordering.
Those that are part of the ordering are put into $later
with their order value as index already.
Only $later
then is sorted with ksort
, and then the two parts are merged:
ksort($later);
$result = array_merge($result, $later);
And done. No callback function needed. Just first filtering, and the indexing with the sort value together with ksort
does the magic. Demo.
Upvotes: 2
Reputation: 781078
I think this is the user-defined sort that you want. The array $last_options
lists all the toppings that should be sorted to the end, and their order in the array specifies the order they should appear in the result.
$last_options = array('Regular Cheese', 'No Cheese', 'Regular Sauce', 'No Sauce');
function topping_order($a, $b) {
$a_pos = array_search($a['descShort'], $last_options);
$b_pos = array_search($b['descShort'], $last_options);
if ($a_pos !== false && $b_pos !== false) {
if ($a_pos > $b_pos) {
return 1;
}
if ($b_pos > $a_pos) {
return -1;
}
return 0;
}
if ($a_pos === false) {
return -1;
}
if ($b_pos === false) {
return 1;
}
return 0;
}
usort($array, 'topping_order');
Upvotes: 0
Reputation: 5239
You can try using usort, which will allow a user-defined sort.
Upvotes: 3