0

I'm trying to do the same as MySQL query

SELECT * FROM table ORDER BY field1, field2, ...

but with php and a multidimensional array:

$Test = array(
    array("a"=>"004", "n"=>"03"),
    array("a"=>"003", "n"=>"02"),
    array("a"=>"001", "n"=>"02"),
    array("a"=>"005", "n"=>"01"),
    array("a"=>"001", "n"=>"01"),
    array("a"=>"004", "n"=>"02"),
    array("a"=>"003", "n"=>"01"),
    array("a"=>"004", "n"=>"01")
);
function msort(&$array, $keys){
    array_reverse($keys);
    foreach($keys as $key){
        uasort($array, sortByKey);
    }
    //
    function sortByKey($A, $B){

        global $key;

        $a = $A[$key];
        $b = $B[$key];
        if($a==$b) return 0;
        return ($a < $b)? -1 : 1 ;
    }
}
//
msort($Test, array("a","n"));
//
foreach($Test as $t){
    echo('<p>'.$t["a"].'-'.$t["n"].'</p>');
}

My theory is: if I sort multiple times on columns with "lesser importance" then columns of "greater importance", I'll achieve an order like the above MySQL query.

Unfortunately, php is returning:

Warning: uasort() expects parameter 2 to be a valid callback, function 'sortByKey' not found or invalid function name in /Library/WebServer/Documents/www/teste.array_sort.php on line 23" (uasort line)

It's a simple order function. What am I missing?

mickmackusa
  • 33,121
  • 11
  • 58
  • 86
Gustavo
  • 1,541
  • 4
  • 22
  • 35
  • 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 Apr 02 '14 at 13:14
  • Maybe, I'll take a look. – Gustavo Apr 02 '14 at 13:18
  • The reference got a different approach. Also, before close this question that is not really just a duplicate, I think it should be answered why the code doesn't work. – Gustavo Apr 02 '14 at 17:23
  • It doesn't work because you're sorting by *a*, and then you're sorting again from scratch by *b*. You're not refining a sort, you're resorting by something else. Also, that's not how you pass functions as callback. Also, declaring functions within functions does not really work [the way you may think it does]. – deceze Apr 02 '14 at 19:03
  • Sorry, I didn't get your a - b explanation, but you given me a clue, so I could solve it above, using all contributions here and from the reference. And if you see it with some patience, you'll realize that this post is not a duplicate, my answer is different. – Gustavo Apr 02 '14 at 22:41
  • I'm saying if you first do `usort` by key `a` and then again `usort` by key `n`, you're not going to end up with a `ORDER BY a, n`, you'll just end up with the equivalent of `ORDER BY n`. One sort does not "refine" the previous one. – deceze Apr 03 '14 at 07:25

4 Answers4

6

Fundamentally we're going to use the same approach as explained here, we're just going to do it with a variable number of keys:

/**
 * Returns a comparison function to sort by $cmp
 * over multiple keys. First argument is the comparison
 * function, all following arguments are the keys to
 * sort by.
 */
function createMultiKeyCmpFunc($cmp, $key /* , keys... */) {
    $keys = func_get_args();
    array_shift($keys);

    return function (array $a, array $b) use ($cmp, $keys) {
        return array_reduce($keys, function ($result, $key) use ($cmp, $a, $b) {
            return $result ?: call_user_func($cmp, $a[$key], $b[$key]);
        });
    };
}

usort($array, createMultiKeyCmpFunc('strcmp', 'foo', 'bar', 'baz'));
// or
usort($array, createMultiKeyCmpFunc(function ($a, $b) { return $a - $b; }, 'foo', 'bar', 'baz'));

That's about equivalent to an SQL ORDER BY foo, bar, baz.

If of course each key requires a different kind of comparison logic and you cannot use a general strcmp or - for all keys, you're back to the same code as explained here.

Community
  • 1
  • 1
