Найти максимально близкое значение

Итак, давайте посмотрим на пару способов того, как можно найти максимально близкое значение в заданном массиве чисел или базе данных. Для начала, разберем вариант на PHP.

function closestNum($arr,$num){
  $res = false;
  sort($arr);
  $less = array_filter($arr, function($a) use ($num){return $a <= $num;});
  $greater = array_filter($arr, function($a) use ($num){return $a > $num;});
  $min = end($less);
  $max = reset($greater);
  $avg = (($min + $max) / 2);
  $res = 'Число '.$num.' - ';
  if($min && $max){
    if($num == $min) {
      $res .= ' равно числу '.$min;
    } else {
      $res .= $num == $avg ? 'ровно между '.$min.' и '.$max : 'ближе к '.($num > $avg ? $max : $min);
    }
  } else {
    $res .= ' ближе всего к '.($min ? $min : $max);
  }
  return $res;
}
// Тестируем
$arr = array(755, 100, 320, 410, 200, 555);
echo closestNum($arr,5); // Число 5 - ближе всего к 100
echo closestNum($arr,320); // Число 320 - равно числу 320
echo closestNum($arr,100.1); // Число 100.1 - ближе к 100
echo closestNum($arr,150); // Число 150 - ровно между 100 и 200
echo closestNum($arr,10000); // Число 10000 - ближе всего к 755

В данном примере, я расписываю ответ функции для наглядности, но а в реальном случае, вам нужно только возвращать само число, которое будет ближе всего к заданному. Теперь давайте пошагово пробежимся по тому, что происходит в этой функции:

  1. Сортируем исходный массив - sort()
  2. Фильтруем массив с помощью функции array_filter(). В первом случае, в переменную "$less" попадут все значения, которые меньше или равны указанному числу "$num", а во втором случае, в переменную "$greater" - только те значения, которые больше числа "$num"
  3. Из первого отфильтрованного массива ("$less"), мы вытаскиваем последний элемент в переменную "$min", а из второго ("$greater") - первый элемент в переменную "$max" с помощью функций end() и reset() соответственно.
  4. Находим среднее арифметическое чисел "$min" и "$max" и присваиваем переменной "$avg", которая нам поможет при сравнении дальше.

Если визуализировать процесс, то получится что-то вроде такого: исходный массив содержит числа "5,2,1,4,3", ищем ближайшее к числу "3", после сортировки массив - "1,2,3,4,5", первый отфильтрованный массив - "1,2,3", второй - "4,5", из первого массива получаем "3", а из второго - "4". Теперь остаётся сравнивать значения, но это, я так думаю, что описывать не нужно ;) Единственное, что нужно отметить - это то, что для версии PHP 5.2 и ниже, функцию array_filter() надо будет записать немного по-другому, т.к. в этих версиях нет поддержки use():

/* ... */
$less = array_filter($arr, create_function('$a, $b='.$num, 'return $a <= $b;'));
$greater = array_filter($arr, create_function('$a, $b='.$num, 'return $a >= $b;'));
/* ... */

Почему так много манипуляций с сравнениями? Причина только в том, что заданное число, может оказаться ровно посередине между двумя значениями в массиве и, если этот момент важен в дальнейших операциях, то приходится использовать и множество сравнений. В противном случае, всю функцию можно записать гораздо короче:

function closestNum($arr,$num){
  $tmp = array();
  foreach($arr as $val){
    $tmp[$val] = abs($val - $num);
  }
  asort($tmp);
  return key($tmp);
}

Мы находим модуль разницы каждого значения массива и заданного числа - abs(), записываем полученное во временный массив "$tmp", где ключами элементов будут сами значения массива, сортируем массив "$tmp" сохраняя ключи - asort() - и возвращаем ключ - key() - первого элемента. В этом случае, если в массиве были значения 100 и 200, а заданное число - 150, то функция нам вернет 100. В остальном, мы получим такой же результат, как и в первом варианте.
А теперь, делая плавный переход, я бы хотел показать интересный способ нахождения ближайшего значения на MySQL, который строится по тому же принципу, как и во втором примере:

SELECT 
  `num` 
FROM 
  `numbers`
ORDER BY ABS(`num` - 99.99) ASC LIMIT 1

"num" - поле с числовыми значениями, "numbers" - имя таблицы, "99.99" - некое заданное число, к которому и ищем ближайшее значение в базе. Функция ABS() в MySQL - идентична одноименной функции в PHP. Думаю, что комментировать дальше не имеет смысла ;)


Данные способы нахождения ближайшего значения может и не претендуют на какую-то оригинальность, но работают исправно и вполне подходят для задач такого плана. Я буду только рад, если вы предложите альтернативные варианты. Ведь лишних способов не бывает и для каждого можно найти своё применение в той или иной ситуации. Оставляйте свои решения в комментариях и они займут достойное место в коллекции, которая пригодится другим ;)

Incode Pro logo

2 комментария

Гость 22.07.2014 22:01
Вот спасибо! Буквально сегодня думал, как это лучше сделать.
Гость 20.10.2016 21:59
Красавчик, спасибо
Ваш комментарий:
X