3
\$\begingroup\$

I am trying to sort an array of associative arrays in reverse order by the key of the subarrays.

I solved it with a bubble sort algorithm which worked just fine in my test.

I was wondering if there is a better way, maybe a standard PHP method which I am not aware of.

$list = array(
    array(0 => "F"),
    array(1 => "E"),
    array(60 => "B"),
    array(20 => "C"),
    array(14 => "D"),
    array(100 => "A"),
);

$tmp = [];
for($x = 0; $x < sizeof($list); $x++) {
    for($i = 0; $i < sizeof($list); $i++) {
        
        if (!isset($list[$i + 1])) { continue; }

        $nextVal = key($list[$i + 1]);
        $currentVal = key($list[$i]);
        
        if ($nextVal > $currentVal) {
            
            $tmp = $list[$i];
            
            $list[$i] = $list[$i + 1];
            $list[$i + 1] = $tmp;
        }
    }
}

Test it on onlinephp.io

Output:

AFTER:
array(6) {
  [0]=>
  array(1) {
    [100]=>
    string(1) "A"
  }
  [1]=>
  array(1) {
    [60]=>
    string(1) "B"
  }
  [2]=>
  array(1) {
    [20]=>
    string(1) "C"
  }
  [3]=>
  array(1) {
    [14]=>
    string(1) "D"
  }
  [4]=>
  array(1) {
    [1]=>
    string(1) "E"
  }
  [5]=>
  array(1) {
    [0]=>
    string(1) "F"
  }
}
\$\endgroup\$
3
  • \$\begingroup\$ There are fine manuals. \$\endgroup\$
    – greybeard
    Sep 19, 2022 at 15:31
  • 1
    \$\begingroup\$ Alas, the k in the uksort() I first hyperlinked refers to the keys of the outer array $list. To sort by the "inner arrays' key" seems to work using usort($list, fn ($x, $y) => key($x) <=> key($y));. Disclaimer: I never coded PHP (& don't intend to). \$\endgroup\$
    – greybeard
    Sep 19, 2022 at 16:05
  • \$\begingroup\$ I tried it, it works, but the spaceship operator sorts from low to high value. If I use < then it works though. I will add a second answer, thx!! \$\endgroup\$
    – Black
    Sep 19, 2022 at 16:19

3 Answers 3

5
\$\begingroup\$

Short of simplifying your input array structure, using functional sorting will be most direct with array_multisort() which is fed an array of keys to sort in a descending fashion.

Code: (Demo)

array_multisort(array_map('key', $list), SORT_DESC, $list);
var_export($list);

This is a good idea because key() will only be called n times, which will be less than if key() was called twice for every iteration inside of usort() (which is n log n).

Furthermore, it is much easier to read because it explicitly mentions SORT_DESC, but then you (the developer) need to understand that only the last argument inside the sorting function is actually modified. Comparatively, usort() is less expressive/intuitive to the novice developer because you must be aware of the 3-way comparison operator's behavior as well as why $a and $b are written where they are.


If your application's business logic guarantees that the keys within each row will be unique, then you can safely flatten your array and just call krsort() as mentioned elsewhere on this page.

\$\endgroup\$
3
\$\begingroup\$

Bubble sort algorithm - inefficiently implemented

After each iteration of the Bubble sort algorithm "one less element (the last one) is needed to be compared until there are no more elements left to be compared." 1 Thus the nested for loop can be modified so its test expression is $i < sizeof($list) - $x - 1 instead of simply $i < sizeof($list). This leads to half as many comparisons being performed. For the sample data, that means 15 comparisons instead of 30.

Simplifying that test expression it will also allow for the removal of this line:

if (!isset($list[$i + 1])) { continue; } 

Performance considerations

If is a concern, then consider storing sizeof($list) in a variable outside the loops and using that with the comparisons. Doing so will avoid excess function calls to sizeof(). This is likely not an issue for small data sets but would be more noticeable with larger data sets.

Swapping variables

The original code swaps values with the traditional technique of using a temporary variable

      $tmp = $list[$i];
       
      $list[$i] = $list[$i + 1];
      $list[$i + 1] = $tmp;

While this works, it can be simplified using array destructuring assignment (a shorthand of the list() function), available in PHP 7.1+.

[$list[$i], $list[$i + 1]] = [$list[$i + 1], $list[$i]];

See it demonstrated on 3v4l.org

Sorting with simplified dataset

In the V2 array mentioned in your answer the data in $list is simplified to a single level of data. With this type of data the php function krsort() can be used, as it will "Sort an array by key in descending order" 2.

krsort($list);

See it demonstrated on 3v4l.org.

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

I solved it with usort like recommended by @greybeard. Credits to him!

<?php

$list = array(
    array(0 => "F"),
    array(1 => "E"),
    array(60 => "B"),
    array(20 => "C"),
    array(14 => "D"),
    array(100 => "A"),
);

function mysort($a, $b)
{
    return key($a) < key($b);
}

usort($list, "mysort");

var_dump($list);

Test it on onlinephp.io

Output:

Deprecated: usort(): Returning bool from comparison function is deprecated, return an integer less than, equal to, or greater than zero in /home/user/scripts/code.php on line 18
array(6) {
  [0]=>
  array(1) {
    [100]=>
    string(1) "A"
  }
  [1]=>
  array(1) {
    [60]=>
    string(1) "B"
  }
  [2]=>
  array(1) {
    [20]=>
    string(1) "C"
  }
  [3]=>
  array(1) {
    [14]=>
    string(1) "D"
  }
  [4]=>
  array(1) {
    [1]=>
    string(1) "E"
  }
  [5]=>
  array(1) {
    [0]=>
    string(1) "F"
  }
}

V2:

I even improved it later, by reducing my array to make it more uncomplicated:

I solved it with uksort in this case:

<?php

$list = array(
            0 => "F",
            1 => "E",
            60 => "B",
            20 => "C",
            14 => "D",
            100 => "A"
        );

function mysort($a, $b)
{
    return $a < $b;
}

uksort($list, "mysort");

var_dump($list);

Test it on onlinephp.io

Output:

Deprecated: uksort(): Returning bool from comparison function is deprecated, return an integer less than, equal to, or greater than zero in /home/user/scripts/code.php on line 10
array(6) {
  [100]=>
  string(1) "A"
  [60]=>
  string(1) "B"
  [20]=>
  string(1) "C"
  [14]=>
  string(1) "D"
  [1]=>
  string(1) "E"
  [0]=>
  string(1) "F"
}

More improvements:

You can further improve it by using anonymous functions:

<?php

$list = array(
    array(0 => "F"),
    array(1 => "E"),
    array(60 => "B"),
    array(20 => "C"),
    array(14 => "D"),
    array(100 => "A"),
);

usort($list, function ($a, $b) {
    // return key($a) < key($b);  // this will work, but it throws: Deprecated: usort(): Returning bool from comparison function is deprecated, return an integer less than, equal to, or greater than zero
    return key($b) <=> key($a); // use the spaceship operator <=> instead, note that position of $a and $b were swapped.
});

var_dump($list);

Test it on onlinephp.io

\$\endgroup\$
8
  • 1
    \$\begingroup\$ (this is "one level of array nesting less" than in the question. Try defining the comparison function using <=> (spaceship operator). \$\endgroup\$
    – greybeard
    Sep 19, 2022 at 16:02
  • \$\begingroup\$ Yes I changed the array to make it work with uksort, i found no other way. If I use the space ship operator, then it sorts from low to high. \$\endgroup\$
    – Black
    Sep 19, 2022 at 16:09
  • 1
    \$\begingroup\$ If you use <=>, it won't give "Deprecated: usort(): Returning bool from comparison function is deprecated..." and it will handle duplicate values (which the second version won't handle anyway, as you can't have duplicate keys). And even easier for the second version, just use krsort which sorts by key in reverse order. \$\endgroup\$
    – mdfst13
    Sep 19, 2022 at 18:31
  • 1
    \$\begingroup\$ key($a)<=>key($b) vs key($b)<=>key($a) vs -1 * key($a)<=>key($b) \$\endgroup\$
    – slepic
    Sep 20, 2022 at 3:44
  • 1
    \$\begingroup\$ Thanks for posting this code. It's a good idea to summarise which changes you made, and why - a self-answer ought to review the code, just like any other answer. \$\endgroup\$ Sep 20, 2022 at 16:27

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.