How to identify the index in an array based on descending criterias?

Viewed 60

I have an array which looks e.g. like this:

[
0=>['v'=> 1, 'c1'=>null , 'c2'=>null , 'c3'=>null],
1=>['v'=> 2, 'c1'=>'A'  , 'c2'=>null , 'c3'=>null],
2=>['v'=> 3, 'c1'=>'A'  , 'c2'=>'B'  , 'c3'=>null],
3=>['v'=> 4, 'c1'=>'A'  , 'c2'=>'B'  , 'c3'=> 'C']
]

The keys c1, c2, c3 can be independently filled with either null or with any string.

I get a context input in the form of ['c1'=>'A','c2'=>'B','c3'=>'C'].

How do I determine the index of the row which best fits my input array.

"Best" means (in descending order):

  • If there is an exact fit of all three strings (c1, c2, c3) in context and array return this index. (in this example, this rule would already apply and return 3)
  • If this is not existing, return the index of the row where 2 context strings fit and the remaining one c* is null. (this would apply for a context like ['c1'=>'A', 'c2'=>'B', 'c3'=>'Z'] and return 2)
  • If this is not existing , return the index of the row where 1 string fits and the two remaining c*s are null. (this would apply for a context like ['c1'=>'A', 'c2'=>'Y', 'c3'=>'Z'] and return 1)
  • If this is not existing, return the index of the row where all three c*s are null. (this would apply for a context like ['c1'=>'X', 'c2'=>'Y', 'c3'=>'Z'] and return 0)

I know that there can be entries in my array which theoretically return more than 1 index, but this can be excluded in the way the base array is created.

I started many tries which all ended up in a lot of spaghetti-nested-if-then-else stuff and it feels like there must be a more structural way to achieve this.

4 Answers
<?php

   function find_best($data, $lookfor) {
     $bestmatch = false; // what returns if nothing found
     $bestscore = -1;
     foreach($data as $rowkey => $row) {
       $score = 0;
       // count how many matches
       foreach($lookfor as $k => $v) {
         if (isset($row[$k])) { // not null
           if ($row[$k] == $v) {
             $score++;
           } else { // not null but do not match: abort the search, go to next row
             $score = -1; 
             break;
           }
         }
       }
       if ($score > $bestscore) {
         if ($score == count($lookfor)) return $rowkey; // best result, return immediately
         $bestscore = $score;
         $bestmatch = $rowkey;
       }
     }
     return $bestmatch;
   }

   // Test;

   $data =
[
0=>['v'=> 1, 'c1'=>null , 'c2'=>null , 'c3'=>null],
1=>['v'=> 2, 'c1'=>'A'  , 'c2'=>null , 'c3'=>null],
2=>['v'=> 3, 'c1'=>'A'  , 'c2'=>'B'  , 'c3'=>null],
3=>['v'=> 4, 'c1'=>'A'  , 'c2'=>'B'  , 'c3'=> 'C']
];

   print find_best($data, ['c1'=>'A','c2'=>'B','c3'=>'C']) . PHP_EOL; // 3

   print find_best($data, ['c1'=>'A','c2'=>'B','c3'=>'X']) . PHP_EOL; // 2

   print find_best($data, ['c1'=>'A','c2'=>'X','c3'=>'C']) . PHP_EOL; // 1

For each of your search case, you can use array_filter with an appropriate callback of your own.

// $results will contain all of the elements of $values that have ['cX' => 'myValX']
$results = array_filter(
    $values, // is your big array in which you want to search
    function($value){ // this callback will be called for each element of $values
        if($value['c1'] === 'myVal1' AND $value['c2'] === 'myVal2' and $value['c3'] === 'myVal3'){
            return true; // to have the match element of $values array to be in the returned array
        }
    }
);

you want to write your own function, that could be something like

$referenceArray = [
  0=>['v'=> 1, 'c1'=>null , 'c2'=>null , 'c3'=>null],
  1=>['v'=> 2, 'c1'=>'A'  , 'c2'=>null , 'c3'=>null],
  2=>['v'=> 3, 'c1'=>'A'  , 'c2'=>'B'  , 'c3'=>null],
  3=>['v'=> 4, 'c1'=>'B'  , 'c2'=>'B'  , 'c3'=> 'C']
];
function getBest ( $needle, $reference ) {
  $myBestIndex = null;
  $myBestMetrix = null;
  foreach ( $reference as $index => $values ) {
    $currentMetrix = 0;
    foreach ($values as $key => $value ) {
      if (is_null($value)) continue;
      if ($value == $needle[$key]) $currentMetrix++;
      else {
        $currentMetrix = 0;
        break;
      }
    }
    if ($currentMetrix >= ($myBestMetrix ?? $currentMetrix) {
      $myBestIndex = $index;
      $myBestMetrix = $currentMetrix;
    }
  }
  return $myBestIndex;
}

You must iterate over whole array and count value for each row. You can encapsulate this part of code into the custom function if you want.

$array = [
    0 => ['v' => 1, 'c1' => null, 'c2' => null, 'c3' => null],
    1 => ['v' => 2, 'c1' => 'A', 'c2' => null, 'c3' => null],
    2 => ['v' => 3, 'c1' => 'A', 'c2' => 'B', 'c3' => null],
    3 => ['v' => 4, 'c1' => 'B', 'c2' => 'B', 'c3' => 'C']
];

$input = ['c1' => 'B', 'c2' => 'B', 'c3' => 'C'];

$best = null;

array_walk($array, function ($val, $key) use (&$best, $input) {
    $cur_value = count(array_intersect_assoc($val, $input));
    if (is_null($best) || $cur_value > $best['value']) {
        $best = [
            'index' => $key,
            'value' => $cur_value
        ];
    }
});

print_r($best['index']); //output 3
Related