3
\$\begingroup\$

I am trying to sort an associative array by following this scheme:

  1. Sort by 'qt': positives first, non-positives after. Magnitude does not matter, only sign.
  2. In positive part: sort by sa DESC
  3. In negative part: sort by sa DESC

I managed to have the desired result but by tinkering :

$array = [
    [
        "id" => 1,
        "qt" => 1000,
        "sa" => 50
    ],
    [
        "id" => 2,
        "qt" => 0,
        "sa" => 50
    ],
    [
        "id" => 3,
        "qt" => 0,
        "sa" => 350
    ],
    [
        "id" => 4,
        "qt" => 5000,
        "sa" => 250
    ],
    [
        "id" => 5,
        "qt" => 10,
        "sa" => 9000
    ]
];

$p_qty = array();
$n_qty = array();
foreach ($array as $e) {
    if($e['qt'] <= 0){ array_push($n_qty, $e); }
    else             { array_push($p_qty, $e); }
}
usort($p_qty, function($a, $b){
    return 
        $a['sa'] == $b['sa'] ? (
            $a['sa'] == $b['sa'] ? 0 : (
                $a['qt'] < $b['qt'] ? 1 : -1
            )
        ) : (
            $a['sa'] < $b['sa'] ? 1 : -1
        );
});
usort($n_qty, function($a, $b){
    return 
        $a['sa'] == $b['sa'] ? (
            $a['sa'] == $b['sa'] ? 0 : (
                $a['qt'] < $b['qt'] ? 1 : -1
            )
        ) : (
            $a['sa'] < $b['sa'] ? 1 : -1
        );
});

I get the wanted result: ID [5, 4, 1, 3, 2]

Is there a simple to do the wanted result in a unique usort function ?

\$\endgroup\$
4
  • \$\begingroup\$ So in your example you expect qt values [ 10, 5000, 1000, 0, 0 ] ? These are not sorted. Are you sure your example is valid? \$\endgroup\$ May 12, 2021 at 12:14
  • \$\begingroup\$ Actually, 'sa' must take priority once the 'qt' was sorted. With my code I get two arrays : $p_qty = [ [ id=5, qt=10, sa=5000 ], [ id=4, qt=5000, sa=250 ], [ id=1, qt=1000, sa=50 ] ]; $n_qty = [ [ id=3, qt=0, sa=350 ], [ id=2, qt=0, sa=50 ] ]; And for some improvements, I'd like to get this result : [ [ id=5, qt=10, sa=5000 ], [ id=4, qt=5000, sa=250 ], [ id=1, qt=1000, sa=50 ], [ id=3, qt=0, sa=350 ], [ id=2, qt=0, sa=50 ] ]; \$\endgroup\$
    – aurepito
    May 12, 2021 at 12:22
  • \$\begingroup\$ @AurélienPitaut Can qt value possibly be negative in your business logic? Or when you say "negative", do you more accurately mean "non-positive" (zero). Assuming qt means "quantity", is it possible to have a negative quantity? \$\endgroup\$ May 13, 2021 at 6:00
  • \$\begingroup\$ Yes it's possible to have a negative value (like -1) :') \$\endgroup\$
    – aurepito
    May 14, 2021 at 20:14

3 Answers 3

2
\$\begingroup\$

You are not actually sorting on qt, only using it to separate two sets (positive qt coming first). Only within each set you are actually sorting (on sa). Therefore, the usort() comparator function could look like this:

usort($array, function($a, $b) {
    // split in positive and negative set
   if ($a['qt'] <= 0 && $b['qt'] > 0) return 1; // b before a
   if ($a['qt'] > 0 && $b['qt'] <= 0) return -1; // a before b

   // both a and b are in the same set. sort on `sa` descending
   return $b['sa'] - $a['sa'];
});

\$\endgroup\$
2
\$\begingroup\$

The spaceship operator provides a far simpler technique for implementing multi-condition sorting.

Notice that there are no conditions in my sorting function's body. Arrays on either side of <=> are synchronously compared element-by-element. By checking if qt is larger than zero and using that boolean value, zeros become false and non-zeros become true. Furthermore when sorting booleans in DESC order, true comes before false.

Sort by qt non-zeros before zeros, then sa DESC: (Demo)

usort(
    $array,
    function($a, $b) {
        return [$b['qt'] > 0, $b['sa']] <=> [$a['qt'] > 0, $a['sa']];
    }
);

Same thing from PHP7.4 and higher (Demo)

usort($array, fn($a, $b) => [$b['qt'] > 0, $b['sa']] <=> [$a['qt'] > 0, $a['sa']]);
\$\endgroup\$
0
1
\$\begingroup\$
$array = [

I personally don't like the name $array. I would prefer a name like data if a more specific name is not available.

$p_qty = array();
$n_qty = array();

It's somewhat odd that your first array uses the short syntax [] but these use the older long syntax array(). It would be more consistent to use the short syntax here as well. And of course, shorter.

foreach ($array as $e) {
    if($e['qt'] <= 0){ array_push($n_qty, $e); }
    else             { array_push($p_qty, $e); }
}

A simpler way of doing this would be

$n_qty = array_filter($data, function ($v) { $v['qt'] <= 0; });
$p_qty = array_diff($data, $n_qty);

So the whole algorithm could be

usort($data, function ($a, $b) { return $b['sa'] <=> $a['sa']; });

$p_qty = array_filter($data, function ($v) { return $v['qt'] > 0; });
$data = array_merge($p_qty, array_diff($data, $p_qty));

You could use the newer fn syntax in PHP 7.4 and later. I spend most of my time coding with a requirement of PHP 7.3 compatibility, so I'm not really up on it.

Note: I am not saying that this is better than putting everything in the same comparator function (as the other two current answers are doing). That saves you the intermediate $p_qty array. What I am saying is that this is a simpler way of accomplishing your original approach of breaking into two arrays. PHP has a lot of array manipulation functions that will save iterating over an array. In other circumstances where you want to split an array like this, it will help to know them.

Note that sorting by sa first makes it so that you don't have to sort by it twice (less code). Both subarrays will retain that order. However, sorting twice would be more efficient, as it is quicker to sort two small arrays than one combined array.

In general, it is preferable to do

$n_qty[] = e;

to

array_push($n_qty, e);

when adding a single element to an array. Where array_push shines is when you have to add multiple elements at the same time. It doesn't really matter here unless you are doing this a lot. But the PHP manual recommends avoiding using array_push for single elements, and it may be a good habit to adopt.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.