Sort array using array_multisort() with dynamic number of arguments/parameters/rules/data
Asked Answered
O

6

3

I'm trying to sort any array with array_multisort() and everything is working great. However, based on conditions in my script, I need to change the options.

What I have so far is this:

array_multisort(
    $sort1,
    SORT_ASC,
    $sort2,
    SORT_ASC,
    $sort3,
    SORT_ASC, 
    $arraytosort
);

I would like to write something that will allow a more flexible/dynamic payload of sorting data/rules. Something like this:

$dynamicSort = "$sort1,SORT_ASC,$sort2,SORT_ASC,$sort3,SORT_ASC,";

array_multisort(
    $dynamicSort, 
    $arraytosort
);

How can I feed an unknown number of parameters to array_multisort() and have it modify the $arraytosort array?

Odalisque answered 19/3, 2010 at 2:55 Comment(2)
eval it would be, but really, don't do it. I don't see why you can't do this with a loop and without array_multisort.Yonina
this was just an example... I have a multidimensional array with about 30 points of sort... as far as I know thats what array_multisort is for.Odalisque
S
5

You could try to use call_user_func_array. But I've never tried it on a built-in function before. Here is an example:

$dynamicSort = "$sort1,SORT_ASC,$sort2,SORT_ASC,$sort3,SORT_ASC";
$param = array_merge(explode(",", $dynamicSort), array($arrayToSort))
call_user_func_array('array_multisort', $param)
Steadman answered 19/3, 2010 at 3:23 Comment(4)
I had the same thought and went to try, and it does seem to work on built-in functions (despite the name!)Lasley
hmmm seems like a perfect solutions but I keep getting "Argument #1 is expected to be an array or a sort flag" even though it works normally.Odalisque
Oh, try single quotes instead of double quotes. It may be trying to pass the value of $sort1 instead the name "sort1"Steadman
I'm very curious about how the array data contained in $sort1, $sort2, and $sort3 can possibly be stored as the $dynamicSort string. Please prove that this accepted answer works by providing a runnable online demo link.Reuben
I
3

I had the same problem with this answer: "Argument #1 is expected to be an array or a sort flag"

For anyone having the same problem try this instead:

$dynamicSort = array(&$sort1, SORT_ASC, &$sort2, SORT_ASC, &$sort3, SORT_ASC); 
$param = array_merge($dynamicSort, array(&$arrayToSort));
call_user_func_array('array_multisort', $param);

Note that i have used the reference to my variables "&$" instead of $. This works great in php 5.3 but may cause error in 5.2 due to a bug.

Incardinate answered 28/5, 2013 at 7:32 Comment(0)
C
2

It is important to understand that the array sent to call_user_func_array() must consist only of references; it is not important whether the array itself is passed by reference. I spent the better part of a day troubleshooting this; the fact that the examples on the function page at php.net all used literal arrays led me to this page: php Bug #49353. Problem solved.

This doesn't seem to be very well (or consistently) documented, so here goes....

These DO NOT WORK (PHP 5.3.3):

$multisort_array = array($arr1, SORT_DESC, SORT_STRING, $arr2);      // array of values
call_user_func_array('array_multisort', $multisort_array);           // array passed by value

$multisort_array = array($arr1, SORT_DESC, SORT_STRING, $arr2);      // array of values
call_user_func_array('array_multisort', &$multisort_array);          // array passed by reference

$multisort_array = array(&$arr1, SORT_DESC, SORT_STRING, &$arr2);    // non-constants by reference
call_user_func_array('array_multisort', $multisort_array);           // array passed by value

$multisort_array = array(&$arr1, SORT_DESC, SORT_STRING, &$arr2);    // non-constants by reference
call_user_func_array('array_multisort', &$multisort_array);          // array passed by reference

These DO WORK:

$sort = array('desc' => SORT_DESC, 'string' => SORT_STRING);
$multisort_array = array(&$arr1, &$sort['desc'], &$sort['string'], &$arr2);      // all by reference
call_user_func_array('array_multisort', $multisort_array);                       // array passed by value

$sort = array('desc' => SORT_DESC, 'string' => SORT_STRING);
$multisort_array = array(&$arr1, &$sort['desc'], &$sort['string'], &$arr2);      // all by reference
call_user_func_array('array_multisort', &$multisort_array);                      // array passed by reference
Carillon answered 11/7, 2014 at 15:16 Comment(1)
What is the benefit of making a PHP constant a reference variable? Or do you just like the look of variables with & in front of the dollar sign?Reuben
R
2

