263

I have a number from minus 1000 to plus 1000 and I have an array with numbers in it. Like this:

[2, 42, 82, 122, 162, 202, 242, 282, 322, 362]

I want that the number I've got changes to the nearest number of the array.

For example I get 80 as number I want it to get 82.

Bilesh Ganguly
  • 3,792
  • 3
  • 36
  • 58
noob
  • 8,982
  • 4
  • 37
  • 65

19 Answers19

308

ES5 Version:

var counts = [4, 9, 15, 6, 2],
  goal = 5;

var closest = counts.reduce(function(prev, curr) {
  return (Math.abs(curr - goal) < Math.abs(prev - goal) ? curr : prev);
});

console.log(closest);
Richard Parnaby-King
  • 14,703
  • 11
  • 69
  • 129
Joe Grund
  • 4,449
  • 2
  • 17
  • 9
  • 1
    downside is that it only works if reduce's callback is called from the same scope as the declared vars. Since you can't pass `goal` to reduce, you must reference it from a global scope. – 7yl4r Feb 06 '15 at 15:09
  • 9
    could use a higher order function to do that too. – danp Apr 25 '15 at 19:45
  • 6
    @7yl4r or wrap it in a function? ;) – Dominic May 28 '15 at 10:26
  • 2
    @7yl4r not really... you can use bind to achieve this... ---------- // reducer.js function reducer(goal, prev, curr) { return (Math.abs(curr - goal) < Math.abs(prev - goal) ? curr : prev); } // main.js var counts = [4, 9, 15, 6, 2], goal = 5; counts.reduce(reducer.bind(null, goal)); ---------- I don't know how to put code in the comments hahaha. – Mauricio Soares Jan 22 '16 at 18:12
  • 1
    This iterates over every item which is not optimal if the list is ordered but ok for small lists. Even without a binary search a loop could exit if the next number is further way. – Dominic Dec 19 '19 at 05:21
166

Here's the pseudo-code which should be convertible into any procedural language:

array = [2, 42, 82, 122, 162, 202, 242, 282, 322, 362]
number = 112
print closest (number, array)

def closest (num, arr):
    curr = arr[0]
    foreach val in arr:
        if abs (num - val) < abs (num - curr):
            curr = val
    return curr

It simply works out the absolute differences between the given number and each array element and gives you back one of the ones with the minimal difference.

For the example values:

number = 112  112  112  112  112  112  112  112  112  112
array  =   2   42   82  122  162  202  242  282  322  362
diff   = 110   70   30   10   50   90  130  170  210  250
                         |
                         +-- one with minimal absolute difference.

As a proof of concept, here's the Python code I used to show this in action:

def closest (num, arr):
    curr = arr[0]
    for index in range (len (arr)):
        if abs (num - arr[index]) < abs (num - curr):
            curr = arr[index]
    return curr

array = [2, 42, 82, 122, 162, 202, 242, 282, 322, 362]
number = 112
print closest (number, array)

And, if you really need it in Javascript, see below for a complete HTML file which demonstrates the function in action:

<html>
    <head></head>
    <body>
        <script language="javascript">
            function closest (num, arr) {
                var curr = arr[0];
                var diff = Math.abs (num - curr);
                for (var val = 0; val < arr.length; val++) {
                    var newdiff = Math.abs (num - arr[val]);
                    if (newdiff < diff) {
                        diff = newdiff;
                        curr = arr[val];
                    }
                }
                return curr;
            }
            array = [2, 42, 82, 122, 162, 202, 242, 282, 322, 362];
            number = 112;
            alert (closest (number, array));
        </script>
    </body>
</html>

