7

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
    )

)
mickmackusa
  • 43,625
  • 12
  • 83
  • 136
glarkou
  • 7,023
  • 12
  • 68
  • 118
  • 3
    Hm, desired output would make things more clear then quessing it from the code. Could you provide that? – Wrikken Jun 19 '12 at 20:31
  • OK, 1 question left: are the subarrays always either empty or of consistent length, or can we expect a subarray with 2 items here, and if so, how do we deal with that? Assume they still match up with the first 2 items from the other subarrays? – Wrikken Jun 19 '12 at 20:40
  • @Wrikken: A working example can be found here: http://codepad.org/mjSBYEyi . Unfortunately, sub-arrays are always inconsistent. I will provide another example. Added another example – glarkou Jun 19 '12 at 20:42
  • OK, so numerical keys matter, got it. – Wrikken Jun 19 '12 at 20:48

2 Answers2

3

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;
}
Wrikken
  • 69,272
  • 8
  • 97
  • 136
  • Yes mate I thought of that. And I did http://codepad.org/4QcAoemv but it is not actually adding the keys to the original array. Additionally is there a way to get rid of those 'empty' values after doing the multisort? – glarkou Jun 19 '12 at 21:22
  • Ugh, codepad is running < 5.3, cannot use closures... Something like this you mean: http://codepad.org/5kMHlRc6 – Wrikken Jun 19 '12 at 21:33
  • Yes that will do the job. If you can check the code I tried to use a simpler method to fill missing keys `foreach ($array['type'] as $k => $v) { foreach($array as $element => $a) { $iterator = $array[$element]; if(!isset($iterator[$k])){ $iterator[$key] = ''; } } }`. This was temporary filling the `$iterator` array and not the original array. Thanks again for your help I will use your solution. I marked it as correct. Can you please edit your answer? – glarkou Jun 19 '12 at 21:38
  • Edited in the final function. – Wrikken Jun 19 '12 at 21:49
  • Thanks mate. A final question. I know that this is stupid but unfortunately I have a key with value `-1` and its now the only value that is causing problems. Is it possible to do something about it with little modification? – glarkou Jun 19 '12 at 21:57
  • 1
    There ya go, I hope you get the idea, if there is as error, feel free to edit the solution it in, I'm off ;) – Wrikken Jun 19 '12 at 22:07
0
  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

mickmackusa
  • 43,625
  • 12
  • 83
  • 136