72 votes

Trouver une valeur correspondante ou la plus proche dans un tableau

Comment puis-je chercher et trouver, pour une valeur cible donnée, la valeur la plus proche dans un tableau ?

Disons que j'ai ce tableau exemplaire :

array(0, 5, 10, 11, 12, 20)

Par exemple, lorsque je cherche avec la valeur cible 0, la fonction renvoie 0 ; lorsque je cherche avec 3, elle renvoie 5 ; lorsque je cherche avec 14, elle renvoie 12.

2 votes

Je ne sais pas ce que vous voulez faire.

0 votes

Il n'existe pas de fonction intégrée pour ce faire. Vous devrez écrire la vôtre. Vous devriez également envisager de demander à un véritable question dans votre question ; qu'est-ce qui vous pose problème actuellement ?

4 votes

@meagar je pense que la déclaration est très claire, ayant à demandez à une question n'est pas une obligation, n'est-ce pas ?

135voto

Tim Cooper Points 55292

Passez le numéro que vous recherchez comme premier paramètre et le tableau de numéros comme second :

function getClosest($search, $arr) {
   $closest = null;
   foreach ($arr as $item) {
      if ($closest === null || abs($search - $closest) > abs($item - $search)) {
         $closest = $item;
      }
   }
   return $closest;
}

0 votes

Ça marche bien aussi. Et vous l'avez proposé avant RobertPitt ;)

1 votes

Oui, j'étais à moitié endormi, c'est pourquoi j'ai échoué à la première réponse, +1 pour votre approche cependant

0 votes

Excellente réponse ! également facile à convertir pour retourner une clé, merci ;)

20voto

mario Points 76989

Une approche particulièrement paresseuse consiste à demander à PHP de trier le tableau par la distance au nombre recherché :

$num = 3;    
$array = array(0, 5, 10, 11, 12, 20);
$smallest = [];

foreach ($array as $i) {
    $smallest[$i] = abs($i - $num);
}
asort($smallest);
print key($smallest);

17voto

Peter Points 7477

C'est une fonction performante que j'ai écrite pour grands tableaux triés

Testé, la boucle principale ne nécessite que ~20 itérations pour un tableau de 20000 éléments.

Attention, le tableau doit être trié (ascendant) !

define('ARRAY_NEAREST_DEFAULT',    0);
define('ARRAY_NEAREST_LOWER',      1);
define('ARRAY_NEAREST_HIGHER',     2);

/**
 * Finds nearest value in numeric array. Can be used in loops.
 * Array needs to be non-assocative and sorted.
 * 
 * @param array $array
 * @param int $value
 * @param int $method ARRAY_NEAREST_DEFAULT|ARRAY_NEAREST_LOWER|ARRAY_NEAREST_HIGHER
 * @return int
 */
function array_numeric_sorted_nearest($array, $value, $method = ARRAY_NEAREST_DEFAULT) {    
    $count = count($array);

    if($count == 0) {
        return null;
    }    

    $div_step               = 2;    
    $index                  = ceil($count / $div_step);    
    $best_index             = null;
    $best_score             = null;
    $direction              = null;    
    $indexes_checked        = Array();

    while(true) {        
        if(isset($indexes_checked[$index])) {
            break ;
        }

        $curr_key = $array[$index];
        if($curr_key === null) {
            break ;
        }

        $indexes_checked[$index] = true;

        // perfect match, nothing else to do
        if($curr_key == $value) {
            return $curr_key;
        }

        $prev_key = $array[$index - 1];
        $next_key = $array[$index + 1];

        switch($method) {
            default:
            case ARRAY_NEAREST_DEFAULT:
                $curr_score = abs($curr_key - $value);

                $prev_score = $prev_key !== null ? abs($prev_key - $value) : null;
                $next_score = $next_key !== null ? abs($next_key - $value) : null;

                if($prev_score === null) {
                    $direction = 1;                    
                }else if ($next_score === null) {
                    break 2;
                }else{                    
                    $direction = $next_score < $prev_score ? 1 : -1;                    
                }
                break;
            case ARRAY_NEAREST_LOWER:
                $curr_score = $curr_key - $value;
                if($curr_score > 0) {
                    $curr_score = null;
                }else{
                    $curr_score = abs($curr_score);
                }

                if($curr_score === null) {
                    $direction = -1;
                }else{
                    $direction = 1;
                }                
                break;
            case ARRAY_NEAREST_HIGHER:
                $curr_score = $curr_key - $value;
                if($curr_score < 0) {
                    $curr_score = null;
                }

                if($curr_score === null) {
                    $direction = 1;
                }else{
                    $direction = -1;
                }  
                break;
        }

        if(($curr_score !== null) && ($curr_score < $best_score) || ($best_score === null)) {
            $best_index = $index;
            $best_score = $curr_score;
        }

        $div_step *= 2;
        $index += $direction * ceil($count / $div_step);
    }

    return $array[$best_index];
}
  • ARRAY_NEAREST_DEFAULT trouve l'élément le plus proche
  • ARRAY_NEAREST_LOWER trouve l'élément le plus proche qui est INFÉRIEUR
  • ARRAY_NEAREST_HIGHER trouve l'élément le plus proche qui est plus haut

Utilisation :

$test = Array(5,2,8,3,9,12,20,...,52100,52460,62000);

// sort an array and use array_numeric_sorted_nearest
// for multiple searches. 
// for every iteration it start from half of chunk where
// first chunk is whole array
// function doesn't work with unosrted arrays, and it's much
// faster than other solutions here for sorted arrays

sort($test);
$nearest = array_numeric_sorted_nearest($test, 8256);
$nearest = array_numeric_sorted_nearest($test, 3433);
$nearest = array_numeric_sorted_nearest($test, 1100);
$nearest = array_numeric_sorted_nearest($test, 700);

4voto

Wrikken Points 37727
<?php
$arr = array(0, 5, 10, 11, 12, 20);

function getNearest($arr,$var){
    usort($arr, function($a,$b) use ($var){
        return  abs($a - $var) - abs($b - $var);
    });
    return array_shift($arr);
}
?>

0 votes

Cela pourrait fonctionner, mais cela demande plus de traitement que TimCooper.

0 votes

Cela fonctionne, mais en effet, je pense que celle de Tim nécessiterait moins de cycles.

1voto

Gajus Kuizinas Points 4713

La mise en œuvre de Tim suffira la plupart du temps. Néanmoins, pour des raisons de performance, vous pouvez trier la liste avant l'itération et interrompre la recherche lorsque la différence suivante est supérieure à la dernière.

<?php
function getIndexOfClosestValue ($needle, $haystack) {
    if (count($haystack) === 1) {
        return $haystack[0];
    }

    sort($haystack);

    $closest_value_index = 0;
    $last_closest_value_index = null;

    foreach ($haystack as $i => $item) {
        if (abs($needle - $haystack[$closest_value_index]) > abs($item - $needle)) {
            $closest_value_index = $i;
        }

        if ($closest_value_index === $last_closest_value_index) {
            break;
        }
    }
    return $closest_value_index;
}

function getClosestValue ($needle, $haystack) {
    return $haystack[getIndexOfClosestValue($needle, $haystack)];
}

// Test

$needles = [0, 2, 3, 4, 5, 11, 19, 20];
$haystack = [0, 5, 10, 11, 12, 20];
$expectation = [0, 0, 1, 1, 1, 3, 5, 5];

foreach ($needles as $i => $needle) {
    var_dump( getIndexOfClosestValue($needle, $haystack) === $expectation[$i] );
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X