查找最接近变量的值的数组名称?

I am attempting to find the closest product to the given budget

$array = array(
    'productname1' => 5,
    'productname2' => 10,
    'productname3' => 15
)

$budget = 12;

I have tried using a function like the following to find the nearest value, but it only returns the number which is closest to the budget rather than the product name.

   function closest($array, $number) {
        sort($array);
        foreach ($array as $a) {
            if ($a >= $number) return $a;
        }
        return end($array);
    }

I can't help but think there is a MUCH better implementation of this. Any help would be much appreciated.

foreach($array as $k => $v){ $diff[abs($v - $budget)] = $k; }
ksort($diff, SORT_NUMERIC);
$closest_key = current($diff);

var_dump($closest_key);         // Product Name
var_dump($array[$closest_key]); // Product Cost

Prints:

    string(12) "productname2"
    int(10)

Or as a function:

function closest($array, $price)
{
    foreach($array as $k => $v){ $diff[abs($v - $price)] = $k; }
    ksort($diff, SORT_NUMERIC);
    $closest_key = current($diff);
    return array($closest_key, $array[$closest_key]);
}

print_r(closest($array, $budget));

Prints:

    Array
    (
        [0] => productname2  // Product Name
        [1] => 10            // Product Price
    )

Both formats include only three steps:

  • Calculate the difference between the product cost and the budget
  • Sort these
  • Take the first element from the sorted array (the element whose price is closest to the budget).

EDIT: If you don't care about anything other than the single closest product, then a sort is overkill and a simple min() function (like Emil used) would be a lot faster. For example:

function closest($array, $price)
{
    foreach($array as $k => $v){ $diff[abs($v - $price)] = $k; }
    $closest_key = $diff[min(array_keys($diff))];
    return array($closest_key, $array[$closest_key]);
}
function closest($array, $number) {
     sort($array);
     foreach ($array as $name => $a) {
         if ($a >= $number) return $name;
     }
     return end(array_keys($array));
 }

The trick comes in on this line:

 foreach ($array as $name => $a) {

Here you assign $name to the array key and $a to the array value. Since you want the name, return $name;

Also, if no match is found, do, end(array_keys($array))); to get the name of the product, otherwise it will just spit out the value, which is not what you want.

You'll want to return the KEY, not the value:

function closest($array, $number) {
    sort($array);
    foreach ($array as $product=>$a) {
        if ($a >= $number) return $product;
    }
    return $product;
}

Here's a functional way of doing it.

  1. Map each post to the difference from the budget.
  2. Find the smallest value.
  3. Filter away all products not adhering to that value.

Implementation:

$diffs = array_map(function ($value) use ($budget) {
                     return abs($value - $budget);
                   }, $array);

$smallest = min($diffs);
$products = array_filter($array,
                         function ($value) use ($budget, $smallest) {
                           return abs($value - $budget) == $smallest;
                         });

$products will now contain all the products which are closest to the budget.