-1

I have an associative array:

    Array(

        [110] => Array
            (
                [releaseDate] => 2020-08-15 00:00:00
                [isNewest] => 
            )
        [128] => Array
            (
                [releaseDate] => 2020-08-01 00:00:00
                [isNewest] => 
            )
        [129] => Array
            (
                [releaseDate] => 2020-08-01 00:00:00
                [isNewest] => 
            )
        [130] => Array
            (
                [releaseDate] => 2020-08-01 00:00:00
                [isNewest] => 
            )
        [132] => Array
            (
                [releaseDate] => 2020-08-01 00:00:00
                [isNewest] => 
            )
        [123] => Array
            (
                [releaseDate] => 2020-07-01 00:00:00
                [isNewest] => 
            )
        [124] => Array
            (
                [releaseDate] => 2020-07-01 00:00:00
                [isNewest] => 
            )
        [125] => Array
            (
                [releaseDate] => 2020-07-01 00:00:00
                [isNewest] => 
            )
        [127] => Array
            (
                [releaseDate] => 2020-07-01 00:00:00
                [isNewest] => 
            )
     )

Generally this should be sorted by releaseDate but the elements for which isNewest is true should come first.

I use uasort() to accomplish that:

    uasort($arr, function($a, $b){
        return $a['isNewest'] - $b['isNewest'];
    });

Sometimes isNewest will be true but in this example (and in the data conditions i first discovered this bug) isNewest is false for all entries.

Running the above, this is the result:

Array
    (
        [124] => Array
            (
                [releaseDate] => 2020-07-01 00:00:00
                [isNewest] => 
            )
        [125] => Array
            (
                [releaseDate] => 2020-07-01 00:00:00
                [isNewest] => 
            )
        [127] => Array
            (
                [releaseDate] => 2020-07-01 00:00:00
                [isNewest] => 
            )
        [123] => Array
            (
                [releaseDate] => 2020-07-01 00:00:00
                [isNewest] => 
            )
        [132] => Array
            (
                [releaseDate] => 2020-08-01 00:00:00
                [isNewest] => 
            )
        [128] => Array
            (
                [releaseDate] => 2020-08-01 00:00:00
                [isNewest] => 
            )
        [129] => Array
            (
                [releaseDate] => 2020-08-01 00:00:00
                [isNewest] => 
            )
        [130] => Array
            (
                [releaseDate] => 2020-08-01 00:00:00
                [isNewest] => 
            )

        [110] => Array
            (
                [releaseDate] => 2020-08-15 00:00:00
                [isNewest] => 
            )
    )

The problem is that sorting the array the way i do it, using uasort() seems to reverse the array order. If you look at the above two arrays and check the releaseDate, you will see what i mean.

If isNewest were true for any entries, they'd come first, but the rest of the array order would still end up being reversed.

I seem to have some trouble understanding how the uasort() comparison function works. I tried returning -1 and 1 and even flipped the $a and $b parameters, but to no avail.

What am i doing wrong here? How can i use uasort() properly here, so that the array remains sorted by releaseDate in descending order, but in such a way that entries that have isNewest set to true come first?

Thank You!

SquareCat
  • 5,699
  • 9
  • 41
  • 75

2 Answers2

1

You can use a single uasort call to sort your data, checking first whether the dates are equal (if not, returning that sort result) and then sorting the true values first:

uasort($array, function ($a, $b) {
    // if dates not equal, return result of comparison
    // since dates are in Y-m-d H:i:s format we can compare as strings
    if (($rdcmp = strcmp($a['releaseDate'], $b['releaseDate'])) != 0) return $rdcmp;
    // dates are equal, so sort true values first
    if ($a['isNewest'] && !$b['isNewest']) return -1;
    elseif (!$a['isNewest'] && $b['isNewest']) return 1;
    else return 0;
});
print_r($array);

Demo on 3v4l.org

Or to sort all true values before false, and then sort by releaseDate, you can use this code:

uasort($array, function ($a, $b) {
    // sort true values first
    if ($a['isNewest'] && !$b['isNewest']) return -1;
    elseif (!$a['isNewest'] && $b['isNewest']) return 1;
    // boolean values are equal, so sort by date. 
    // Since dates are in Y-m-d H:i:s format, we can sort as strings
    else return strcmp($a['releaseDate'], $b['releaseDate']);
});

Demo on 3v4l.org

Note that if you want to sort by releaseDate descending you should change

strcmp($a['releaseDate'], $b['releaseDate'])