Now keep in mind there may be scope for improved efficiency if, for example, your data items are sorted (that could be inferred from the sample data but you don't explicitly state it). You could, for example, use a binary search to find the closest item.

You should also keep in mind that, unless you need to do it many times per second, the efficiency improvements will be mostly unnoticable unless your data sets get much larger.

If you do want to try it that way (and can guarantee the array is sorted in ascending order), this is a good starting point:

<html>
    <head></head>
    <body>
        <script language="javascript">
            function closest (num, arr) {
                var mid;
                var lo = 0;
                var hi = arr.length - 1;
                while (hi - lo > 1) {
                    mid = Math.floor ((lo + hi) / 2);
                    if (arr[mid] < num) {
                        lo = mid;
                    } else {
                        hi = mid;
                    }
                }
                if (num - arr[lo] <= arr[hi] - num) {
                    return arr[lo];
                }
                return arr[hi];
            }
            array = [2, 42, 82, 122, 162, 202, 242, 282, 322, 362];
            number = 112;
            alert (closest (number, array));
        </script>
    </body>
</html>

It basically uses bracketing and checking of the middle value to reduce the solution space by half for each iteration, a classic O(log N) algorithm whereas the sequential search above was O(N):

0  1  2   3   4   5   6   7   8   9  <- indexes
2 42 82 122 162 202 242 282 322 362  <- values
L             M                   H  L=0, H=9, M=4, 162 higher, H<-M
L     M       H                      L=0, H=4, M=2, 82 lower/equal, L<-M
      L   M   H                      L=2, H=4, M=3, 122 higher, H<-M
      L   H                          L=2, H=3, difference of 1 so exit
          ^
          |
          H (122-112=10) is closer than L (112-82=30) so choose H

As stated, that shouldn't make much of a difference for small datasets or for things that don't need to be blindingly fast, but it's an option you may want to consider.

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • 2
    @micha, I've added the eqivalent JS code to the answer, it just took a while to change languages from one I was more used to :-) – paxdiablo Dec 21 '11 at 04:46
  • 2
    Poor runtime for this algorithm if you have large datasets. – ylun.ca Apr 21 '15 at 08:14
  • 4
    @ylun.ca, since there's no _explicit_ statement in the question that the data is sorted (the example is sorted but that may be coincidence), you can't get better efficiency than O(n). In any case, for a dataset that size, efficiency is mostly irrelevant. But your point is a valid one so I'll add notes to that effect to hopefully make the answer more complete. – paxdiablo Apr 25 '15 at 23:14
  • 1
    Thank you I wish I could upvote more than once! More answers on stack overflow should have this kind of effort put into them. – Richard Vanbergen Oct 11 '17 at 14:09
  • 1
    The amount of effort you put into this answer is awesome, thank you so much for these diagrams, it makes it easier to understand – visylvius Jun 16 '21 at 18:21
109

ES6 (ECMAScript 2015) Version:

const counts = [4, 9, 15, 6, 2];
const goal = 5;

const output = counts.reduce((prev, curr) => Math.abs(curr - goal) < Math.abs(prev - goal) ? curr : prev);

console.log(output);

For reusability you can wrap in a curry function that supports placeholders (http://ramdajs.com/0.19.1/docs/#curry or https://lodash.com/docs#curry). This gives lots of flexibility depending on what you need:

const getClosest = _.curry((counts, goal) => {
  return counts.reduce((prev, curr) => Math.abs(curr - goal) < Math.abs(prev - goal) ? curr : prev);
});

const closestToFive = getClosest(_, 5);
const output = closestToFive([4, 9, 15, 6, 2]);

console.log(output);
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.20/lodash.min.js"></script>
Erick Petrucelli
  • 14,386
  • 8
  • 64
  • 84
Joe Grund
  • 4,449
  • 2
  • 17
  • 9
25

Working code as below:

var array = [2, 42, 82, 122, 162, 202, 242, 282, 322, 362];

function closest(array, num) {
  var i = 0;
  var minDiff = 1000;
  var ans;
  for (i in array) {
    var m = Math.abs(num - array[i]);
    if (m < minDiff) {
      minDiff = m;
      ans = array[i];
    }
  }
  return ans;
}
console.log(closest(array, 88));
dota2pro
  • 7,220
  • 7
  • 44
  • 79
Umesh Patil
  • 10,475
  • 16
  • 52
  • 80
  • 7
    I would argue this is a better solution because it uses only JavaScript. The accepted answer uses jQuery, which was not mentioned in the original question, and which others looking at this question may not be using. – Sean the Bean Apr 14 '14 at 22:42
  • 1
    If you go searching for the closest number to `5000` in the array `[1, 2, 3]`, you're going to be in for a rude shock. – paxdiablo Nov 16 '20 at 22:16
22

Works with unsorted arrays

While there were some good solutions posted here, JavaScript is a flexible language that gives us tools to solve a problem in many different ways. It all comes down to your style, of course. If your code is more functional, you'll find the reduce variation suitable, i.e.:

  arr.reduce(function (prev, curr) {
    return (Math.abs(curr - goal) < Math.abs(prev - goal) ? curr : prev);
  });

However, some might find that hard to read, depending on their coding style. Therefore I propose a new way of solving the problem:

  var findClosest = function (x, arr) {
    var indexArr = arr.map(function(k) { return Math.abs(k - x) })
    var min = Math.min.apply(Math, indexArr)
    return arr[indexArr.indexOf(min)]
  }

  findClosest(80, [2, 42, 82, 122, 162, 202, 242, 282, 322, 362]) // Outputs 82

Contrary to other approaches finding the minimum value using Math.min.apply, this one doesn't require the input array arr to be sorted. We don't need to care about the indexes or sort it beforehand.

I'll explain the code line by line for clarity:

  1. arr.map(function(k) { return Math.abs(k - x) }) Creates a new array, essentially storing the absolute values of the given numbers (number in arr) minus the input number (x). We'll look for the smallest number next (which is also the closest to the input number)
  2. Math.min.apply(Math, indexArr) This is a legit way of finding the smallest number in the array we've just created before (nothing more to it)
  3. arr[indexArr.indexOf(min)] This is perhaps the most interesting part. We have found our smallest number, but we're not sure if we should add or subtract the initial number (x). That's because we used Math.abs() to find the difference. However, array.map creates (logically) a map of the input array, keeping the indexes in the same place. Therefore, to find out the closest number we just return the index of the found minimum in the given array indexArr.indexOf(min).

I've created a bin demonstrating it.

Jpsy
  • 20,077
  • 7
  • 118
  • 115
Dan Mindru
  • 5,986
  • 4
  • 28
  • 42
  • IMHO, I don't see how the ES6 version of the answer makes it better, except for the use of `const` (what's the point of the arrow function?). Some might argue that it's not even more readable. On top of that, I doubt that ES6 is performant in general atm, keep in mind that we're still running mostly ES5 and using Babel to transpile ES6 code. Lastly, I think the performance argument is ridiculous. I challenge you to benchmark all the methods and see which is the slowest in a browser. I don't know how much benchmarking you did in your life, but by the looks of it - you're in for a surprise. – Dan Mindru Oct 10 '16 at 18:32
  • @noob Just my 2 cents. And don't get me wrong, I don't think the other answers are worst or mine is better, I just want to show another approach. `reduce` is super powerful, but it's not the first thing you learn in JavaScript and some mind find it a bit difficult to grasp (and not only beginners, it's difficult for most devs without a functional background) – Dan Mindru Oct 10 '16 at 18:39
  • 1
    Yeah it's great to see you learning! By the way I only spoke about performance doubts not arguing that it actually performed worse but I ran the numbers for you and your `O(n)` solution performs around 100k ops/sec less then @paxdiablo `O(log n)` on random numbers. When designing an algorithm always sort first they say. (Except if you know what you're doing and you've got benchmarks to back you up.) – noob Oct 10 '16 at 22:23
  • 1
    Props for the simple solution. Works great for my use-case (I don't have the luxury of having a pre-sorted array like noob does.) – jaggedsoft Nov 06 '16 at 08:43
  • 2
    You can make the findClosest return a reduce callback function to make it reusable on all arrays: `const findClosest = goal => (a,b) => Math.abs(a - goal) < Math.abs(b - goal) ? a : b;` – Jakob E Feb 08 '19 at 12:51
  • 1
    **Example:** `[2, 42, 82, 122, 162, 202, 242, 282, 322, 362].reduce(findClosest(80))` – Jakob E Feb 08 '19 at 12:57
14

All of the solutions are over-engineered.

It is as simple as:

const needle = 5;
const haystack = [1, 2, 3, 4, 5, 6, 7, 8, 9];

haystack.sort((a, b) => {
  return Math.abs(a - needle) - Math.abs(b - needle);
})[0];

// 5
kmoser
  • 8,780
  • 3
  • 24
  • 40
Gajus
  • 69,002
  • 70
  • 275
  • 438
  • 2
    This is not efficient at all – bumbeishvili Nov 08 '20 at 15:39
  • 5
    I need to find the closest number in a list of 20k+ numbers, and I need to possibly do it very often (possibly every time a user scores). I also need to do it in two dimensions, so two closests in two very long lists. "Over-engineering" is relative to demands. I'm finding everything here under-engineered. – Kyle Baker Mar 04 '21 at 00:14
  • @KyleBaker That's fine, if one know that your list could be huge, one should look for a performative implementation. If list is not huge, there's no need to over-engineer or prematurely optimize. This answer will serve mostly needs for sure. And there's a concern in not sorting original array, just dup it: `[...haystack].sort(...` – Andre Figueiredo Feb 14 '23 at 16:14
13

For sorted arrays (linear search)

All answers so far concentrate on searching through the whole array. Considering your array is sorted already and you really only want the nearest number this is probably the easiest (but not fastest) solution:

var a = [2, 42, 82, 122, 162, 202, 242, 282, 322, 362];
var target = 90000;

/**
 * Returns the closest number from a sorted array.
 **/
function closest(arr, target) {
  if (!(arr) || arr.length == 0)
    return null;
  if (arr.length == 1)
    return arr[0];

  for (var i = 1; i < arr.length; i++) {
    // As soon as a number bigger than target is found, return the previous or current
    // number depending on which has smaller difference to the target.
    if (arr[i] > target) {
      var p = arr[i - 1];
      var c = arr[i]
      return Math.abs(p - target) < Math.abs(c - target) ? p : c;
    }
  }
  // No number in array is bigger so return the last.
  return arr[arr.length - 1];
}

// Trying it out
console.log(closest(a, target));

Note that the algorithm can be vastly improved e.g. using a binary tree.

Hubert Grzeskowiak
  • 15,137
  • 5
  • 57
  • 74
  • While this strategy here is good, this has several typos. Example, `a[i]` or `i[0]`. – Wesley Workman Mar 09 '17 at 16:10
  • 1
    Thanks, @WesleyWorkman. Just fixed them. I hope I got all. By the way, you can also edit others' answers. – Hubert Grzeskowiak Mar 25 '17 at 08:14
  • Where I need to make the correction in above answered code to get the higher closest value? Example: [110, 111, 120, 140, 148, 149, 155, 177, 188, 190] If I search 150, I should get 155 not 149. I tried but finding difficulty in solving this. Could you please help. Thanks – user1199842 Jan 03 '18 at 14:44
  • You say this is probably the fastest, and then say if can be vastly improved. – Kyle Baker Mar 04 '21 at 00:12
10

ES6

Works with sorted and unsorted arrays

Numbers Integers and Floats, Strings welcomed

/**
 * Finds the nearest value in an array of numbers.
 * Example: nearestValue(array, 42)
 * 
 * @param {Array<number>} arr
 * @param {number} val the ideal value for which the nearest or equal should be found
 */
const nearestValue = (arr, val) => arr.reduce((p, n) => (Math.abs(p) > Math.abs(n - val) ? n - val : p), Infinity) + val

Examples:

let values = [1,2,3,4,5]
console.log(nearestValue(values, 10)) // --> 5
console.log(nearestValue(values, 0)) // --> 1
console.log(nearestValue(values, 2.5)) // --> 2

values = [100,5,90,56]
console.log(nearestValue(values, 42)) // --> 56

values = ['100','5','90','56']
console.log(nearestValue(values, 42)) // --> 56

Community
  • 1
  • 1
Sébastien
  • 1,749
  • 1
  • 15
  • 19
7

This solution uses ES5 existential quantifier Array#some, which allows to stop the iteration, if a condition is met.

Opposit of Array#reduce, it does not need to iterate all elements for one result.

Inside the callback, an absolute delta between the searched value and actual item is taken and compared with the last delta. If greater or equal, the iteration stops, because all other values with their deltas are greater than the actual value.

If the delta in the callback is smaller, then the actual item is assigned to the result and the delta is saved in lastDelta.

Finally, smaller values with equal deltas are taken, like in the below example of 22, which results in 2.

If there is a priority of greater values, the delta check has to be changed from:

if (delta >= lastDelta) {

to:

if (delta > lastDelta) {
//       ^^^ without equal sign

This would get with 22, the result 42 (Priority of greater values).

This function needs sorted values in the array.


Code with priority of smaller values:

function closestValue(array, value) {
    var result,
        lastDelta;

    array.some(function (item) {
        var delta = Math.abs(value - item);
        if (delta >= lastDelta) {
            return true;
        }
        result = item;
        lastDelta = delta;
    });
    return result;
}

var data = [2, 42, 82, 122, 162, 202, 242, 282, 322, 362];

console.log(21, closestValue(data, 21)); // 2
console.log(22, closestValue(data, 22)); // 2  smaller value
console.log(23, closestValue(data, 23)); // 42
console.log(80, closestValue(data, 80)); // 82

Code with priority of greater values:

function closestValue(array, value) {
    var result,
        lastDelta;

    array.some(function (item) {
        var delta = Math.abs(value - item);
        if (delta > lastDelta) {
            return true;
        }
        result = item;
        lastDelta = delta;
    });
    return result;
}

var data = [2, 42, 82, 122, 162, 202, 242, 282, 322, 362];

console.log(21, closestValue(data, 21)); //  2
console.log(22, closestValue(data, 22)); // 42 greater value
console.log(23, closestValue(data, 23)); // 42
console.log(80, closestValue(data, 80)); // 82
Emma
  • 27,428
  • 11
  • 44
  • 69
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
7

Other answers suggested the you would need to iterate through the entire array:

  • calculate the deviation for each element
  • keep track of the smallest deviation and its element
  • finally, after iterating through the entire array, return that element with that smallest deviation.

If the array is already sorted, that does not make sense. There is no need to calculate all deviations. e.g. in an ordered collection of 1 million elements, you only need to calculate ~19 deviations (at most) to find your match. You can accomplish this with a binary-search approach:

function findClosestIndex(arr, element) {
    let from = 0, until = arr.length - 1
    while (true) {
        const cursor = Math.floor((from + until) / 2);
        if (cursor === from) {
            const diff1 = element - arr[from];
            const diff2 = arr[until] - element;
            return diff1 <= diff2 ? from : until;
        }

        const found = arr[cursor];
        if (found === element) return cursor;

        if (found > element) {
            until = cursor;
        } else if (found < element) {
            from = cursor;
        }
    }
}

Result:

console.log(findClosestIndex([0, 1, 2, 3.5, 4.5, 5], 4));
// output: 3

console.log(findClosestIndex([0, 1, 2, 3.49, 4.5, 5], 4));
// output: 4

console.log(findClosestIndex([0, 1, 2, 3.49, 4.5, 5], 90));
// output: 5

console.log(findClosestIndex([0, 1, 2, 3.49, 4.5, 5], -1));
// output: 0
bvdb
  • 22,839
  • 10
  • 110
  • 123
  • 2
    would love to know why this was downvoted; - I tried to improve the answer explaining why this is a superior answer. – bvdb Jul 14 '21 at 09:27
  • 1
    THANK YOU! From someone who's unfamiliar with data structures and algorithms, but needing to do a big search through a sorted array... this was supremely helpful. It's much faster than the other naive algorithms. I couldn't figure out how to implement this on my own, but your sample worked flawlessly. Thank you!! – i-know-nothing Jul 01 '22 at 21:59
3

A simpler way with O(n) time complexity is to do this in one iteration of the array. This method is intended for unsorted arrays.

Following is a javascript example, here from the array we find the number which is nearest to "58".

var inputArr = [150, 5, 200, 50, 30];
var search = 58;
var min = Math.min();
var result = 0;
for(i=0;i<inputArr.length;i++) {
  let absVal = Math.abs(search - inputArr[i])
  if(min > absVal) {
    min=absVal;
    result = inputArr[i];
  }
}
console.log(result); //expected output 50 if input is 58

This will work for positive, negative, decimal numbers as well.

Math.min() will return Infinity.

The result will store the value nearest to the search element.

varad_s
  • 764
  • 1
  • 12
  • 24
2

My answer to a similar question is accounting for ties too and it is in plain Javascript, although it doesn't use binary search so it is O(N) and not O(logN):

var searchArray= [0, 30, 60, 90];
var element= 33;

function findClosest(array,elem){
    var minDelta = null;
    var minIndex = null;
    for (var i = 0 ; i<array.length; i++){
        var delta = Math.abs(array[i]-elem);
        if (minDelta == null || delta < minDelta){
            minDelta = delta;
            minIndex = i;
        }
        //if it is a tie return an array of both values
        else if (delta == minDelta) {
            return [array[minIndex],array[i]];
        }//if it has already found the closest value
        else {
            return array[i-1];
        }

    }
    return array[minIndex];
}
var closest = findClosest(searchArray,element);

https://stackoverflow.com/a/26429528/986160

Community
  • 1
  • 1
Michail Michailidis
  • 11,792
  • 6
  • 63
  • 106
1

I don't know if I'm supposed to answer an old question, but as this post appears first on Google searches, I hoped that you would forgive me adding my solution & my 2c here.

Being lazy, I couldn't believe that the solution for this question would be a LOOP, so I searched a bit more and came back with filter function:

var myArray = [2, 42, 82, 122, 162, 202, 242, 282, 322, 362];
var myValue = 80;

function BiggerThan(inArray) {
  return inArray > myValue;
}

var arrBiggerElements = myArray.filter(BiggerThan);
var nextElement = Math.min.apply(null, arrBiggerElements);
alert(nextElement);

That's all !

  • 2
    So what's about the lower bound? You always use the next higher value. But your approach reminds me of `goog.math.clamp` (google closure) just with arrays and without taking care about the lower bound. – noob Apr 06 '14 at 08:55
  • 1
    Your solution returns the next higher number from the array. Neither the nearest nor the exact value are found. – Hubert Grzeskowiak Aug 01 '14 at 22:44
1

I like the approach from Fusion, but there's a small error in it. Like that it is correct:

    function closest(array, number) {
        var num = 0;
        for (var i = array.length - 1; i >= 0; i--) {
            if(Math.abs(number - array[i]) < Math.abs(number - array[num])){
                num = i;
            }
        }
        return array[num];
    }

It it also a bit faster because it uses the improved for loop.

At the end I wrote my function like this:

    var getClosest = function(number, array) {
        var current = array[0];
        var difference = Math.abs(number - current);
        var index = array.length;
        while (index--) {
            var newDifference = Math.abs(number - array[index]);
            if (newDifference < difference) {
                difference = newDifference;
                current = array[index];
            }
        }
        return current;
    };

I tested it with console.time() and it is slightly faster than the other function.

Jon
  • 188
  • 1
  • 9
  • Hey, can you explain why it is an `improved for loop`? Reversed loops aren't always a performance improvement. – A1rPun Mar 01 '16 at 15:43
  • You evaluate `.length` only once, when you declare `i`, whereas for this loop. But I think `var i = arr.length;while (i--) {}` would be even faster – Jon Mar 03 '16 at 07:34
  • I updated my answer. I changed it to `while`. Now it's even faster. – Jon Mar 03 '16 at 07:44
1

The most efficient would be a binary search. However even simple solutions can exit when the next number is a further match from the current. Nearly all solutions here are not taking into account that the array is ordered and iterating though the whole thing :/

const closest = (orderedArray, value, valueGetter = item => item) =>
  orderedArray.find((item, i) =>
    i === orderedArray.length - 1 ||
    Math.abs(value - valueGetter(item)) < Math.abs(value - valueGetter(orderedArray[i + 1])));

var data = [2, 42, 82, 122, 162, 202, 242, 282, 322, 362];

console.log('21 -> 2', closest(data, 21) === 2);
console.log('22 -> 42', closest(data, 22) === 42); // equidistant between 2 and 42, select highest
console.log('23 -> 42', closest(data, 23) === 42);
console.log('80 -> 82', closest(data, 80) === 82);

This can be run on non-primitives too e.g. closest(data, 21, item => item.age)

Change find to findIndex to return the index in the array.

Dominic
  • 62,658
  • 20
  • 139
  • 163
  • What about unordered array? – EdG May 09 '20 at 11:45
  • For unordered take one of the solutions which does not exit, however if you are doing the work many times over then it might be more optimal to sort the array once. As mentioned if the array is really large then a binary search will be more efficient than a linear one. – Dominic May 09 '20 at 13:15
  • 1
    If you have an array of 1.000.000 objects, a binary search could find any number within 19 compares. However, the solution presented here would need 500.000 on average, and a maximum of 1.000.000. Yes, it's a performance tweak, but I don't think the complexity of the code justifies it. - Having said that, I doubt the `Math.abs` is really necessary in your solution. If anything, I think it breaks your function for arrays which have both positive and negative numbers. – bvdb Jul 14 '21 at 09:50
1

If the array is sorted like in your example, you can use a Binary Search for a better time complexity of O(log n).

const myArray = [2, 42, 82, 122, 162, 202, 242, 282, 322, 362];

const binaryClosestIdx = (arr, target) => {
    let start = 0;
    let end = arr.length - 1;
    let mid = Math.floor((start + end) / 2);

    while (1) {
        if (arr[mid] === target) {
            return mid;
        }
        else if (start >= end) {
            break;
        }
        else if (arr[mid] > target) {
            end = mid - 1;
        } else {
            start = mid + 1;
        }
        
        mid = Math.floor((start + end) / 2);
    }

    // Return the closest between the last value checked and it's surrounding neighbors
    const first = Math.max(mid - 1, 0);
    const neighbors = arr.slice(first, mid + 2);
    const best = neighbors.reduce((b, el) => Math.abs(el - target) < Math.abs(b - target) ? el : b);

    return first + neighbors.indexOf(best);
}

const closestValue = myArray[binaryClosestIdx(myArray, 80)];
console.log(closestValue);

How it works :

  1. It compares the target value to the middle element of the array. If the middle element is bigger we can ignore every element after it as they are going to be even bigger. The same goes if the middle element is smaller, we can ignore every element before it.

  2. If the target value is found we return it, otherwise we compare the last value tested with its surrounding neighbors as the closest value can only be between those 3 values.

DevAb
  • 530
  • 6
  • 12
  • why `start` and `end` are added in line 5 when `start` is going to be 0 at that time? – Sujal Singh Jan 10 '22 at 06:55
  • Not sure if I understood your question, but in the while loop we set ```start``` or ```end``` = ```mid```. By doing this we remove from the array every element before or after the currently checked value. – DevAb Jan 10 '22 at 22:08
  • Yes in the while loop it makes sense but while you are initializing `start` why not just do `Math.floor(end/2)` or am I missing something here? – Sujal Singh Jan 11 '22 at 00:29
  • 1
    Oh it's just for code readability thus it look the same as the ```mid = Math.floor((start + end) / 2);``` in the while loop but I'm not if it's really better tbh. – DevAb Jan 11 '22 at 16:53
-1

Another variant here we have circular range connecting head to toe and accepts only min value to given input. This had helped me get char code values for one of the encryption algorithm.

function closestNumberInCircularRange(codes, charCode) {
  return codes.reduce((p_code, c_code)=>{
    if(((Math.abs(p_code-charCode) > Math.abs(c_code-charCode)) || p_code > charCode) && c_code < charCode){
      return c_code;
    }else if(p_code < charCode){
      return p_code;
    }else if(p_code > charCode && c_code > charCode){
      return Math.max.apply(Math, [p_code, c_code]);
    }
    return p_code;
  });
}
Rob
  • 26,989
  • 16
  • 82
  • 98
Vikas
  • 39
  • 5
-1

You can use below logic to find closest number without using reduce function

let arr = [0, 80, 10, 60, 20, 50, 0, 100, 80, 70, 1];
const n = 2;
let closest = -1;
let closeDiff = -1;

for (let i = 0; i < arr.length; i++) {
  if (Math.abs(arr[i] - n) < closeDiff || closest === -1) {
    closeDiff = Math.abs(arr[i] - n);
    closest = arr[i];
  }
}
console.log(closest);
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Dec 05 '21 at 12:23
-3

For a small range, the simplest thing is to have a map array, where, eg, the 80th entry would have the value 82 in it, to use your example. For a much larger, sparse range, probably the way to go is a binary search.

With a query language you could query for values some distance either side of your input number and then sort through the resulting reduced list. But SQL doesn't have a good concept of "next" or "previous", to give you a "clean" solution.

Hot Licks
  • 47,103
  • 17
  • 93
  • 151