PHP's USORT Callback Function Parameters - php

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?

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

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.

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.

Related

Shuffle array with a repeatable/predictable result based on a seed integer

I have 6 users and I want to shuffle an array for each user but with a particular logic.
I have array like this:
$a = array(1, 6, 8);
When shuffled it gives me these results:
shuffle($a); //array(8,6,1) or array(8,1,6) or ...
I want to shuffle the array for a specific user and have it be the same every time for that user:
for user that has id equals 1, give array like this array(6,8,1) every time
for user that has id equals 2, give array like this array(1,8,6) every time
In other words, I want to shuffle an array with private key!
If you provide a seed to the random number generator it will randomize the same way for the same seed (see the version differences below). So use the user id as the seed:
srand(1);
shuffle($a);
Output for 7.1.0 - 7.2.4
Array
(
[0] => 1
[1] => 8
[2] => 6
)
Output for 4.3.0 - 5.6.30, 7.0.0 - 7.0.29
Array
(
[0] => 6
[1] => 1
[2] => 8
)
Note: As of PHP 7.1.0, srand() has been made an alias of mt_srand().
This Example should always produce the same result.
Quoting php.net:
This function shuffles (randomizes the order of the elements in) an array. It uses a pseudo random number generator that is not suitable for cryptographic purposes.
Whatever you are trying to get as a result, you cannot use shuffle because it will randomly give you some order.
If you want to randomly do an order to array, based on some criteria use usort:
function cmp($a, $b)
{
if ($a == $b) {
return 0;
}
return ($a < $b) ? -1 : 1;
}
$a = array(3, 2, 5, 6, 1);
usort($a, "cmp");
Now get the logic in the cmp function...

PHP 7 usort adds equal items to end of array where in PHP 5 it adds to the beginning

There seems to be an undocumented change to how PHP 7 handles equal results in usort functions.
$myArray = array(1, 2, 3);
usort($myArray, function($a, $b) { return 0; });
print_r($myArray);
// PHP 5:
Array
(
[0] => 3
[1] => 2
[2] => 1
)
// PHP 7
Array
(
[0] => 1
[1] => 2
[2] => 3
)
In other words in PHP 7, usort is adding equal values to the end of the array, whereas PHP 5 adds them to the beginning. I can't find any mention of this behaviour.
Is there a way of forcing the PHP 5 behaviour?
From the PHP docs:
If two members compare as equal, their relative order in the sorted array is undefined.
Relying on undefined behavior is a bad idea. There is no way to change the behavior (apart from making the items not equal).

Sort multidimensional array based upon first integer in each sub array

$final = array(array([0] => 123, [1] => 3, [2] => "true"),
array([0] => 9, [1] => 4, [3] => "false"),
array([0] => 541, [1] => 1, [3] => "false"));
I tried using php's sort function so that the $final array will be sorted from lowest to highest based upon the $final[$i][0] value, but it doesn't seem to work. This is just a quick example for the sake of in simplicity. In the real problem that I'm working with, the arrays that are inside of the $final array range from having as little as 3 elements to as many as 7. Don't know if that has any effect on the problem or not.
That's what PHP's usort function is for:
usort($final, function ($a, $b)
{
return $a[0] < $b[0] ? -1 : 1;
});
See it here in action: http://codepad.viper-7.com/omXxkR

ksort produces wrong result when dealing with alphanumeric characters

<?php
$a = array(
'a'=>'7833',
'd'=>'1297',
'c'=>'341',
'1'=>'67',
'b'=>'225',
'3'=>'24',
'2'=>'44',
'4'=>'22',
'0'=>'84'
);
ksort($a);
print_r($a);
The above code produces the following output.
Array
(
[0] => 84
[a] => 7833
[b] => 225
[c] => 341
[d] => 1297
[1] => 67
[2] => 44
[3] => 24
[4] => 22
)
Why does ksort give wrong result?
You'll want to use the SORT_STRING flag. SORT_REGULAR would compare items with their current types, in which case the number 1 does come after the string 'a':
php -r "echo 1 > 'a' ? 'yes' : 'no';" // yes
The default sorting uses SORT_REGULAR.
This takes the values and compares them as described on the comparison operators manual page. For the times when the string keys, in your example, are compared with zero; those strings are converted to numbers (all 0) for comparision. If two members compare as equal, their relative order in the sorted array is undefined. (Quoted from usort() manual page.)
If you want the sorted output to have numbers before letters, you should use SORT_NATURAL as of PHP 5.4. SORT_STRING will also do the job only if the numbers remain single digits.
SORT_NATURAL (PHP 5.4 or above) gives keys ordered as:
0,1,2,4,11,a,b,c
SORT_STRING gives keys ordered as:
0,1,11,2,4,a,b,c
An alternative to SORT_NATURAL for PHP less than 5.4, would be use uksort().
uksort($a, 'strnatcmp');
Try ksort($a, SORT_STRING) to force string comparisons on the keys.
This will work:
<?php ksort($a,SORT_STRING); ?>
Checkout the other sort_flags here http://www.php.net/manual/es/function.sort.php
Cheers!
ksort(array, sortingtype) sorts an associative array in ascending order, according to the keys, for a specified sorting type (sortingtype). But because sortingtype has a default value of SORT_REGULAR, when the keys have a combination of numbers and strings, that weid or unexpected behaviour occurs.
You must always remember to explicitly specify the sorting type, to avoid it confusing numbers with strings.
$a = array('a'=>'7833','d'=>'1297','c'=>'341','1'=>'67','b'=>'225','3'=>'24','2'=>'44','4'=>'22','0'=>'84');
ksort($a, SORT_STRING);
foreach ($a as $key => $val) {
echo "$key = $val\n";
}
PHP documentation on ksort
See this page for an overview of the different sort functions in php:
http://php.net/manual/en/array.sorting.php
If you want it sorted by key, then use asort(), which produces this output:
Array
(
[4] => 22
[3] => 24
[2] => 44
[1] => 67
[0] => 84
[b] => 225
[c] => 341
[d] => 1297
[a] => 7833
)

Sort array with the highest points

How do I sort an array with the highest points?
Example:
$sale = array();
Array
(
[UserA] => Array
(
[unsuccessful] => 0
[Points] => 31
[procesing] => 4
)
[UserB] => Array
(
[unsuccessful] => 4
[Points] => 200
[procesing] => 1
)
[UserC] => Array
(
[unsuccessful] => 3
[Points] => 150
[procesing] => 55
)
)
Sort by points, it should be in order: UserB, UserC, UserA
uasort($array, function($a, $b) {
return $b['Points'] - $a['Points'];
});
The uasort() and usort() functions take a callback that should specify exactly what makes one item greater or lower than another item. If this function returns 0, then the items are equal. If it returns a positive number, then the second item is greater than the first item. Else, the first item is greater than the second.
The difference between uasort() and usort() is that uasort() also keeps the keys, while usort() does not. Also take a look at the comparison of array sorting functions to find out about all the other ways you can sort arrays.
You can use the php function usort to provide your own custom sorting logic. See http://www.php.net/manual/en/function.usort.php
You may find USort helpful:
http://php.net/manual/en/function.usort.php
Also this other article on SO.
Sort multi-dimensional array with usort

Categories