Preserve key order (stable sort) when sorting with PHP's uasort
Since PHP does not support stable sort after PHP 4.1.0, you need to write your own function.
This seems to do what you're asking: http://www.php.net/manual/en/function.usort.php#38827
As the manual says, "If two members compare as equal, their order in the sorted array is undefined." This means that the sort used is not "stable" and may change the order of elements that compare equal.
Sometimes you really do need a stable sort. For example, if you sort a list by one field, then sort it again by another field, but don't want to lose the ordering from the previous field. In that case it is better to use usort with a comparison function that takes both fields into account, but if you can't do that then use the function below. It is a merge sort, which is guaranteed O(n*log(n)) complexity, which means it stays reasonably fast even when you use larger lists (unlike bubblesort and insertion sort, which are O(n^2)).
<?php
function mergesort(&$array, $cmp_function = 'strcmp') {
// Arrays of size < 2 require no action.
if (count($array) < 2) return;
// Split the array in half
$halfway = count($array) / 2;
$array1 = array_slice($array, 0, $halfway);
$array2 = array_slice($array, $halfway);
// Recurse to sort the two halves
mergesort($array1, $cmp_function);
mergesort($array2, $cmp_function);
// If all of $array1 is <= all of $array2, just append them.
if (call_user_func($cmp_function, end($array1), $array2[0]) < 1) {
$array = array_merge($array1, $array2);
return;
}
// Merge the two sorted arrays into a single sorted array
$array = array();
$ptr1 = $ptr2 = 0;
while ($ptr1 < count($array1) && $ptr2 < count($array2)) {
if (call_user_func($cmp_function, $array1[$ptr1], $array2[$ptr2]) < 1) {
$array[] = $array1[$ptr1++];
}
else {
$array[] = $array2[$ptr2++];
}
}
// Merge the remainder
while ($ptr1 < count($array1)) $array[] = $array1[$ptr1++];
while ($ptr2 < count($array2)) $array[] = $array2[$ptr2++];
return;
}
?>
Also, you may find this forum thread interesting.
PHP usort reorders array the sort value is the same for all
Aha, a case for the Schwartzian Transform.
It basically consists of three steps:
- decorate; you turn every value into an array with the value as the first element and the key/index as the second
- sort (as per normal)
- undecorate; you reverse step 1
Here it is (I've tweaked it to your particular use case):
function decorate(&$v, $k)
{
$v['authn_weight'] = array($v['authn_weight'], $k);
}
function undecorate(&$v, $k)
{
$v['authn_weight'] = $v['authn_weight'][0];
}
array_walk($a, 'decorate');
usort($a, 'weightSortImplementation');
array_walk($a, 'undecorate');
The trick is in the following assertion:
array($x, 0) < array($x, 1)
This is what keeps the correct order of your array. And, no recursion required :)
arsort() not stable
You don't need to sort the array if you're only looking for the preferred language:
<?php
function findPrefferedLanguage($languages) {
foreach ($languages as $lang => $weight) {
if (empty($key) || ($weight > $languages[$key])) {
$key = $lang;
}
}
return $key;
}
$foo = array('es' => .6, 'en' => 1, 'fr' => 1, 'de' => .5);
var_dump(findPrefferedLanguage($foo)); // en
Hastily tested... there's probably some edge-cases that will generate errors/warnings.
How to have a stable sort in PHP with arsort()?
Construct a new array whose elements are the original array's keys, values, and also position:
$temp = array();
$i = 0;
foreach ($array as $key => $value) {
$temp[] = array($i, $key, $value);
$i++;
}
Then sort using a user-defined order that takes the original position into account:
uasort($temp, function($a, $b) {
return $a[2] == $b[2] ? ($a[0] - $b[0]) : ($a[2] < $b[2] ? 1 : -1);
});
Finally, convert it back to the original associative array:
$array = array();
foreach ($temp as $val) {
$array[$val[1]] = $val[2];
}
Why usort reverts an array when values are equals on PHP prior to 7.0
If you want to have the same results in both versions, you will need to ensure that you specify how equal items will deal with other fields.
So you could solve this with...
usort($arr1, function ($a, $b) {
if($a['points'] == $b['points']){
return $a['date'] - $b['date'];
}
return ($a['points'] < $b['points']) ? 1 : -1;
});
So if the points
value is the same, then use date
as the differentiator (assuming a numeric comparison would work.)
Sorting array keeping even values on top PHP
usort
is not stable. The documentation states:
If two members compare as equal, their relative order in the sorted array is undefined.
So, what you can do is:
usort($array, function($a, $b) {
if ($a % 2 == $b % 2) {
return intval($a - $b);
}
elseif ($a % 2 == 0 )
{
return -1 ;
}
else
{
return 1;
}
}
);
This compares the actual values if both are even or both are odd.
Related Topics
PHP 5 Strpos() Difference Between Returning 0 and False
Insert Query on Page Load, Inserts Twice
Upload Multiple Files With PHP and Jquery
How to Convert Many Statement MySQL to Laravel Eloquent
Restructure Multidimensional Array of Column Data into Multidimensional Array of Row Data
Best Practice Multi Language Website
Create a Folder If It Doesn't Already Exist
Switch PHP Versions on Commandline Ubuntu 16.04
How to Write to the Console in PHP
Upstream Sent Too Big Header While Reading Response Header from Upstream
Get Absolute Path of Initially Run Script
Add a Custom Attribute to a Laravel/Eloquent Model on Load
How to Check If a String Starts With a Specified String