phparraysregexsortingusort

How to implement multiple custom sorting rules based on hyphen-delimited substrings?


I have an array of filenames of this form:

"A - 1.2 - Floor Plan.PDF"

I need to sort the array first by the category at the beginning, in the following order:

1. Category: A
2. Category: ESC
3. Category: C
4. Category: M
5. Category: E
6. Category: P

Then I need to sort the array by the numbers following the category.

Here's an example of the array to be sorted:

$arr[0] = "A - 1.0 - Title Page.PDF";
$arr[1] = "A - 2.2 - Enlarged Floor Plans";
$arr[2] = "A - 2.1.0 - Structural Details.PDF";
$arr[3] = "E - 1.0 - Electrical Title Page.PDF";
$arr[4] = "A - 1.2 - Floor Plan.PDF";
$arr[5] = "P - 1.0 - Plumbing Title Page.PDF";
$arr[6] = "A - 2.1.1 - Structural Details.PDF";
$arr[7] = "C - 1.0 - Civil Title Page.PDF";
$arr[8] = "M - 1.0 - Mechanical Title Page.PDF";
$arr[9] = "ESC - 1.0 - Erosion Control Plan.PDF";

Ideally, this array would then become

$arr[0] = "A - 1.0 - Title Page.PDF";
$arr[1] = "A - 1.2 - Floor Plan.PDF";
$arr[2] = "A - 2.1.0 - Structural Details.PDF";
$arr[3] = "A - 2.1.1 - Structural Details.PDF";
$arr[4] = "A - 2.2 - Enlarged Floor Plans";
$arr[5] = "ESC - 1.0 - Erosion Control Plan.PDF";
$arr[6] = "C - 1.0 - Civil Title Page.PDF";
$arr[7] = "M - 1.0 - Mechanical Title Page.PDF";
$arr[8] = "E - 1.0 - Electrical Title Page.PDF";
$arr[9] = "P - 1.0 - Plumbing Title Page.PDF";

I have the following regular expression for grouping the file names appropriately:

^([A-Z]+?) ?- ?([0-9]+)\.([0-9]+)(\.([0-9]+))?.*$

I want the array sorted by group 1, then by group 2, then by group 3. If Group 5 exists, then sort last by group 5. Ignore group 4.

It may be easier to sort the categories lexicographically. If so, that would be alright; though it would be preferable if they were sorted in the order mentioned above.

Is there any way to do this with PHP?


Solution

  • There is sort function which takes compare method as an argument. You can use it for example like this:

    $order = array('A', 'ESC', 'C', 'M', 'E', 'P'); // order of categories
    $order = array_flip($order); // flip order array, it'll look like: ('A'=>0, 'ESC'=>1, ...)
    
    function cmp($a, $b)
    {
        global $order;
    
        $ma = array();
        $mb = array();
        preg_match('/^([A-Z]+?) ?- ?([0-9]+)\.([0-9]+)(?:\.([0-9]+))?.*$/', $a, $ma);
        preg_match('/^([A-Z]+?) ?- ?([0-9]+)\.([0-9]+)(?:\.([0-9]+))?.*$/', $b, $mb);
    
        if ($ma[1] != $mb[1]) {
            return ($order[$ma[1]] < $order[$mb[1]]) ? -1 : 1;
        }
        if ($ma[2] != $mb[2]) {
            return $ma[2] < $mb[2] ? -1 : 1;
        }
        if ($ma[3] != $mb[3]) {
            return $ma[3] < $mb[3] ? -1 : 1;
        }
        // I've changed a regex a little bit, so the last number is 4th group now
        if (@$ma[4] != @$mb[4]) { 
            return @$ma[4] < @$mb[4] ? -1 : 1;
        }
        return 0;
    }
    usort($arr, "cmp");