How to sort an array of associative arrays by value of a given key in PHP?
Solution 1
You are right, the function you're looking for is array_multisort()
.
Here's an example taken straight from the manual and adapted to your case:
$price = array();
foreach ($inventory as $key => $row)
{
$price[$key] = $row['price'];
}
array_multisort($price, SORT_DESC, $inventory);
As of PHP 5.5.0 you can use array_column()
instead of that foreach:
$price = array_column($inventory, 'price');
array_multisort($price, SORT_DESC, $inventory);
Solution 2
PHP 7+
As of PHP 7, this can be done concisely using usort
with an anonymous function that uses the spaceship operator to compare elements.
You can do an ascending sort like this:
usort($inventory, function ($item1, $item2) {
return $item1['price'] <=> $item2['price'];
});
Or a descending sort like this:
usort($inventory, function ($item1, $item2) {
return $item2['price'] <=> $item1['price'];
});
To understand how this works, note that usort
takes a user-provided comparison function that must behave as follows (from the docs):
The comparison function must return an integer less than, equal to, or greater than zero if the first argument is considered to be respectively less than, equal to, or greater than the second.
And note also that <=>
, the spaceship operator,
returns 0 if both operands are equal, 1 if the left is greater, and -1 if the right is greater
which is exactly what usort
needs. In fact, almost the entire justification given for adding <=>
to the language in https://wiki.php.net/rfc/combined-comparison-operator is that it
makes writing ordering callbacks for use with
usort()
easier
PHP 5.3+
PHP 5.3 introduced anonymous functions, but doesn't yet have the spaceship operator. We can still use usort
to sort our array, but it's a little more verbose and harder to understand:
usort($inventory, function ($item1, $item2) {
if ($item1['price'] == $item2['price']) return 0;
return $item1['price'] < $item2['price'] ? -1 : 1;
});
Note that although it's fairly common for comparators dealing with integer values to just return the difference of the values, like $item2['price'] - $item1['price']
, we can't safely do that in this case. This is because the prices are floating point numbers in the question asker's example, but the comparison function we pass to usort
has to return integers for usort
to work properly:
Returning non-integer values from the comparison function, such as float, will result in an internal cast to integer of the callback's return value. So values such as 0.99 and 0.1 will both be cast to an integer value of 0, which will compare such values as equal.
This is an important trap to bear in mind when using usort
in PHP 5.x! My original version of this answer made this mistake and yet I accrued ten upvotes over thousands of views apparently without anybody noticing the serious bug. The ease with which lackwits like me can screw up comparator functions is precisely the reason that the easier-to-use spaceship operator was added to the language in PHP 7.
Solution 3
While others have correctly suggested the use of array_multisort()
, for some reason no answer seems to acknowledge the existence of array_column()
, which can greatly simplify the solution. So my suggestion would be:
array_multisort(array_column($inventory, 'price'), SORT_DESC, $inventory);
If you want Case Insensitive Sort on strings, you can use SORT_NATURAL|SORT_FLAG_CASE
array_multisort(array_column($inventory, 'key_name'), SORT_DESC, SORT_NATURAL|SORT_FLAG_CASE, $inventory);
Solution 4
Since your array elements are arrays themselves with string keys, your best bet is to define a custom comparison function. It's pretty quick and easy to do. Try this:
function invenDescSort($item1,$item2)
{
if ($item1['price'] == $item2['price']) return 0;
return ($item1['price'] < $item2['price']) ? 1 : -1;
}
usort($inventory,'invenDescSort');
print_r($inventory);
Produces the following:
Array
(
[0] => Array
(
[type] => pork
[price] => 5.43
)
[1] => Array
(
[type] => fruit
[price] => 3.5
)
[2] => Array
(
[type] => milk
[price] => 2.9
)
)
Solution 5
I ended on this:
function sort_array_of_array(&$array, $subfield)
{
$sortarray = array();
foreach ($array as $key => $row)
{
$sortarray[$key] = $row[$subfield];
}
array_multisort($sortarray, SORT_ASC, $array);
}
Just call the function, passing the array and the name of the field of the second level array. Like:
sort_array_of_array($inventory, 'price');
Matt
Updated on July 08, 2022Comments
-
Matt almost 2 years
Given this array:
$inventory = array( array("type"=>"fruit", "price"=>3.50), array("type"=>"milk", "price"=>2.90), array("type"=>"pork", "price"=>5.43), );
I would like to sort
$inventory
's elements by price to get:$inventory = array( array("type"=>"pork", "price"=>5.43), array("type"=>"fruit", "price"=>3.50), array("type"=>"milk", "price"=>2.90), );
How can I do this?
-
Matt over 14 yearsYes. I'll do that if I can't find a solution. I'm pretty sure there are some weird parameters you can add to one of the sorts to accomplish this. Thanks for your thoughts though!
-
Matt over 14 yearsThough this is definitely more expensive than the alternatives.
-
Josh Davis over 14 yearsMore expensive? That's weird, on my machine (running PHP 5.3.1-dev) array_multisort() is a few percent faster on small arrays and up to 100 times faster on big arrays (100+ elements)
-
Josh Davis over 12 yearsIt shouldn't require any change to work with numeric keys. If you're hitting a bug or weird behaviour related to numeric keys, post it as a new question.
-
schellmax over 12 yearsthis won't work for multidimensional arrays, but just helped me out for another problem, thanks :)
-
machineaddict over 10 yearsarray_multisort has a big problem: it doesn't maintain the original key.
-
Mark Amery over 10 yearsThis can't be used to sort a list of dictionaries by a particular dictionary key, and hence doesn't answer the question posed.
-
Chris Baker over 10 yearsThe requirement for unique sort keys is sort of a deal breaker, though. If you have unique sort values that can be keys, it begs the question: why not simply construct the array with those keys to begin with? In the OP's scenario, it is difficult to imagine that two items with the same price would be impossible. That in mind, using this solution would cause items from the array to mysteriously and silently disappear from the sorted result set.
-
Matteo-SoftNet about 10 yearsSorry, but this approach deletes the string keys from associative arrays. "uasort" function should be used, instead.
-
Mark Amery about 10 years@DotMat Interesting - I didn't know about
uasort
. After looking at the docs, though, this answer is still correct in this case. In the OP's example, the array to be sorted has sequential numeric indexes rather than string indexes, sousort
is more appropriate. Usinguasort
on a sequentially-indexed array will result in a sorted array which is not ordered by its numeric indexes, such that the first element seen in aforeach
loop is not$your_array[0]
, which is unlikely to be desirable behaviour. -
Alan Porter about 10 yearsCombining with some of the other comments here (uasort and inline anonymous functions), you get this one-liner:
uasort( $inventory, function ($a, $b) { if ( $a==$b ) return 0; else return ($a > $b) ? -1 : 1; });
-
Matej Svajger about 9 years@machineaddict it does maintain the associative keys.
-
machineaddict about 9 years@MatejSvajger: quote from php.net
Associative (string) keys will be maintained, but numeric keys will be re-indexed.
-
Matej Svajger about 9 years@machineaddict hence the associative keys remain intact, or am i reading this wrong?
-
machineaddict about 9 years@MatejSvajger: only the string keys will remain intact
-
Mark Amery about 9 years@AlanPorter
usort
seems more appropriate thanuasort
for sorting an array with sequential numeric keys. Ending up with an array where the first element is at index1
and the second element is at index0
is weird behavior and a sure trap for people who aren't familiar with the details of PHP's arrays;usort
gives you the output you'd intuitively expect. -
Blue over 7 yearsWhile this code snippet may solve the question, including an explanation really helps to improve the quality of your post. Remember that you are answering the question for readers in the future, and those people might not know the reasons for your code suggestion. Please also try not to crowd your code with explanatory comments, as this reduces the readability of both the code and the explanations!
-
lmo over 7 yearsThis answer turned up in the low quality review queue, presumably because you don't provide any explanation of the code. If this code answers the question, consider adding adding some text explaining the code in your answer. This way, you are far more likely to get more upvotes — and help the questioner learn something new.
-
Matthieu over 7 yearsI think you meant array_multisort($inventory, SORT_DESC, $price);
-
Nefelim over 7 years@Chris Baker, you are right. This works only for unique values. But this solution works very fast, so speed was the reason of make and use it. At the moment may be it is not actual, need to test it with PHP 7.1.x.
-
Goose almost 7 years@MarkAmery I prefer answers contained in functions. It encourages copy pasters to use functions and hopefully write less spaghetti code.
-
That Realty Programmer Guy over 6 yearsif you want to maintain numeric index then you're not sorting them lol. to do this however simply do this:
for($i=0,$L=count($myArray); $i<$L; $i++) $newArray[$i +''] = $myArray[$i];
then you can sort it as an associative array and use the sorted $newArray as follows to map back to your original:foreach($newArray as $k => $v) greatFunction( $myArray[ ($k + 0) ] );
the function will get called with the element in proper order, but refering to the original indexed array -
Matt P about 6 yearsVersions PHP 5 >= 5.5.0, PHP 7 for those of you like me that really wanted this to work for them..
-
Pabamato about 6 yearsFor some reason I was not able to make it work with strings having lower/upper letters. Even using the SORT_FLAG_CASE. The following worked for string comparision for me: array_multisort( array_map(strtolower, array_column($ipr_projects, 'Name')), SORT_ASC, $ipr_projects);
-
Mark Amery almost 5 years@Matthieu No, the order written in this answer is correct and the order you suggest would not sort the
$inventory
array. Test it yourself - it's trivial to do so. -
Mark Amery almost 5 yearsRemarkable that
strnatcmp
, meant for comparing strings, seems to work fine here. Apparently the "natural order" it implements includes sorting numerical strings numerically rather than lexically. -
Mark Amery almost 5 years-1; the
cmp
function here is wrong. It's supposed to return "an integer less than, equal to, or greater than zero if the first argument is considered to be respectively less than, equal to, or greater than the second" but instead returnstrue
orfalse
. It seems, remarkably, to nonetheless work - perhaps because the current implementation ofusort
and friends treats the "less than" and "equal to" cases identically - but don't count on it carrying on working in future PHP versions. If they try to make sorts be stable (i.e. not move around equal elements unnecessarily), this will break. -
Mark Amery almost 5 yearsAlso,
usort
would be more appropriate thanuasort
here, sinceuasort
preserves the association between keys and values which is confusing and unexpected when deaing with a sequential numerical array. For instance, the indexes of$array
above after callinguasort
are 2, 0, and 1, in that order. Unless you for some reason want that, you'll probably be more comfortable usingusort
, which reindexes the array as well as reordering it. -
Mark Amery almost 5 yearsYou call this
usortarr
but then calluasort
instead ofusort
; perhaps a bit confusing. The latter is - in the case of a sequential array with numerical indexes, like the one exhibited in the question - probably what you actually want. -
Mark Amery almost 5 yearsThis approach works, but is a fair bit less concise than either Josh Davis's answer (with
array_multisort
) or mine (withusort
) and seems to offer no advantages over them in exchange. -
Plutian about 4 yearsHi and welcome to stackoverflow, and thank you for answering. While this code might answer the question, can you consider adding some explanation for what the problem was you solved, and how you solved it? This will help future readers to understand your answer better and learn from it.
-
mickmackusa almost 4 yearsCode-only answers are low-value on Stack Overflow because they do a poor job of educating/empowering the OP and thousands of future researchers. Furthermore, suggesting techniques that were already provided years earlier does NOT help researchers -- in fact, it wastes their research time because they end up reading more content but gain no new information.
-
mickmackusa almost 4 yearsIn comparison to the other answers posted here, this answer looks more like a "hint" that should be a comment under the question instead of an answer.
-
mickmackusa almost 4 yearsPlease never duplicate the advice of previous posts (especially on the same page). This needlessly bloats Stack Overflow and wastes researchers' time.
-
ddruganov over 3 yearsin php7+: in the cmp function one should use the <=> 'spaceship' operator
-
Ajowi over 2 yearsThis works. I was wondering how to give the outer index values (for many records) but it figures it out by itself; just give the name in array_column then use array_multisort. As for index preservation, numerics are reset but others are maintained.
-
Inderjeet about 2 yearsWorked like a charm.
-
Bang Nguyen almost 2 yearsWorking well PHP 7.4