deceze
  • 471,072
  • 76
  • 664
  • 811
  • Well, your answer is more elegant than mine, to say at list. But as long you say is the same approach, you use a lot of different techniques to solve the problem, maybe I have a different understanding of "approach", but your answer is really a nice contribution to this forum. Thanks! – Gustavo Apr 03 '14 at 12:36
  • "Same approach" as in "we'll create one comparison function with which you'll do exactly one sort". As opposed to your initial approach of doing several successive sorts. This answer here just demonstrates a technique to dynamically create that comparison function, nothing more. :) – deceze Apr 03 '14 at 13:05
4

Here's some code I wrote to do something similar:

uasort($array,function($a,$b) {
    return strcmp($a['launch'],$b['launch'])
        ?: strcmp($a['tld'],$b['tld'])
        ?: strcmp($a['sld'],$b['sld']);
});

It kind of abuses the fact that negative numbers are truthy (only zero is falsy) to first compare launch, then tld, then sld. You should be able to adapt this to your needs easily enough.

Niet the Dark Absol
  • 301,028
  • 70
  • 427
  • 540
  • Neat! I linked to it from http://stackoverflow.com/questions/17364127/reference-all-basic-ways-to-sort-arrays-and-data-in-php :) – deceze Apr 02 '14 at 13:18
  • You can, of course, use different `*cmp` functions (`strcasecmp`, `strncmp`, `strncasecmp`, `strnatcmp`, `strnatcasecmp`) depending on the desired result. – Niet the Dark Absol Apr 02 '14 at 13:21
  • Or *any* comparison operation, really. `return $a['foo'] - $b['foo'] ?: $a['bar'] - $b['bar']` – deceze Apr 02 '14 at 13:24
  • @deceze True! So long as the function returns zero for "they are equal", you're all good. – Niet the Dark Absol Apr 02 '14 at 13:28
  • The reference got similar solution. But using a function with unlimited keys we will get a solution for more cases (more generic solution). That's why I put the sql query, you have a database in a string and can sort wherever you need. – Gustavo Apr 02 '14 at 17:28
0

This will do the job, thanks for contributions!

function mdsort(&$array, $keys){

    global $KeyOrder;

    $KeyOrder = $keys;
    uasort($array, cmp);    
}
function cmp(array $a, array $b) {

    global $KeyOrder;

    foreach($KeyOrder as $key){
        $res = strcmp($a[$key], $b[$key]);
        if($res!=0) break;
    }
    return $res;
}
//
mdsort($Test, array("a","n"));

This code is a little ugly, though, I believe it can be better - maybe a class to solve the issue of passing the array with the keys to the "cmp" function. But it's a start point, you can use it with any number of keys to sort.

Gustavo
  • 1,541
  • 4
  • 22
  • 35
0

PHP7.4's arrow syntax eliminates much of the code bloat which was previously necessary to bring your column orders into the usort() scope.

Code: (Demo)

$orderBy = ['a', 'n'];
usort($Test, fn($a, $b) =>
    array_map(fn($v) => $a[$v], $orderBy)
    <=>
    array_map(fn($v) => $b[$v], $orderBy)
);
var_export($Test);

I reckon this is a very elegant and concise way to script the task. You generate the nominated column values from $a and $b as separate arrays and write the spaceship operator between them.

Without the arrow syntax, the snippet gets a little more chunky.

Code: (Demo)

$orderBy = ['a', 'n'];
usort($Test, function($a, $b) use ($orderBy) {
    return 
        array_map(function($v) use ($a){
            return $a[$v];
        }, $orderBy)
        <=>
        array_map(function($v) use ($b){
            return $b[$v];
        }, $orderBy);
});
var_export($Test);

The spaceship operator will walk through corresponding pairs of data ([0] vs [0], then [1] vs [1], and so on) until it reaches a non-zero evaluation or until it exhausts the comparison arrays.

mickmackusa
  • 33,121
  • 11
  • 58
  • 86