'PHP usort() order in case of equality

In the PHP manual for usort(), it states:

If two members compare as equal, their relative order in the sorted array is undefined.

Also,

A new sort algorithm was introduced. The cmp_function doesn't keep the original order for elements comparing as equal.

Then my question is: what happens if two elements are equal (e.g. the user defined function returns 0)? I am using this function and apparently equal items are arranged randomly in the sorted array.



Solution 1:[1]

Be careful not to confuse "undefined" and "random".

A random implementation would indeed be expected to give a different ordering every time. This would imply there was specific code to shuffle the results when they're found to be equal. This would make the algorithm more complex and slower, and rarely be a desirable outcome.

What undefined means is the opposite: absolutely no care has been taken while designing the algorithm to have predictable or stable order. This means the result may be different every time you run it, if that happens to be the side effect of the algorithm on that data.

You can see the core sort implementation in the PHP source code. It consists of a mixture of "quick sort" (divide and conquer) and insertion sort (a simpler algorithm effective on short lists) with hand-optimised routines for lists of 2, 3, 4, and 5 elements.

So the exact behaviour of equal members will depend on factors like the size of the list, where in the list those equal members come, how many equal members there are in one batch, and so on. In some situations, the algorithm will see that they're identical, and not swap them (the ideal, because swapping takes time); in others, it won't directly compare them until they've already been moved relative to other things, so they'll end up in a different order.

Solution 2:[2]

I also find the same while sorting my array, then I found some custom made function because php have some limitation to use defined sorting function .

http://php.net/manual/en/function.uasort.php#Vd114535

PHP 7 uses a stable sort algorithm for small arrays (< 16), but for larger arrays the algorithm is still not stable. Furthermore PHP makes no guarantee whether sorting with *sort() is stable or not https://bugs.php.net/bug.php?id=53341.

Solution 3:[3]

If I have an array of: ['b', 'a', 'c', 'b'] and I were to sort this I would get: ['a','b','b','c']. Since 'b' == 'b' php can not guarantee that one comes before the other so the sort order is 'undefined', however since they are equal, does this matter?

If you are using a sort function that returns 0 for unequal objects you're facing a different problem altogether.

Solution 4:[4]

understand that php does not care about the order if all the compared values are same.

Example:

$temp=array("b"=>"10","c"=>"10","d"=>"10","e"=>"4");

as above array has 4 array length in which 3 are having the same values as shown b,c,d = 10; arsort() //The arsort() function sorts an associative array in descending order, according to the value

if print_r(arsort($temp)) o/p: => Array ( [b] => 10 [c] => 10 [d] => 10 [e] => 4 )

this means it return array after sorting equal value but keeps the position(order) same for equal values

but

if $temp=array("a"=>"4",b"=>"10","c"=>"10","d"=>"10","e"=>"4"); here in above array b,c,d = 10 are bounded under two extreme left and right arrays having value less then center (b,c,d = 10) values

the arsort of above temp is o/p: Array ( [c] => 10 [b] => 10 [d] => 10 [a] => 4 [e] => 4 )

it gives the middle part i.e [c] array in center. this means if similar values or equal values array is bounded by from both side by lower values array or the first value is lower then the order of equal gives middle one from three array values as first in that three .

Solution 5:[5]

I was also facing the same problem, where 2 rows have same values and when apply sort function its order gets changed which I didn't want. I want to sort keys based on their values, if they are equal don't change order. So here is my solution-

// sample array 
$arr = Array("a" => 0.57,"b" => 1.19,"c" => 0.57,"d" => 0.57,"e" => 0.57,"f" => 0.57,"g" => 0.57,"h" => 0.57,"i" => 0.99,"j" => 1.19,"k" => 1.19);
    $multi_arr = [];
    foreach ($arr as $k=>$val){
       $multi_arr["$val"][] = array($k=>$val); 
    }
    uksort($multi_arr, function ($a, $b) { 
                    return $b > $a ? 1 : -1;
            });
    $s_arr = [];
    foreach ($multi_arr as $k=>$val){
        foreach($val as $p_id){         
            $p_arr = array_keys($p_id);
            $s_arr[] = $p_arr[0]; 
        }
    }
print_r($s_arr);

output-

Array([0] => b,[1] => j,[2] => k,[3] => i,[4] => a,[5] => c,[6] => d,[7] => e,[8] => f,[9] => g,[10] => h)

Solution 6:[6]

This function preserves order for equal values (example from my CMS EFFCORE):

function array_sort_by_number(&$array, $key = 'weight', $order = 'a') {
  $increments = [];
  foreach ($array as &$c_item) {
    $c_value = is_object($c_item) ? $c_item->{$key} : $c_item[$key];
    if ($order === 'a') $increments[$c_value] = array_key_exists($c_value, $increments) ? $increments[$c_value] - .0001 : 0;
    if ($order === 'd') $increments[$c_value] = array_key_exists($c_value, $increments) ? $increments[$c_value] + .0001 : 0;
    if (is_object($c_item)) $c_item->_synthetic_weight   = $c_value + $increments[$c_value];
    else                    $c_item['_synthetic_weight'] = $c_value + $increments[$c_value];
  }
  uasort($array, function ($a, $b) use ($order) {
    if ($order === 'a') return (is_object($b) ? $b->_synthetic_weight : $b['_synthetic_weight']) <=> (is_object($a) ? $a->_synthetic_weight : $a['_synthetic_weight']);
    if ($order === 'd') return (is_object($a) ? $a->_synthetic_weight : $a['_synthetic_weight']) <=> (is_object($b) ? $b->_synthetic_weight : $b['_synthetic_weight']);
  });
  foreach ($array as &$c_item) {
    if (is_object($c_item)) unset($c_item->_synthetic_weight);
    else                    unset($c_item['_synthetic_weight']);
  }
  return $array;
}

It works with both arrays and objects. It adds a synthetic key, sorts by it, and then removes it.

Test

$test = [
  'a' => ['weight' =>  4],
  'b' => ['weight' => 10],
  'c' => ['weight' => 10],
  'd' => ['weight' => 10],
  'e' => ['weight' =>  4],
];
$test_result = [
  'b' => ['weight' => 10],
  'c' => ['weight' => 10],
  'd' => ['weight' => 10],
  'a' => ['weight' =>  4],
  'e' => ['weight' =>  4],
];
array_sort_by_number($test, 'weight', 'a');
print_R($test);
var_dump($test === $test_result);

$test = [
  'a' => ['weight' =>  4],
  'b' => ['weight' => 10],
  'c' => ['weight' => 10],
  'd' => ['weight' => 10],
  'e' => ['weight' =>  4],
];
$test_result = [
  'a' => ['weight' =>  4],
  'e' => ['weight' =>  4],
  'b' => ['weight' => 10],
  'c' => ['weight' => 10],
  'd' => ['weight' => 10],
];
array_sort_by_number($test, 'weight', 'd');
print_R($test);
var_dump($test === $test_result);

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 IMSoP
Solution 2 Muneer Alam
Solution 3 Alexander Varwijk
Solution 4 bg17aw
Solution 5 Nisse Engström
Solution 6 Maxim Rysevets