From PHP5.6, you can use a variadic technique. Simply push all of your sorting data and sorting logic into an indexed array, then use the splat operator to unpack the parameters into array_multisort(). Be sure to make the array that you wish to modify -- modifiable by reference before pushing it into the parameters array.

The pushing of parameters into $sortingParams below can be written more succinctly as a single declaration, but I think it will be easier to conceptualize this way. These individual pushes would be suitable inside of an iterating process (e.g. foreach()).

For every column of data used to sort the parent array, you may elect to push zero, one, or two additional elements to best signify the sorting logic.

Code: (Demo)

$array = [
    ['number' => 2, 'letter' => 'a', 'price' => 9.99], 
    ['number' => 3, 'letter' => 'b', 'price' => 9.99], 
    ['number' => 1, 'letter' => 'c', 'price' => 9.50],
    ['number' => 1, 'letter' => 'd', 'price' => 10],
    ['number' => 1, 'letter' => 'e', 'price' => 9.99],
];

$sortingParams[] = array_column($array, 'number');  // 1-dimensional
$sortingParams[] = SORT_ASC;                        // this is omittable as well because it is assumed (just for demo)
$sortingParams[] = array_column($array, 'price');   // 1-dimensional
$sortingParams[] = SORT_DESC;
$sortingParams[] = SORT_NUMERIC;                    // this is omittable as well because it is assumed (just for demo)
$sortingParams[] = &$array;                         // this is the actual master array which should be modified

array_multisort(...$sortingParams);                 // unpack with splat operator
var_export($array);

Output:

array (
  0 => 
  array (
    'number' => 1,
    'letter' => 'd',
    'price' => 10,
  ),
  1 => 
  array (
    'number' => 1,
    'letter' => 'e',
    'price' => 9.99,
  ),
  2 => 
  array (
    'number' => 1,
    'letter' => 'c',
    'price' => 9.5,
  ),
  3 => 
  array (
    'number' => 2,
    'letter' => 'a',
    'price' => 9.99,
  ),
  4 => 
  array (
    'number' => 3,
    'letter' => 'b',
    'price' => 9.99,
  ),
)

This technique is super powerful if you have dynamic rules being passed to your process. In my case, I needed to collect filters from my DataTables UI and regenerate the data as a .csv. I merely needed to iterate through DataTable's order data and derive my set of rules - done.

I find this syntax much kinder on the eyes versus call_user_func_array().

Here is a more complex implementation: Sort array of associative arrays on multiple columns using specified sorting rules

Reuben answered 16/6, 2020 at 6:36 Comment(0)
T
1

To add onto the existing answers, just thought I would add a little something. For anyone passing the desired "sort by" as a comma-separated $_POST variable (or any comma-separated variable for that matter):

//$_POST["sort_by"] = "column_A DESC, column_B ASC, columns_C DESC";
$sort_bys = explode(",", $_POST["sort_by"]);
$dynamicSort = array();
foreach($sort_bys as $sort_by){
    $sort_by2 = trim(str_replace('DESC','',$sort_by));
    $direction = (strpos($sort_by, 'DESC') !== false)?SORT_DESC:SORT_ASC;
    $$sort_by2  = array_column($array_to_sort, $sort_by2);
    $dynamicSort[] = &$$sort_by2;
    $dynamicSort[] = $direction;
    $dynamicSort[] = SORT_NUMERIC; //or SORT_STRING or SORT_REGULAR ...
}    
$param = array_merge($dynamicSort, array(&$array_to_sort));
call_user_func_array('array_multisort', $param);
Transcendentalism answered 3/2, 2019 at 19:55 Comment(0)
P
0

I'm trying to do a very similar thing, but as my $sort arrays are also created dynamically, I opted for a slightly different approach: on input I get two arrays $sortColumns and $sortDirections

$sortColumnsArrayColumn = array_map(
    function ($n) use ($mainArray) {
        return array_column($mainArray, $n);
    }, $sortColumns
); //creates "$sort" arrays
                
$columnsAndDirections = array_map(null, $sortColumnsArrayColumn, $sortDirections); // zip both arrays together
$params = array_merge(...$columnsAndDirections); // flatten zipped array
$params[] = &$mainArray; // add reference to the mainArray
array_multisort(...$params);

I still get the warning message though, and not sure how to change that: "Warning: array_multisort(): Argument #2 is expected to be an array or a sort flag"


EDIT: Turned out my $sortDirections array contained strings, not PHP constants. Changing that solved the issue ;)

Pteridology answered 12/10, 2023 at 16:27 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.