php sort 2d array by sort order of another 2d array

201 views Asked by At

I have two multi-dimensional arrays and need to sort the first array in the same order as the second array based on different keys (but their values are the same). In the below example I need $allOptions to be sorted into the same order as $regOptions but based on the values of clID == optID.

However, not all $allOptions sub-arrays (clID) are present in $regOptions sub-arrays (optID)....so any non-matched elements in $allOptions would be thrown to the bottom/end of the array.

How can I do this?

$allOptions = array(
     array("clID"=> 171, ...other values),
     array("clID"=> 191, ...other values),
     array("clID"=> 131, ...other values),
     array("clID"=> 101, ...other values),
     array("clID"=> 201, ...other values),
     array("clID"=> 181, ...other values),
     ...
     array("clID"=> 99, ...other values),  // not in regOptions
     array("clID"=> 129, ...other values)  // not in regOptions
     array("clID"=> 139, ...other values)
    
) ;

$regOptions = array(
    array("order"=>1,"optID"=> 131, ...other values),
    array("order"=>2,"optID"=> 191, ...other values),
    array("order"=>3,"optID"=> 181, ...other values),
    array("order"=>4,"optID"=> 139, ...other values),
    array("order"=>5,"optID"=> 101, ...other values),
    array("order"=>6,"optID"=> 201, ...other values),
    array("order"=>7,"optID"=> 171, ...other values) 
    ...
) ;

So the output would be:

$allOptions = array(
     array("clID"=> 131, ...other values),
     array("clID"=> 191, ...other values),
     array("clID"=> 181, ...other values),
     array("clID"=> 139, ...other values)
     array("clID"=> 101, ...other values),
     array("clID"=> 201, ...other values),
     array("clID"=> 171, ...other values),
     ...
     array("clID"=> 99, ...other values),  // not in regOptions
     array("clID"=> 129, ...other values)  // not in regOptions
) ;
4

There are 4 answers

4
mickmackusa On BEST ANSWER

All earlier posted answers are working Waaaaaaaay too hard. Use array_column() to generate a lookup array. Use usort() or array_multisort() to sort by that priority array.

Code: (Demo)

$priority = array_column($regOptions, 'order', 'optID');
usort(
    $allOptions,
    fn($a, $b) => ($priority[$a['clID']] ?? PHP_INT_MAX) <=> ($priority[$b['clID']] ?? PHP_INT_MAX)
);
var_export($allOptions);

If you want to fallback to sorting by clID ASC for all of the unmentioned clId values, then use the elvis operator to break those ties with a simple 3-way comparison on the column value.

$priority = array_column($regOptions, 'order', 'optID');
usort(
    $allOptions,
    fn($a, $b) =>
        ($priority[$a['clID']] ?? PHP_INT_MAX) <=> ($priority[$b['clID']] ?? PHP_INT_MAX)
        ?: $a['clID'] <=> $b['clID']
);

The first script can be performed with array_multisort() in the following way. Note that this approach may perform better than usort() because it makes fewer lookups, but it does sort on $priority, then sort on $allOptions row length, then clId (how it would work if you called sort($allOptions); I imagine that the rows all have the same length though). (Demo)

$priority = array_column($regOptions, 'order', 'optID');
array_multisort(
    array_map(fn($v) => $priority[$v['clID']] ?? PHP_INT_MAX, $allOptions),
    $allOptions
);
var_export($allOptions);
2
Zsoldo On

Use php usort()

Example

    function customSort($a, $b, $regOptions) {
      $aOptID = $a['clID'];
      $bOptID = $b['clID'];
      $aOrder = array_search($aOptID, array_column($regOptions, 'optID'));
      $bOrder = array_search($bOptID, array_column($regOptions, 'optID'));

      if ($aOrder === false) $aOrder = PHP_INT_MAX;

      if ($bOrder === false) $bOrder = PHP_INT_MAX;

      return $aOrder - $bOrder;
    } 

    usort($allOptions, function($a, $b) use ($regOptions) {
      return customSort($a, $b, $regOptions);
    });
1
Olivier On

Here is a solution not based on array_search():

$allOptions = [["clID" => 1], ["clID" => 2], ["clID" => 3], ["clID" => 4]];
$regOptions = [["optID" => 3], ["optID" => 2]];

$order = [];
foreach($regOptions as $key => $option)
    $order[$option['optID']] = $key;

usort($allOptions, function($o1, $o2) use($order)
{
    $id1 = $o1['clID'];
    $id2 = $o2['clID'];
    if(isset($order[$id1]) && isset($order[$id2]))
        return $order[$id1] <=> $order[$id2];
    if(isset($order[$id1]) && !isset($order[$id2]))
        return -1;
    if(!isset($order[$id1]) && isset($order[$id2]))
        return 1;
    return $id1 <=> $id2;
});

var_export($allOptions);

Result:

array (
  0 => 
  array (
    'clID' => 3,
  ),
  1 => 
  array (
    'clID' => 2,
  ),
  2 => 
  array (
    'clID' => 1,
  ),
  3 => 
  array (
    'clID' => 4,
  ),
)
2
WOUNDEDStevenJones On

Another option to sorting would be iterating over $regOptions and building a new array from $indexedOptions, and then appending any items not found to the end. Live example at https://3v4l.org/jd5Rq

<?php

$allOptions = array(
    array("clID"=> 171),
    array("clID"=> 191),
    array("clID"=> 131),
    array("clID"=> 101),
    array("clID"=> 201),
    array("clID"=> 181),
    array("clID"=> 99),  // not in regOptions
    array("clID"=> 129),  // not in regOptions
    array("clID"=> 139),
) ;

$regOptions = array(
    array("order"=>1,"optID"=> 131),
    array("order"=>2,"optID"=> 191),
    array("order"=>3,"optID"=> 181),
    array("order"=>4,"optID"=> 139),
    array("order"=>5,"optID"=> 101),
    array("order"=>6,"optID"=> 201),
    array("order"=>7,"optID"=> 171) 
);

// build new array from original array with indexes for faster lookups
$indexedOptions = [];
foreach($allOptions as $val) {
    $indexedOptions[$val['clID']] = $val;
}

$newArray = [];
foreach ($regOptions as $order) {
    $id = $order['optID'];
    
    if (isset($indexedOptions[$id])) {
        $newArray[] = $indexedOptions[$id];
        unset($indexedOptions[$id]);
    }
}

// at this point, $newArray has all of the found values in order,
// and $indexedOptions has any remaining items.
// var_dump($newArray);
// var_dump($indexedOptions);

// put them together for the final array
$final = array_merge($newArray, $indexedOptions);

var_dump($final);

Output:

array(9) {
  [0]=> array(1) {
    ["clID"]=> int(131) 
  }
  [1]=> array(1) {
    ["clID"]=> int(191) 
  }
  [2]=> array(1) {
    ["clID"]=> int(181) 
  }
  [3]=> array(1) {
    ["clID"]=> int(139) 
  }
  [4]=> array(1) {
    ["clID"]=> int(101) 
  }
  [5]=> array(1) {
    ["clID"]=> int(201) 
  }
  [6]=> array(1) {
    ["clID"]=> int(171) 
  }
  [7]=> array(1) {
    ["clID"]=> int(99) 
  }
  [8]=> array(1) {
    ["clID"]=> int(129) 
  } 
}