Stable uasort - why is the order being reversed? - php

I'm using uasort to sort an array that looks like this:
Array
(
[2] => 0
[3] => 0
[4] => 0
)
I'm trying to sort by value, maintaining key association. In addition, I need to keep the original order if the values are the same.
So I'm doing the following:
uasort($arr, array($this, 'mysort'));
function mysort($a, $b){
if($a == $b){
return 0;
}
return ($a < $b) ? -1:1;
}
However, this gives the resulting array:
Array
(
[4] => 0
[3] => 0
[2] => 0
)
Why is the array being effectively reversed?

uasort is not a stable sort, that is, it doesn't maintain the order of equal values.
The manual's notes section contains a stable_uasort.

Related

Spliting .txt file lines in to multi-layered array key value pairs then sorting it

Since this question has a long explanation, I'll ask the question, then have the explanation below -- Can you sort a multidimensional array by their internal array key value, or is there a better way to get around sorting key value pairs that will have inevitable duplicates, than just using an array?
I am mostly unfamiliar with using PHP and want to learn how to store data.
The very simple example I made is just two HTML form inputs for a score and a name and a PHP file to handle the input to be stored in a plain .txt file, which was originally written with the pattern
42|John
32|Jane
25|John
I was able to successfully split the data, sort it, add the new inputted values then store it all back in the text file to be displayed somewhere else, using the name as the key and the score as the value.
I did all this only to realize that it would only store sort and display the last value associated with each name (i.e.)
42|John
32|Jane
25|John
would be sorted to
32|Jane
25|John
because you, obviously, can't have two of the same keys in an array, which is something I completely overlooked.
My solution, currently is to have an extra number that is unique to each name/score pair, which I formatted in the text file as
1|42|John
2|32|Jane
3|25|John
I then split them into a multidimensional array using this foreach loop
foreach($arr as $key => $value) {
$lineData = explode("|", $value);
$scores[$lineData[0]] = array($lineData[1] => $lineData[2]);
}
To get this output
Array
(
[1] => Array
(
[42] => John
)
[2] => Array
(
[32] => Jane
)
[3] => Array
(
[25] => John
)
)
which avoids overwriting any duplicate names or scores, but leaves me in a position where I can't (to my knowledge) use arsort() to sort the array in to highest to lowest.
You can use array_multisort for that, in combination with array_column. Because the key values are strings, you need to also convert them to integers, for which you can use array_map("intval", ...):
foreach($arr as $value) {
$result[] = explode("|", $value);
}
array_multisort(array_map("intval", array_column($result, 0)), $result);
After the above code has run, $result will be sorted by the key values:
[
['25', 'John'],
['32', 'Jane'],
['42', 'John']
]
To reverse the order, apply array_reverse to the result.
Alternative
You could also decide to sort the original array without conversion to a 2D array, and sort it with a custom sort callback, using usort and (again) intval:
usort($arr, function ($a, $b) {
return intval($a) - intval($b);
});
Then $arr will be sorted to:
[
'25|John',
'32|Jane',
'42|John'
]
To reverse the order, switch the position of $a and $b in the sort callback function:
return intval($b) - intval($a);
If we make a small change to your foreach iteration like this:
foreach($arr as $key => $value) {
$lineData = explode("|", $value);
$scores[] = array('score' => $lineData[1], 'name' => $lineData[2]);
}
Your array will have:
Array
(
[0] => Array
(
[score] => 42
[name] => John
)
[1] => Array
(
[score] => 32
[name] => Jane
)
[2] => Array
(
[score] => 25
[name] => John
)
)
You can use the uasort function, which takes the array to sort, and an user-defined function to do the sorting. The code would look like this:
function compare($a, $b)
{
if ($b['score'] == $a['score']) {
if ($a['name'] == $b['name']) {
return 0;
} elseif ($a['name'] < $b['name']) {
return -1;
} else {
return 1;
}
} else {
return ($b['score'] - $a['score']);
}
}
print_r($scores);
uasort($scores, 'compare');
print_r($scores);
Which gives the following result:
Array
(
[1] => Array
(
[score] => 32
[name] => Jane
)
[2] => Array
(
[score] => 25
[name] => John
)
[0] => Array
(
[score] => 42
[name] => John
)
)
When you use a user-defined function for the sorting you need to return one of 3 values (0 of the values as equal, -1 if $a < $b, and 1 if $b > $a. In this case we're sorting first by score (descending), then by name (ascending). Since you need to order from highest to lowest score, the comparison is $b against $a, for ascending order is $a against $b. I didn't consider the extra number necessary. If you need it then change this line:
$scores[] = array('score' => $lineData[1], 'name' => $lineData[2]);
To this:
$scores[$lineData[0]] = array('score' => $lineData[1], 'name' => $lineData[2]);

Using uasort trying to sort a numeric array and put any alphabetical values at the end of the array

I have an associative array something like this:
$return['data']
Array (
[0] => Array
(
[id] => 19957
[yt_id] => 19957-OPEN
[name] => 19957-OPEN
[score] => N/A
)
[1] => Array
(
[id] => 19965
[yt_id] => 19965-OPEN
[name] => 19965-OPEN
[score] => N/A
)
[2] => Array
(
[id] => 20034
[yt_id] => UCGbcYB89XesffWVJKPUpA
[name] => 1994firebug
[score] => 458.43
)
[3] => Array
(
[id] => 20036
[yt_id] => UCGbcfgXexlIirWVJKPUpA
[name] => 1994firebug
[score] => 344.69
) );
I'm sorting this array on the basis of score in ascending and descending order.
So now of the values in the array for score are 'N/A'.
So when sorting in ascending the the score with 'N/A' values shows at last. And when descending it shows up in the beginning.
Now what I want to do is whether ascending or descending the array should be sorted accordingly based on the score values but when score values are 'n/a' I want all those 'N/A' values to show at the end of the array at all times whether its ascending or descending.
Here is my code.
if($sort_by == "score"){
if($sort_direction == "asc") {
uasort($return['data'], function($a, $b) use ($sort_by) { return strnatcmp($a[$sort_by], $b[$sort_by]);});
} else {
uasort($return['data'], function($a, $b) use ($sort_by) { return strnatcmp($b[$sort_by], $a[$sort_by]); });
}
$return['data'] = array_slice($return['data'], $start, $length);
}
I hope my question is clear and if someone can help with an easy and efficient solution.
Edit:- I thought of doing this - check if the values of score in the array are 'N/A' unset them from their position and move them to the last. Although it is still not working I'm getting an error "Illegal offset type in unset". I don't know what I'm doing wrong there.
I'm doing this once the whole array is sorted in the if else statements.
foreach ($return['data'] as $key) {
if($key['score'] == 'N/A'){
$item = $key;
unset($key);
array_push($return['data'], $item);
}
}
Even though if it works my application has hundreds in some cases thousands of records to sort in an array. So sorting this way has already become very slow. So I'm sure using any more loops or something like what I'm trying to do will make it even more slow. Which is not feasible in my case. So does anyone have a better solution to this?
Edit: I've changed the code a bit and its no longer giving me an illegal offset error. But still it's not working as I expected it to. Not removing all the N/A values and putting them in the end.
To simplify it:
usort($data, function (array $a, array $b) {
if ($a['score'] == $b['score']) {
return 0;
}
if ($a['score'] == 'N/A') {
return -1;
}
if ($b['score'] == 'N/A') {
return 1;
}
return $a['score'] - $b['score'];
});
If both values are equal ("N/A" or anything else), the result is 0, else if a is "N/A" (and the other one is not, as proven in the previous step), then a is greater, vice versa for b, else do a general numeric comparison.

Sort an array from high to low

In the following array, I want to change the key order from high to low (so for example the year 2014 data appears first).
print_r($array);
Output:
Array
(
[0] => Array
(
[year] => 2013
[name] => xx
)
[1] => Array
(
[year] => 2014
[name] => xx
)
)
I have tried using rsort, but it returns only "1".
$array = rsort($array);
print_r($array); //1
var_dump($array); //bool(true).
rsort() will only work on single-dimensional arrays. You have a 2-dimensional array, so you will need to use a different function such as usort(), which lets you use user-defined comparison function for sorting:
usort($data, function ($a, $b) {
return $a['year'] < $b['year'];
});
Output:
Array
(
[0] => Array
(
[year] => 2014
[name] => xx
)
[1] => Array
(
[year] => 2013
[name] => xx
)
)
Working demo
change
$array = rsort($array);
print_r($array);
to
rsort($array);
print_r($array);
rsort has return value of boolean, so just simple use it like this:
rsort($array);
And also, rsort is sorting array values in reverse order, not array keys, check the documentation:
http://php.net/manual/en/function.rsort.php
So in reverse order simply just use krsort - Sort an array by key in reverse order:
http://php.net/manual/en/function.krsort.php
So your code:
krsort($array);
usort($array, function($item1, $item2){
if ($item1->year > $item2->year ) return true;
else return false;
})
That´s if you wanted to order by year, if you want to order by keys uksort could be used instead

Sorting multidimensional array values in PHP

I have the following array:
Array ( [0] => Array
( [name] => Jonah
[age] => 27 )
[1] => Array
( [name] => Bianca
[age] => 32 )
)
Is it possible to sort the sub-array values in [age] into some sort of order, such as lowest to highest or vice versa?
You can do this using usort:
usort($arr, function($a, $b)
{
return $a['age'] - $b['age']; // sorts lowest to highest
});
Swap $a and $b in the function to reverse the ordering.
I think this should be possible with bool usort ( array &$array , callback $cmp_function )
http://php.net/manual/en/function.usort.php
Just define a callback that sorts by the [age] key of the value.
This will work:
$ages = array();
foreach ($array as $value) {
$ages[] = $value;
}
array_multisort($values, SORT_ASC, $array);
Any way is good, but this is the "PHP way":
array_multisort() can be used to sort several arrays at once, or a multi-dimensional array by one or more dimensions.

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