-1

I have an array like this:

$sort_me = array(
   array("file"=>"Desert.jpg"), 
   array("file"=>"Hello.jpg"), 
   array("file"=>"Test.jpg)
)

I want to sort this array based on the file attribute alphabetically. To complicate matters I have an optional array:

$sort_order = array("Test.jpg", "Hello.jpg", "NotFound.jpg")

This array will specify some user defined ordering. These array elements have priority, and will be placed in that order if they are found. Other elements not matching anything in $sort_order should be ordered at the end of the array alphabetically.

In javascript I would use call a sort function with a comparator function that takes 2 elements and returns a number to place the first object ahead or behind the second.

How can I do this with PHP?

Edit

I attempted something and it didn't work. (Modified again to put the logic into one function, and generalize which field to sort by)

<?php
    function sort_priority($sort_me, $sort_order, $field) {
        function compare($a, $b) {
            if ($sort_order) {
                $ai = array_search($a[$field], $sort_order);
                $bi = array_search($b[$field], $sort_order);
                if ($ai !== false && $bi === false) {
                    return -1;
                } else if ($bi !== false && $ai === false) {
                    return 1;
                } else if ($ai !== false && $bi !== false) {
                    return $bi - $ai;
                }
            }
            return $a[$field] < $b[$field] ? -1 : 1;
        }
        usort($sort_me, "compare");
    }

    $sort_order = array("Test.jpg", "Hello.jpg", "NotFound.jpg");
    $sort_me = array(
       array("file"=>"Test.jpg"),
       array("file"=>"Desert.jpg"),
       array("file"=>"Hello.jpg")
    );

    sort_priority($sort_me, $sort_order, "file");
    echo json_encode($sort_me);
?>

This outputs

 Notice: Undefined variable: sort_order in c:\workspace\test.php on line 10

The expected output is

[{"file":"Test.jpg"},{"file":"Hello.jpg"},{"file":"Desert.jpg"}]

I don't know how to get the compare function to properly use the context specific $sort_order function.

Edit

I accepted an answer, but for completeness I wanted to post what I finally ended up with that seemed to work. If anyone wants to post a more elegant solution, I would consider marking it as accepted. But here is what I have:

<?php
    function compare_priority($a, $b) {
        global $g_order, $g_field;
        if ($g_order) {
            $ai = array_search($a[$g_field], $g_order);
            $bi = array_search($b[$g_field], $g_order);
            if ($ai !== false && $bi === false) {
                return -1;
            } else if ($bi !== false && $ai === false) {
                return 1;
            } else if ($ai !== false && $bi !== false) {
                return $ai - $bi;
            }
        }
        return $a[$g_field] < $b[$g_field] ? -1 : 1;
    }

    function sort_priority(&$sort_me, $sort_order, $field) {
        global $g_order, $g_field;
        $g_order = $sort_order;
        $g_field = $field;
        usort($sort_me, "compare_priority");
    }

    $sort_me = array(
       array("file"=>"Z"), 
       array("file"=>"A"), 
       array("file"=>"Y"), 
       array("file"=>"B")
    );
    $sort_order = array("Z", "Y", "C");
    sort_priority($sort_me, $sort_order, "file");
    echo json_encode($sort_me);
?>
codefactor
  • 1,616
  • 2
  • 18
  • 41
  • 2
    Read the documentation about [sort()](http://php.net/sort), [usort()](http://php.net/usort) and derivates. – ComFreek Jan 11 '14 at 21:23
  • So you want the `$sort_me` array to be sorted in the following order: Test.jpg, Hello.jpg, ..., Desert.jpg, ... – Marc Audet Jan 11 '14 at 21:26
  • possible duplicate of [Reference: all basic ways to sort arrays and data in PHP](http://stackoverflow.com/questions/17364127/reference-all-basic-ways-to-sort-arrays-and-data-in-php) – deceze Jan 11 '14 at 21:28
  • @MarcAudet In my example - it would be Test.jpg, Hellp.jpg, Desert.jpg - but nothing in between Hello and Desert - since the output should only consist of what the input array contains, just reordered. – codefactor Jan 11 '14 at 21:29

2 Answers2

1

You can do the same as you would have done with javascript. usort (http://ch2.php.net/manual/en/function.usort.php) allow you to define your custom comparison between elements.

I modified your code and it seems to work :

<?php
    function test() {
        $sort_me = array(
           array("file"=>"Test.jpg"),
           array("file"=>"Desert.jpg"),
           array("file"=>"Hello.jpg")
        );
        global $sort_order;
        $sort_order = array("Test.jpg" , "Hello.jpg", "NotFound.jpg");
        function compare($a, $b) {
            global $sort_order;
            if (is_array($sort_order)) {
                $ai = array_search($a["file"], $sort_order);
                $bi = array_search($b["file"], $sort_order);
                if ($ai !== false && $bi === false) {
                    return -1;
                } else if ($bi !== false && $ai === false) {
                    return 1;
                } else if ($ai !== false && $bi !== false) {
                    return $ai - $bi;
                }
            }
            return $a["file"] < $b["file"] ? -1 : 1;
        }
        usort($sort_me, "compare");
        echo json_encode($sort_me);
    }

    test();
?>
DARK_DUCK
  • 1,727
  • 2
  • 12
  • 22
  • I am new to PHP, can you provide a code example? How would the callable function reference the `$sort_order` array? – codefactor Jan 11 '14 at 21:31
  • I modified my post with a code example attempting your solution which demonstrates my problem of using the `usort` function. – codefactor Jan 11 '14 at 21:54
  • I'm sorry, I had modified my code segment a bit to make the sorting logic and field more generic into a function. – codefactor Jan 11 '14 at 22:00
0

For the cleanest scripting with fewer function calls, use usort() with the spaceship operator.

My technique to follow obeys the 2-step logic (the 1st step containing a condition):

  1. sort by the priority array's index values; if the value is not in the priority array, then use the fallback value (which is higher than the last element's key).
  2. when breaking ties that result from the first rule, sort alphabetically

Sample Inputs:

$sort_me = [
   ["file" => "Desert.jpg"], 
   ["file" => "What.jpg"], 
   ["file" => "Hello.jpg"], 
   ["file" => "Test.jpg"],
   ["file" => "Goodness.jpg"],
];

$sort_order = ["Test.jpg", "Hello.jpg", "NotFound.jpg"];

Processing Code: (Demo)

$lookup = array_flip($sort_order);
$fallback = count($sort_order);

usort($sort_me, function($a, $b) use ($lookup, $fallback) {
    return [$lookup[$a['file']] ?? $fallback, $a['file']]
           <=>
           [$lookup[$b['file']] ?? $fallback, $b['file']];
});
var_export($sort_me);

Output:

array (
  0 => 
  array (
    'file' => 'Test.jpg',
  ),
  1 => 
  array (
    'file' => 'Hello.jpg',
  ),
  2 => 
  array (
    'file' => 'Desert.jpg',
  ),
  3 => 
  array (
    'file' => 'Goodness.jpg',
  ),
  4 => 
  array (
    'file' => 'What.jpg',
  ),
)

From PHP7.4, you can use arrow function syntax to introduce global variables into the custom function's scope without the use() declaration.

usort($sort_me, fn($a, $b) =>
    [$lookup[$a['file']] ?? $fallback, $a['file']]
    <=>
    [$lookup[$b['file']] ?? $fallback, $b['file']]
);
mickmackusa
  • 43,625
  • 12
  • 83
  • 136