Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sort multidimensional array by date column, then use other column values if dates are the same

I have a multidimensional array that stores people.

Array (
   id93294 => array (
       Name => "Tom Anderson",
       Birthday => "03/17/1975",
       Hometown => 'St. Louis',
       CurrentLocation => 'Mars'
   ),
   id29349 => (array (
       Name => "Tom Anderson",
       Birthday => "03/17/1975",
       Hometown => 'New York',
       CurrentLocation => 'New York'
   )
)

Kind of like that except with more info for the people, so I want to first sort by birthdays THEN sort by another attribute (if their hometown matches their current location) but once I do the second sort on the array it loses the first sort I did using the birthdays...

How do I sort multiple times without it messing up my previous sorts.

P.S. I am using uasort.

like image 492
Adrian Avatar asked Mar 04 '11 19:03

Adrian


3 Answers

Update

I recently answered this question in a much more capable manner in the "definitive" topic on sorting multidimensional arrays. You can safely skip reading the rest of this answer and directly follow the link for a much more capable solution.

Original answer

The function uasort lets you define your own comparison function. Simply put all the criteria you want inside that.

For example, to sort by birthday and then by name:

function comparer($first, $second) {
    // First see if birthdays differ
    if ($first['birthday'] < $second['birthday']) {
        return -1;
    }
    else if ($first['birthday'] > $second['birthday']) {
        return 1;
    }

    // OK, birthdays are equal. What else?
    if ($first['name'] < $second['name']) {
        return -1;
    }
    else if ($first['name'] > $second['name']) {
        return 1;
    }

    // No more sort criteria. The two elements are equal.
    return 0;
}

I am ignoring the fact that in your example, the birthdays are not in a format that can be ordered by a simple comparison using the operator <. In practice you would convert them to a trivially-comparable format first.

Update: if you think that maintaining a bunch of these multiple-criteria comparers could get ugly real fast, you find me in agreement. But this problem can be solved as any other in computer science: just add another level of abstraction.

I 'll be assuming PHP 5.3 for the next example, in order to use the convenient anon function syntax. But in principle, you could do the same with create_function.

function make_comparer() {
    $criteriaNames = func_get_args();
    $comparer = function($first, $second) use ($criteriaNames) {
        // Do we have anything to compare?
        while(!empty($criteriaNames)) {
            // What will we compare now?
            $criterion = array_shift($criteriaNames);

            // Do the actual comparison
            if ($first[$criterion] < $second[$criterion]) {
                return -1;
            }
            else if ($first[$criterion] > $second[$criterion]) {
                return 1;
            }

        }

        // Nothing more to compare with, so $first == $second
        return 0;
    };

    return $comparer;
}

You could then do:

uasort($myArray, make_comparer('birthday', 'name'));

This example possibly tries to be too clever; in general I don't like to use functions that do not accept their arguments by name. But in this case, the usage scenario is a very strong argument for being too clever.

like image 85
Jon Avatar answered Oct 13 '22 21:10

Jon


Excellent question.

This pseudo-code comes from the definition of the problem you gave and is intended to be the callback function given to uasort. I cannot fill in the details because you've omitted the code you're using; hopefully this leads you on the right track.

function compare(p1, p2):
    if birthdays of p1 and p2 are not the same
        compare by birthday
    else
        compare by hometown

If someone could verify that this is a valid comparison function for a sort algorithm in the comments, I would be grateful.

like image 29
erisco Avatar answered Oct 13 '22 20:10

erisco


Many years later, there is a cleaner, more succinct technique provided from PHP7+ ...the spaceship operator and balanced arrays of "criteria".

Code: (Demo)

uasort($array, function($a, $b) {
    return [strtotime($a['Birthday']), $a['Hometown'] !== $a['CurrentLocation'], $a['Name']]
           <=>
           [strtotime($b['Birthday']), $b['Hometown'] !== $b['CurrentLocation'], $b['Name']];
});
var_export($array);

See the demo link for sample input and the output.

This snippet will sort by:

  1. Birthday ASC then
  2. Hometown === CurrentLocation before not === then
  3. Name ASC

Note: #2 has !== syntax only because false evaluation are treated as 0 and true evaluations are treated as 1.

If you need to change any of the sorting orders for any of the criteria, just swap the $a and $b at the corresponding elements.

like image 29
mickmackusa Avatar answered Oct 13 '22 21:10

mickmackusa