glarkou
glarkou

Reputation: 7101

Custom sort multidimensional array using array_multisort()

I have the following array:

$array = [
    'note' => [],
    'year' => ['2011','2010', '2012'],
    'type' => ['conference', 'journal', 'conference'],
];

And I use the following function to sort the array using the field type and another array:

function array_multisort_by_order(array $array, $by, array $order)
{
    $order = array_flip($order);
    $params[] = $array[$by];
    foreach($params[0] as &$v) $v = $order[$v];
    foreach($array as &$v) $params[] = &$v; unset($v);
    call_user_func_array('array_multisort', $params);
    return $array;
}

When I call the following function I get the following error:

$array = array_multisort_by_order($array, 'type', array('conference', 'journal'));

print_r($array['type']);

Error:

Warning: array_multisort(): Array sizes are inconsistent.

I know that arrays are inconsistent. Is there a better function to use?

Please check: codepad

Desired Output:

Array
(
[note] => Array
    (
        [0] => 
        [1] => 
        [2] => 
    )

[year] => Array
    (
        [0] => 2011
        [1] => 2012
        [2] => 2010
    )

[type] => Array
    (
        [0] => conference
        [1] => conference
        [2] => journal
    )

)

Example 2:

Array

$array = [
    'note' => ['test1', 'test2'],
    'year' => ['2011', '2012'],
    'type' => ['conference', 'journal', 'conference'],
];

Desired Result 2

Array
(
[note] => Array
    (
        [0] => test1
        [1] => 
        [2] => tes2
    )

[year] => Array
    (
        [0] => 2011
        [1] => 2012
        [2] => 
    )

[type] => Array
    (
        [0] => conference
        [1] => conference
        [2] => journal
    )

)

Upvotes: 7

Views: 2382

Answers (2)

mickmackusa
mickmackusa

Reputation: 47991

  1. Pad the rows to have consistent element counts.
  2. Generate a lookup array using your custom sorting criteria, then populate the first sorting parameter with these translated values.
  3. Push all rows as reference variables into the payload of sorting arguments.
  4. Sort with array_multisort(). No return is needed because all sorting actions are done via references.

Code: (Demo)

function array_multisort_custom(array &$array, $columnKey, array $order)
{
    // guard clause/condition
    if (!array_key_exists($columnKey, $array)) {
        throw new Exception('Nominated sorting column not found');
    }
    
    // pad rows to consistent size
    $maxCount = max(array_map('count', $array));
    array_walk($array, fn(&$row) => $row = array_pad($row, $maxCount, null));

    // populate first sorting parameter with custom order array
    $priority = array_flip($order);
    $default = count($order);
    foreach ($array[$columnKey] as $v) {
        $params[0][] = $priority[$v] ?? $default;
    }
    
    // assign reference variables to parameter array for all rows
    foreach ($array as &$row) {
        $params[] = &$row;
    }
    
    array_multisort(...$params);
}

array_multisort_custom($array, 'type', ['conference', 'journal']);
var_export($array);

If the input array's first level keys were numeric, the above snippet can be compacted a little more and the last foreach() removed (because numeric keys can be unpacked with the spread operator inside of array_multisort()). Demo or even Demo

Related answer: Sort array using array_multisort() with dynamic number of arguments/parameters/rules/data

Upvotes: 0

Wrikken
Wrikken

Reputation: 70520

OK, so, one of the first solutions that comes to mind is adding in the empty values to make them consistent:

function array_multisort_by_order(array $array, $by, array $order)
{
     $max = max(array_map('count',$array));
    //or, alternatively, depending on input (if there are no 'complete' subarrays):
    //$max = max(array_map(function($arr){return max(array_keys($arr));},$array))+1;

    //ADDITION: negative numeric keys:
    $min = min(array_map(function($arr){return min(array_keys($arr));},$array));
    $width = $max - min(0,$min);

    foreach($array as &$sub){
        // $addin = array_diff_key(array_fill(0,$max,null),$sub);
        // $addin changed for negative keys:
        $addin = array_diff_key(array_combine(range($min,$max),array_fill(0,$width,null)),$sub);
        $sub = $addin + $sub;
        ksort($sub);
    }
    $order = array_flip($order);
    $params[] = $array[$by];
    foreach($params[0] as &$v) $v = $order[$v];
    foreach($array as &$v) $params[] = &$v; unset($v);
    call_user_func_array('array_multisort', $params);
    //no closeures here:
    //foreach($array as &$sub) $sub = array_filter(function($a){return !is_null($a);},$sub);
    $filter = create_function('$a','return !is_null($a);');
    foreach($array as &$sub) $sub = array_filter($sub,$filter);
    return $array;
}

Upvotes: 3

Related Questions