Sort a flat, associative array by numeric values, then by non-numeric keys

Viewed 36992

I have an array of string keys with numeric values to be used to create a list of tags with the number of occurrences of each tag resembling this:

$arrTags = [
    'mango' => 2, 
    'orange' => 4, 
    'apple' => 2,
    'banana' => 3
];

I want to display the tags in a list with descending values, then the tag names ascending to produce:

orange (4)  
banana (3) 
apple (2) 
mango (2)

arsort() is not suitable because it will put mango before apple. I'm guessing that usort() may be the way, but I'm not finding a suitable example in the comments on php.net.

8 Answers

Have a look at examples #3: http://php.net/manual/en/function.array-multisort.php

You'll need to create two arrays to use as indexes; one made up of the original array's keys and the other of the original array's values.

Then use multisort to sort by text values (keys of the original array) and then by the numeric values (values of the original array).

SOLVED

After a little experimentation I discovered that array_multisort does the trick nicely:

$tag = array(); 
$num = array();

foreach($arrTags as $key => $value){ 
$tag[] = $key; 
$num[] = $value; 
}

array_multisort($num, SORT_DESC, $tag, SORT_ASC, $arrTags);

:)

Use uksort() to pass the keys into the custom function's scope; within that scope, access the associated value by using the key on the passed in (full) array.

The advantage of this is the time complexity -- this will be more direct than two separate sorting function calls and doesn't require the setup of array_multisort(). Also, array_multisort() will destroy numeric keys (not that the asker's keys are numeric) https://3v4l.org/rQak4.

Although the spaceship (3-way) operator was not available back when this question was asked, it is now and it makes the comparison much easier/cleaner now.

From PHP7.4 and up, the syntax is very concise. (Demo)

uksort($arrTags, fn($a, $b) => [$arrTags[$b], $a] <=> [$arrTags[$a], $b]);

From PHP7.0 - PHP7.3, you must pass in the main array with use(). (Demo)

uksort(
    $arrTags,
    function($a, $b) use ($arrTags) {
        return [$arrTags[$b], $a] <=> [$arrTags[$a], $b];
    }
);

You're thinking too complicated:

ksort($arrTags);
arsort($arrTags);

Now your array is sorted like you want it to.

Note: This technique is only reliable in PHP7 and up: https://3v4l.org/ma7ab

Related