20

If I have a PHP array:

$array

With values:

45,41,40,39,37,31

And I have a variable:

$number = 38;

How can I return the value?:

39

Because that is the closest value to 38 (counting up) in the array?

Regards,

taylor

Someone
  • 894
  • 3
  • 22
  • 43
TaylorMac
  • 8,882
  • 21
  • 76
  • 104
  • No offense meant, but is this a homework question? – jdd May 27 '11 at 03:08
  • 1
    Your description is inconsistent with your question title. Either you're looking whether the number exists and at which index, or you're looking for the closest number. That's quite a difference. – deceze May 27 '11 at 03:12
  • yes i realized that my title is inconsistant. lol no this is not for homework, the reason I need to find this value is because I have objects which are dynamically created from values in a database with a custom attribute 'data-sort', and what I am trying to do is place new objects in order (according to the value of the attribute) by sending an array of the 'data-sort' values from all of the objects from the index page to a PHP page using ajax, and using the jquery .after function to attach it to the correct location – TaylorMac May 27 '11 at 03:16
  • I'm not sure I understand the goal exactly, but this seems like a rather brittle way of doing it. I'd just stuff all objects into the array, then sort the array. Either server-side or client-side... – deceze May 27 '11 at 03:21
  • yes I tried to do that but then you must either load all of the objects again, or detect where the changes are with another function. I am doing it this way because jquery data object is only capturing the code to create new objects when returned – TaylorMac May 27 '11 at 03:23
  • 1
    possible duplicate of [PHP - Nearest value from an array](http://stackoverflow.com/questions/5464919/php-nearest-value-from-an-array) – Gajus Oct 15 '14 at 03:58

11 Answers11

26
<?php
function closest($array, $number) {

    sort($array);
    foreach ($array as $a) {
        if ($a >= $number) return $a;
    }
    return end($array); // or return NULL;
}
?>
quantme
  • 3,609
  • 4
  • 34
  • 49
fardjad
  • 20,031
  • 6
  • 53
  • 68
  • 2
    If you `Copy / Paste` the code, it won't work since I forgot to put a semi-colon at the end of `return end($array)`. Other than that, it works with your input (tested now) :). – fardjad May 27 '11 at 03:30
  • @fardjad Doh. I missed the `sort()` -- +1 –  May 27 '11 at 03:30
  • 4
    This is an O(NLogN) solution at best, this can be performed in O(N). – user183037 May 27 '11 at 03:35
  • how could I echo the value? sorry I am not familiar with what you brilliantly wrote here – TaylorMac May 27 '11 at 03:42
  • 1
    `echo closest($array, 38);` should write 39. – fardjad May 27 '11 at 03:45
  • oh yes that is what I have here, my bad. Working fantastic. thank you very much – TaylorMac May 27 '11 at 03:46
  • 1
    I'm sorry but the function does not return the closest value, consider if your array has 37 and 41 after it and you are seeking for the closest value to 38. This implementation will return 41 (41 - 38 = 3), but you know that that closest value is 37 (38 - 37 = 1) – tonix Nov 25 '14 at 19:09
  • 2
    This function takes an array and a number and returns the closest value in the array that is **greater than or equal** to the specified number. That's what OP asked. – fardjad Nov 25 '14 at 19:31
  • @tonix I had to double check these comments to make sure someone had said this, I am dealing with epochs so this function is use use for me as its not the actual closest value. – Someone Oct 20 '15 at 15:44
10

Here is a high-level process to get the desired results and work for any array data:

  • Filter the array keeping on values greater than or equal to the target and then select the lowest remaining value. This is the "best" value (which may be "nothing" if all the values were less) -- this is O(n)
  • Alternatively, sort the data first and see below -- this is O(n lg n) (hopefully)

Now, assuming that the array is sorted ASCENDING, this approach would work:

  • Loop through the array and find the first element which is larger than or equal to the target -- this is O(n)

And if the array is DESCENDING (as in the post), do as above, but either:

  • Iterate backwards -- this is O(n)
  • Sort it ASCENDING first (see fardjad's answer) -- this is O(n lg n) (hopefully)
  • Iterate forwards but keep a look-behind value (to remember "next highest" if the exact was skipped) -- this is O(n)

Happy coding.

  • 1
    in op's example, 38 is the same distance from 37 as it is from 39. imagine the data set: array(37.5, 38.5, 39.5), and you want the closest value to 38.9. your example would give us 39.5 (lowest value greater than or equal) when what we really want is 38.5, the true "closest" value. – Skwerl Feb 14 '13 at 19:33
7

EDIT typo on array_search

Yo... Seems easy enough. Here's a function

<?php 
$array = array(45,41,40,39,37,31);

   function closest($array, $number){
    #does the array already contain the number?
    if($i = array_search( $number, $array)) return $i;

    #add the number to the array
    $array[] = $number;

    #sort and refind the number
    sort($array);
    $i = array_search($number, $array);

    #check if there is a number above it
    if($i && isset($array[$i+1])) return $array[$i+1];

    //alternatively you could return the number itself here, or below it depending on your requirements
    return null;
}

to Run echo closest($array, 38);

Jason
  • 15,064
  • 15
  • 65
  • 105
  • What if the number is two numbers above it? 3? This is not correctly generalized. –  May 27 '11 at 03:29
  • It has been sorted. It's impossible that the number could be two numbers above the search my friend. That would invalidate it as the numerically closest number – Jason May 27 '11 at 03:34
  • would this work if the number is 31 and the next number above it is 37? – TaylorMac May 27 '11 at 03:35
  • Ahh, clever. Indeed and +1 just for using that logic. However, there is still the issue with the `array_search(...)` if the number does not exist at all in the input, no? –  May 27 '11 at 03:36
  • "Because that is the closest value to 38 (counting up) in the array?" -> that to me says that he's looking for the numerically distance between two numbers, and the one which is the above the current number. So that invalidates any numbers below. By that logic I could remove all the numbers below it... is that not how others read it? if not fair enough, can you help me understand it – Jason May 27 '11 at 03:38
  • OHHHHHHHHHHHHHHHH @pst i had a typo. is that what you were referring too? – Jason May 27 '11 at 03:39
  • this is the least "elegant" solution provided here, but actually comes the closest to finding the true "closest" value. let's say you wanted the closest value to 38.9 is in array(37.5, 38.5, 39.5). this is the only solution that would be headed in the right direction. all of the others would give you 39.5. all you'd have to add to this solution is to measure the distances between $array[i+1] and $array[$i-1] and your target number, and take the one with the smallest difference. – Skwerl Feb 14 '13 at 19:38
  • Thanks for code it helped me out, I didn't think about approaching my problem this way its quite neat. – Someone Oct 21 '15 at 08:41
4

Here's a smaller function that will also return the closest value. Helpful if you don't want to sort the array (to preserve keys).

function closest($array, $number) {
    //does an exact match exist?
    if ($i=array_search($number, $array)) return $i;

    //find closest
    foreach ($array as $match) {
        $diff = abs($number-$match); //get absolute value of difference
        if (!isset($closeness) || (isset($closeness) && $closeness>$diff)) {
            $closeness = $diff;
            $closest = $match;
        }
    }
    return $closest;
}
Dustin
  • 846
  • 8
  • 13
2

Do a linear scan of each number and update two variables and you'll be done.

Python code (performance is O(N), I don't think it's possible to beat O(N)):

def closestNum(numArray, findNum):
    diff = infinity       # replace with actual infinity value
    closestNum = infinity # can be set to any value
    for num in numArray:
        if((num - findNum) > 0 and (num - findNum) < diff):
            diff = num - findNum
            closestNum = num
    return closestNum

Please add null checks as appropriate.

user183037
  • 2,549
  • 4
  • 31
  • 42
1

If you really want the value that's "closest" in distance, even if it's a lesser value, try this, which @Jason gets most of the credit for.

Imagine a scenario when you want the closest number to 38.9 in the following:

$array = array(37.5, 38.5, 39.5);

Most of the solutions here would give you 39.5, when 38.5 is much closer. This solution would only take the next highest value if what you're looking is in the exact middle between two numbers in the array:

function nearest_value($value, $array) {
if (array_search($value, $array)) {
    return $value;
} else {
    $array[] = $value;
    sort($array);
    $key = array_search($value, $array);
    if ($key == 0) { return $array[$key+1]; }
    if ($key == sizeof($array)-1) { return $array[$key-1]; }
    $dist_to_ceil = $array[$key+1]-$value;
    $dist_to_floor = $value-$array[$key-1];
    if ($dist_to_ceil <= $dist_to_floor) {
        return $array[$key+1];
    } else {
        return $array[$key-1];
    }
}
}

What it lacks in elegance, it makes up for in accuracy. Again, much thanks to @Jason.

Skwerl
  • 341
  • 2
  • 8
0

Try this simple PHP function:

<?php
function nearest($number, $numbers) {
    $output = FALSE;
    $number = intval($number);
    if (is_array($numbers) && count($numbers) >= 1) {
        $NDat = array();
        foreach ($numbers as $n)
            $NDat[abs($number - $n)] = $n;
        ksort($NDat);
        $NDat   = array_values($NDat);
        $output = $NDat[0];
    }
    return $output;
}

echo nearest(90, array(0, 50, 89, 150, 200, 250));
?>
Temüjin
  • 15,371
  • 8
  • 35
  • 57
0

I made a shorter function for that:

function nearestNumber($num, $array) {
    if(!in_array($num, $array)) $array[] = $num;
    sort($array);
    $idx = array_search($num, $array);
    if(($array[$idx] -$array[$idx-1]) >= ($array[$idx+1] -$array[$idx])) return $array[$idx+1];
    else return $array[$idx-1];
}

Works great in my case: $array = array(128,160,192,224,256,320); $num = 203 :)

It's taking the nearest number and if there's the same distance between two numbers (like 208 for my example), the next highest number is used.

Dennis98
  • 139
  • 1
  • 12
0

+1 to Jason.

My implementation below, but not as brisk

$array = array(1,2,4,5,7,8,9);

function closest($array, $number) {
    $array = array_flip($array);

    if(array_key_exists($number, $array)) return $number;

    $array[$number] = true;

    sort($array);

    $rendered = array_slice($array, $number, 2, true); 

    $rendered = array_keys($rendered);

    if(array_key_exists(1, $rendered)) return $rendered[1]; 

    return false;
}

print_r(closest($array, 3));
Mwayi
  • 1,623
  • 15
  • 13
0

You could use array_reduce for this, which makes it more functional programming style:

function closest($needle, $haystack) {
    return array_reduce($haystack, function($a, $b) use ($needle) {
        return abs($needle-$a) < abs($needle-$b) ? $a : $b;
    });
}

For the rest, this follows the same principle as the other O(n) solutions.

trincot
  • 317,000
  • 35
  • 244
  • 286
-1

Here is my solution.

$array=array(10,56,78,17,30);
$num=65;
$diff=$num;
$min=$num;

foreach($array as $a){
          if( abs($a-$num)< $diff ){
              $diff=abs($a-$num);
              $min=$a;
          }
}

echo $min;
Rohit Choudhary
  • 2,253
  • 1
  • 23
  • 34