0

PHP multidimensional array sorting is a bit confusing to me.

What I have is an array I formed with json_decode() from a .jsonp file.

It has several variables in each primary array entry. They include "Year", "Month", "Day", "hourTimeStart", and "minuteTimeStart", along with some other information.

I'd like to sort this array by date, so I'd like to first sort by "minuteTimeStart", "hourTimeStart", "Day", then "Month", then "Year", so they're in chronological order.

The array looks like this:

Array ( 
[0] => Array ( [Year] => 2013 [Month] => February [Day] => 5 [hourTimeStart] => 5 [minuteTimeStart] => 0 [Name] => tht ) 
[1] => Array ( [Year] => 2013 [Month] => January [Day] => 6 [hourTimeStart] => 2 [minuteTimeStart] => 0 [Name] => gregre) 
[2] => Array ( [Year] => 2013 [Month] => March [Day] => 4 [hourTimeStart] => 1 [minuteTimeStart] => 15 [Name] => gregre)
)

Essentially what I'm doing is this:

$databaseFileURL = "../Appointments/AllAppointmentData.jsonp";
    if(file_exists($databaseFileURL)){
        $jsonAppointmentData = file_get_contents($databaseFileURL);
    } else $jsonAppointmentData = "";
    $AppointmentData = json_decode($jsonAppointmentData, true);

Then I want to sort $AppointmentData by the date indicated in each sub-array

hakre
  • 178,314
  • 47
  • 389
  • 754