to

strcmp($b['releaseDate'], $a['releaseDate'])

Note also that if your boolean values are actually true and false, you can simplify the boolean compare to

return $b['isNewest'] - $a['isNewest'];

Otherwise, since PHP treats all sorts of values as true (including negative numbers, which could mess up sorting by a subtraction), it's safer to do the specific compare as I have done in the code block.

Nick
  • 138,499
  • 22
  • 57
  • 95
  • `strcmp(!$b['isNewest'] . $b['releaseDate'], !$a['isNewest'] . $a['releaseDate'])` works fine too :) – SirPilan Sep 21 '20 at 10:21
  • @Pilan that would sort by `isNewest` and then `releaseDate`, but OP wants the opposite. Also `false` interpreted as a string is the empty string `''`, so you'd potentially be comparing part of the `releaseDate` with a `1` (if one `isNewest` was `false` and the other `true`). – Nick Sep 21 '20 at 12:37
  • Yes, you are right i missed that. Here the fixed one: `strcmp(intval($b['isNewest']) . $b['releaseDate'], intval($a['isNewest']) . $a['releaseDate'])`. - `isNewest` come first, as OP wanted. Thats what i assumed, but OP may have meant that `isNewest` has to come first for every group. – SirPilan Sep 21 '20 at 12:54
  • @Pilan the question isn't really clear. I've interpreted it as wanting to sort by `releaseDate`, and for a given `releaseDate`, the entries with `isNewest = true` should come first. – Nick Sep 21 '20 at 12:57
  • Actually, sorry for the confusion, but yes, i need `isNewest` elements to always come first, no matter what. But those entries and all others need to be sorted by releaseDate in a descending order. – SquareCat Sep 22 '20 at 13:06
  • Sorry for being unclear in my question. This is the right answer. The comments were extra helpful! Thank you. – SquareCat Sep 22 '20 at 13:09
  • @SquareCat I've updated the answer with code to sort by the `isNewest` values first as well. – Nick Sep 22 '20 at 13:10
0

I cant reproduce the reversal when every isNewest is set to false.

To get your desired result of first(isNewest) and second(releaseDate) order sort, you can simply use uasort twice - use highest priority sort last.

uasort($input, fn($a, $b) => strtotime($b['releaseDate']) - strtotime($a['releaseDate']));
uasort($input, fn($a, $b) => $b['isNewest'] - $a['isNewest']);

Working example.


You can use array_multisort too in case you dont need the keys. (numeric keys will be lost).

array_multisort(
    array_column($input, 'isNewest'),
    SORT_DESC, // 1 before 0 (true before false)
    array_column($input, 'releaseDate'),
    SORT_DESC,
    $input
);

Working example.


Tip for the future. Use var_export to post data here. So we can simply copy/paste it to use it in code.


In case you want to have the isRelease=true candidates at first place in every sub-group (releaseDate) - flip the uasorts, or array_columns in array_multisort.

SirPilan
  • 4,649
  • 2
  • 13
  • 26
  • 1
    Calling `uasort()` twice just sorts by the second condition, not by both conditions. – Barmar Sep 21 '20 at 00:15
  • @Barmar Sorry no, here the [proof](https://3v4l.org/hCskM). – SirPilan Sep 21 '20 at 09:57
  • 1
    It might work sometimes, but it's not guaranteed to work. PHP sorting is not stable. – Barmar Sep 21 '20 at 12:26
  • @Barmar When i recall right the stability of a sort refers to the elements which have the same values being compared - only for this elements there is no defined rule which should come first. But in this context this seem irrelevant to me, am i missing something? – SirPilan Sep 21 '20 at 13:03
  • That's exactly the point. If you sort the array twice by different criteria, and there are elements that are the same in the second criteria, they won't necessarily stay in the order that the first sort produced. – Barmar Sep 21 '20 at 13:05
  • I see. I've looked up the source, as far as i can tell for `uasort` a stable sort is used: [`php_array_user_compare`](https://github.com/php/php-src/blob/master/ext/standard/array.c#L1019) - more [info](https://github.com/php/php-src/commit/e12b9df05d238ec0f3cf47b28a49a4df1b1e3442) - php8 though :/ – SirPilan Sep 21 '20 at 13:12
  • That's only a few months ago. What version of PHP is it in? Most people probably won't be using that version. – Barmar Sep 21 '20 at 13:29
  • 8 didnt know that, thx for the lesson :) – SirPilan Sep 21 '20 at 13:54