PHP's USORT Callback Function Parameters
Asked Answered
C

3

12

This is a really esoteric question, but I'm genuinely curious. I'm using usort for the first time today in years, and I'm particularly interested in what exactly is going on. Suppose I've got the following array:

$myArray = array(1, 9, 18, 12, 56);

I could sort this with usort:

usort($myArray, function($a, $b){
  if ($a == $b) return 0;
  return ($a < $b) ? -1 : 1;
});

I'm not 100% clear about what is going on with the two parameters $a and $b. What are they, and what do they represent. I mean, I could assume that $a represents the current item in the array, but what exactly is this getting compared to? What is $b?

I could increase my array to include strings:

$myArray = array(
  array("Apples", 10),
  array("Oranges", 12),
  array("Strawberries", 3)
);

And run the following:

usort($myArray, function($a, $b){
  return strcmp($a[0], $b[0]);
});

And that would sort my child-arrays alphabetically based upon the [0] index value. But this doesn't offer any clarity about what $a and $b are. I only know that the match the pattern that I'm seeking.

Can somebody offer some clarity about what is actually taking place?

Cockscomb answered 7/7, 2009 at 11:10 Comment(1)
+1 I've always thought the same.Monopolist
B
6

To sort anything you need a means to compare two items and figure out if one comes before the other. This is what you supply to usort. This function will be passed two items from your input array, and returns the order they should be in.

Once you have a means to compare two elements, you can use sort-algorithm-of-your-choice.

If you are unfamiliar, you might like to look at how a simple naive algorithm like bubblesort would use a comparison function.

Behind the scenes, PHP is using a quicksort.

Brendis answered 7/7, 2009 at 11:16 Comment(1)
I believe Jonathan is interested in the "behind the scenes" part.Signory
I
31

The exact definition of $a and $b will depend upon the algorithm used to sort the array. To sort anything you have to have a means to compare two elements, that's what the callback function is used for. Some sorting algorithms can start anywhere in the array, others can start only in a specific part of it so there's no fixed meaning in $a and $b other than they are two elements in the array that have to be compared according to the current algorithm.

This method can be used to shed light upon which algorithm PHP is using.

<?php

$myArray = array(1, 19, 18, 12, 56);

function compare($a, $b) {
    echo "Comparing $a to $b\n";
    if ($a == $b) return 0;
    return ($a < $b) ? -1 : 1;
}

usort($myArray,"compare");
print_r($myArray);
?>

Output

vinko@mithril:~$ php sort.php
Comparing 18 to 19
Comparing 56 to 18
Comparing 12 to 18
Comparing 1 to 18
Comparing 12 to 1
Comparing 56 to 19
Array
(
    [0] => 1
    [1] => 12
    [2] => 18
    [3] => 19
    [4] => 56
)

From the output and looking at the source we can see the sort used is indeed a quicksort implementation, check for Zend/zend_qsort.c in the PHP source (the linked to version is a bit old, but hasn't changed much).

It picks the pivot in the middle of the array, in this case 18, then it needs to reorder the list so that all elements which are less (according to the comparison function in use) than the pivot come before the pivot and so that all elements greater than the pivot come after it, we can see it doing that when it compares everything to 18 at first.

Some further diagrammatic explanation.

Step 0: (1,19,18,12,56); //Pivot: 18, 
Step 1: (1,12,18,19,56); //After the first reordering
Step 2a: (1,12);         //Recursively do the same with the lesser, here 
                         //pivot's 12, and that's what it compares next if 
                         //you check the output.
Step 2b: (19,56);        //and do the same with the greater
Ithaca answered 7/7, 2009 at 11:23 Comment(2)
Excellent answer. Paul's was sufficient, and first. Therefore I awarded him the acceptance. I've upvoted yours though and appreciate your thoroughness.Cockscomb
For the sake of argument, I'd suggest that first isn't always better. If the second answer is more complete people should be rewarded for taking the time to answer the question fully.Pieria
B
6

To sort anything you need a means to compare two items and figure out if one comes before the other. This is what you supply to usort. This function will be passed two items from your input array, and returns the order they should be in.

Once you have a means to compare two elements, you can use sort-algorithm-of-your-choice.

If you are unfamiliar, you might like to look at how a simple naive algorithm like bubblesort would use a comparison function.

Behind the scenes, PHP is using a quicksort.

Brendis answered 7/7, 2009 at 11:16 Comment(1)
I believe Jonathan is interested in the "behind the scenes" part.Signory
M
0

usort() or uasort() have a human-feeling bug on sorted result. See the code segment:

function xxx($a,$b) { if ($a==$b) return 0; else return $a<$b?-1:1; }
$x=array(1=>10,2=>9,3=>9,4=>9,5=>6,6=>38);
uasort($x,'xxx');
print_r($x);

the result is:

Array ( [5] => 6 [4] => 9 [3] => 9 [2] => 9 [1] => 10 [6] => 38 )

Do you see the bug? No? Ok, let me explain it. The original three '9' elments are in key order: 2,3,4. But in the result, the three '9' elements are now in key order: 4,3,2, i.e. equal-value elements are in reverse key order after sorting.

If the element is only single value, as in above example,it's fine with us. However, if the element is compound value, then it could cause human-feeling bug. See another code segments. We are to sort many points horizontally, i.e. sort them based on ascending x-coordinate value order :

function xxx($a,$b) { if ($a['x']==$b['x']) return 0; else return $a['x']<$b['x']?-1:1; }
$x=array(1=>array('x'=>1, 'v'=>'l'),2=>array('x'=>9, 'v'=>'love'),
       3=>array('x'=>9,  'v'=>'Lara'),4=>array('x'=>9,  'v'=>'Croft'),
       5=>array('x'=>15,  'v'=>'and'),6=>array('x'=>38,  'v'=>'Tombraider'));
uasort($x,'xxx');
print_r($x);

the result is:

Array ( [1] => Array ( [x] => 1 [v] => l ) [4] => Array ( [x] => 9 [v] => croft ) 
             [3] => Array ( [x] => 9 [v] => Lara ) [2] => Array ( [x] => 9 [v] => love )
             [5] => Array ( [x] => 15 [v] => and ) [6] => Array ( [x] => 38 [v] => Tombraider ) )

You see 'I love Lara Croft and Tombraider' becomes 'I Croft Lara love and Tombraider'.

I call it human-feeling bug because it depends on what case you use and how you feel it should be sorted in real world when the compared values are same.

Measures answered 19/10, 2016 at 17:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.