JVE999
  • 2,910
  • 8
  • 41
  • 70
  • 2
    This isn't a question. There's no `?`. It's just a list of requirements. You'll probably want to read http://php.net/usort, which is what you'll probably end up using to accomplish your requirements. – Marc B Aug 28 '13 at 20:19
  • possible duplicate of [How do I sort a multidimensional array in php](http://stackoverflow.com/questions/96759/how-do-i-sort-a-multidimensional-array-in-php) - I also removed the two distracting sentences from the start as it always works as already outlined (and we have some pretty good Q&A about the topic here on site) and even the one I linked has a nice comment below the question that is a good pointer to your date problem you face. – hakre Aug 28 '13 at 20:19
  • @hakre I don't think this has to do with multidimensional arrays, the OP doesn't know how to do a custom comparison function to take the details of the value of an array and manipulate it in some way to use it in a custom comparator. – jbx Aug 28 '13 at 20:25
  • @jbx: It is always multi-sort. Instead of comparing all pairs in that array, map the array onto sort-values (one operation per entry), then use multisort with these sort-values. Like outlined in the duplicate. – hakre Aug 28 '13 at 20:29
  • @hakre its not multi-sort, in this case it just happens to be that the values are arrays, but they could just be objects which do not have a natural ordering. The answer you are suggesting is a duplicate uses `array_multisort()` which only takes a few sorting criteria (ascending, descending, natural etc) and not a custom sorting function, as required here (the fields Year, Month, Day, Hour, Minute need to be combined into a value to make it comparable) – jbx Aug 28 '13 at 20:34
  • @jbx: The custom sorting index is to be created - as I just wrote in my last comment. `usort()` can be used as well, however it is complicated and does two things at a time, most often duplicates code inside (especially in cases like these) and when properly implemented uses a mapping function which then could have been already used via `array_map` so every value in the array (which here is an array itself but could be an object as well) is mapped once and then sorted. Instead of being called n/2-1 times per each n too often via a pair comparison function. – hakre Aug 28 '13 at 20:37
  • @hakre Yes of course. It obviously can be implemented in various ways. My point was that this is not a duplicate of the other question. Beginners can find it difficult to bridge their situation to another different one. Here the case was converting custom data to something comparable, while in the other case it was multi-dimensional sorting. If you have more efficient solutions obviously suggest them and say why they are preferred. – jbx Aug 28 '13 at 20:49
  • @jbx: Reading the OPs comments I'm not so sure. I have the feeling he has good understanding about the mapping part and is more looking how to turn that into sort. I can be wrong and actually I like this gets approach from different angles, but with all our discussion here I wonder a bit OP was not able to decide yet which angle to favorize, so it's hard to say where the actual problem lies. – hakre Aug 28 '13 at 21:39

2 Answers2

2

You can use usort() to provide a custom comparison function, and mktime() to build the time (in seconds since Epoch) from your Month, Day, Year, Hour, Minute parameters.

Something like this would solve your problem:

    function myDateSort($ar1, $ar2)
    {
       $month1 = //todo: convert $ar1['Month'] string to the corresponding month number
       $date1 = mktime($ar1['hourTimeStart'], $ar1['minuteTimeStart'], 0,  $month1, $ar1['Day'], $ar1['Year'] );

       $month2 = //todo: convert $ar2['Month'] string to the corresponding month number
       $date2 = mktime($ar2['hourTimeStart'], $ar2['minuteTimeStart'], 0,  $month2, $ar2['Day'], $ar2['Year'] );

       //this will sort ascending, change it to $date2 - $date1 for descending
       return $date1 - $date2;
    }

Then just do:

usort($AppointmentData, "myDateSort");
jbx
  • 18,832
  • 14
  • 73
  • 129
  • I understand what `$ar1` is, but what is `$ar2`? – JVE999 Aug 28 '13 at 20:26
  • `usort` will pass each of your elements into the callback function `myDateSort` so that you can compare them with each other. In order to compare an element with the other you need 2 arguments. So for example $ar1 would be $AppointmentData[0] while $ar2 would be $AppointmentData[1], and you must return a number less than 0 if you consider $ar1 to be less than $ar2 (by comparing their dates). `usort` will take care of calling them one after the other according to its internal sorting algorithm, so you dont need to loop through the elements yourself – jbx Aug 28 '13 at 20:29
  • @jbx: Your explanation is a bit ambigous. The one type of work to be done is to create the mapping function that turns what has been already understood as `$ar1` into a comparable date-time value (e.g. a unix-timestamp for example). Your answer can be confused easily `myDateSort` being that mapping function, but it's not. It is another function that just houses the callback logic for `usort`. Which is related to the mapping, but more making use of it two times. You perhaps should make both more visible. – hakre Aug 28 '13 at 20:41
  • @hakre Why should it be confused with a mapping function, when I provided the links to the functions being used? There is no mention to a mapping function anywhere. I had left the implementation out for the OP to complete it and have a look at the PHP documentation to learn and not have everything spoonfed, now its complete. – jbx Aug 28 '13 at 20:46
  • Let me know if I should post this as a separate question. I'm using a function in a predefined object to convert the month string into a number, but when it's a part of the `myDateSort` function, I get an error that the variable that's assigned to the object does not exist. In this case, that `$CalendarData1` does not exist on the line `$month = $CalendarData1->convertMonthStringtoNumber($ar1['Month']);` It works outside of the function just fine. – JVE999 Aug 28 '13 at 20:47
  • @Jamil: If you look in my added answer (would work for this answer, too, just exemplary), for that mapping function you talk about, in my example you can make use of the so called `use` clause for anonymous functions turning it into a closure: `function(array $stuff) use ($CalendarData1) {`. Hope this helps to get things started. – hakre Aug 28 '13 at 20:49
  • Thanks for the alternative method. It's very clear. I have a question about `use ($CalendarData1)`. When I try to use it with that syntax, I get an `expecting '{'` error... – JVE999 Aug 28 '13 at 20:56
  • @Jamil Most probably `$CalendarData1` is a global variable if it works outside the function. So you need to put `global $CalendarData1;` in the `myDateSort` function to indicate you want to use that global variable. http://php.net/manual/en/function.intval.php – jbx Aug 28 '13 at 21:08
  • @jbx: if you smell global - in case you want to be helpful - always imagine of a way different to global to make the variable accessible. With globalizing something it is always easily possible but keep in mind that global variables are expensive design wise (regardless which programming paradigm you follow) – hakre Aug 28 '13 at 21:41
  • @hakre Well, yes but it wasn't the question I guess, but comment following this. – jbx Aug 28 '13 at 21:52
  • @Jamil if you are converting a string (e.g. February) to a number (e.g. 1), it means your function does not need any object states. So might as well (if you are using PHP 5.3 or later) change your function to `static`. This way you won't need an instance of the class, and you can access the method by the class name `CalendarDate::convertMonthStringToNumber()`. On a separate note avoid global variables as much as possible. They're bad practice and just introduce a spaghetti of dependencies between functions. Try to encapsulate everything and use OO principles, PHP's OO support improved a lot – jbx Aug 28 '13 at 21:56
  • 1
    @jbx: I guess he uses that object as a helper to ship the map from month names to month numerical values (this makes sense if you want to keep things independent to locale). So it needs some object(ive) states. Anyway, I didn't say it explicitly here under your answer: Yes `usort` is a way to solve this problem :) – hakre Aug 28 '13 at 22:20
  • @hakre Mmm, in such a case I think I would prefer to have a Singleton, maybe with the `getInstance()` taking the `locale` parameter so that the instantiation is done once and according to the `locale`. Anyway, besides the point of this question I guess – jbx Aug 29 '13 at 09:34
  • @jbx: A singleton is another word for a global variable in PHP with less flexibility and higher cost, therefore, I would stick to the suggetion to just use the instance instead of global and that global static singleton accessible global static variable that you need for that singleton plus the hidden dependency of the classname of the singleton and so on and so forth. If global is expensive, singleton is beyond insane luxury. – hakre Aug 29 '13 at 09:37
2

First of all you have got an array of "stuff" and you want to sort that "stuff" by date.

To sort something you need to turn it into a number, so that a date in the past is a number lower than a date in the future. One value that has these properties is a UNIX timestamp.

Your stuff has the following properties that constitute stuff's date:

[Year] => 2013  [Month] => February [Day] => 5 
[hourTimeStart] => 5 [minuteTimeStart] => 0 

So you need to create a function that turns stuff's date into a timestamp. As this would be the matter of a different question (and don't worry, this has been answered before so the code is out there), I only mock such a function:

$stuffToTimestamp = function(array $stuff) {
    $timestamp = ... // do whatever needs to be done to turn 
                     // $stuff into a timestamp
    return $timestamp;
}

So now with this function at hand you are able to get a sort-able time-value for each $stuff. This is what you do now:

$sortKeys = array_map($stuffToTimestamp, $AppointmentData);

And then you sort the data:

array_multisort($AppointmentData, $sortKeys);

And that's already. $AppointmentData is now sorted.

hakre
  • 178,314
  • 47
  • 389
  • 754
  • +1 for O(n) calls to `mktime()`. This will be faster than using `usort()` because the function that converts the date will only be used N times, but this approach requires N extra memory for the new $sortKeys array, so depending on the number of items and the 'effort' of the mapping function one should choose one against the other. – jbx Aug 28 '13 at 20:54
  • Yes this is a trade between memory and processor (as often). But with data that has more than one property it is often as well you want to have another sort-key. This is not easily possible with `usort` (it is not stable), so array_multisort allows more flexibility here (with the cost of memory naturally, but that is normally not a problem, processing power is often more important than memory). – hakre Aug 28 '13 at 21:39
  • What stability issues does `usort` have? I think either way works fine. However, I don't know about the stability issues. – JVE999 Aug 28 '13 at 22:42
  • @Jamil: Stabity in the meaning of "Stable Sort Algorithm", see http://en.wikipedia.org/wiki/Category:Stable_sorts - This is from computer science. – hakre Aug 28 '13 at 22:51