从数组中获取最接近的数字

Get the closest number out of an array

本文关键字:数字 最接近 获取 数组      更新时间:2023-09-26

我有一个从负 1000 到正 1000 的数字,我有一个包含数字的数组。喜欢这个:

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

我希望我得到的数字更改为数组中最接近的数字。

例如,我得到80作为数字,我希望它得到82.

ES5 版本:

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);

下面是应该可以转换为任何过程语言的伪代码:

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

它只是计算出给定数字和每个数组元素之间的绝对差异,并为您提供差异最小的一个。

对于示例值:

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.

作为概念证明,以下是我用来演示的 Python 代码:

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)

而且,如果你真的需要在Javascript中使用它,请参阅下面的完整HTML文件,该文件演示了该函数的实际操作:

<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>

现在请记住,例如,如果对数据项进行排序(可以从示例数据中推断出,但您没有明确说明(,则可能会提高效率。例如,您可以使用二叉搜索来查找最近的项目。

您还应该记住,除非您需要每秒多次执行此操作,否则除非您的数据集变得更大,否则效率的提高几乎不会引起注意。

如果您确实想尝试这样做(并且可以保证数组按升序排序(,这是一个很好的起点:

<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>

它基本上使用括号和中间值的检查来将每次迭代的解空间减少一半,这是一种经典的O(log N)算法,而上面的顺序搜索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

如前所述,对于小型数据集或不需要快速运行的东西来说,这应该不会有太大区别,但这是您可能需要考虑的选项。

ES6 (ECMAScript 2015( 版本:

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);

为了可重用性,您可以包装在支持占位符(http://ramdajs.com/0.19.1/docs/#curry 或 https://lodash.com/docs#curry(的咖喱函数中。这提供了很大的灵活性,具体取决于您的需求:

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>

工作代码如下:

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));

适用于未排序的数组

虽然这里发布了一些很好的解决方案,但 JavaScript 是一种灵活的语言,它为我们提供了以多种不同方式解决问题的工具。当然,这一切都归结为你的风格。如果你的代码功能更强大,你会发现减少变化是合适的,即:

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

但是,有些人可能会发现很难阅读,具体取决于他们的编码风格。因此,我提出了一种解决问题的新方法:

  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

与其他使用Math.min.apply查找最小值的方法相反,这种方法不需要对输入数组arr进行排序。我们不需要事先关心索引或对其进行排序。

为清楚起见,我将逐行解释代码:

  1. arr.map(function(k) { return Math.abs(k - x) }) 创建一个新数组,实质上存储给定数字的绝对值(以arr为单位的数字(减去输入数字(x(。接下来我们将寻找最小的数字(也是最接近输入的数字(
  2. Math.min.apply(Math, indexArr) 这是在我们之前刚刚创建的数组中查找最小数字的合法方法(仅此而已(
  3. arr[indexArr.indexOf(min)] 这也许是最有趣的部分。我们已经找到了最小的数字,但我们不确定是否应该添加或减去初始数字(x(。那是因为我们用Math.abs()来发现差异。但是,array.map(逻辑上(创建输入数组的映射,将索引保留在同一位置。因此,要找出最接近的数字,我们只需返回给定数组中找到的最小值的索引 indexArr.indexOf(min)

我创建了一个箱来演示它。

所有的解决方案都经过了过度设计。

它就像:

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

对于排序数组(线性搜索(

到目前为止,所有答案都集中在搜索整个数组上。考虑到您的数组已经排序并且您实际上只想要最接近的数字,这可能是最简单(但不是最快的(解决方案:

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));

请注意,该算法可以大大改进,例如使用二叉树。

ES6

适用于排序和未排序数组

数字整数和浮点数,欢迎字符串

/**
 * 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

例子:

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

该解决方案使用 ES5 存在量词 Array#some ,如果满足条件,它允许停止迭代。

反对Array#reduce,它不需要迭代所有元素以获得一个结果。

在回调中,搜索值与实际item之间的绝对delta,并与最后一个增量进行比较。如果大于或相等,则迭代停止,因为所有其他值及其增量都大于实际值。

如果回调中的delta较小,则实际项目分配给结果,并将delta保存在 lastDelta 中。

最后,取具有相等增量的较小值,如下面的 22 示例所示,这会导致 2

如果存在更大值的优先级,则必须将增量检查从:

if (delta >= lastDelta) {

自:

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

这将得到22,结果42(更大值的优先级(。

此函数需要数组中的排序值。


优先级为较小值的代码:

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

优先级为较大值的代码:

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

其他答案表明您需要遍历整个数组

  • 计算每个元素的偏差
  • 跟踪最小偏差及其元素
  • 最后,在遍历整个数组之后,返回偏差最小的元素。

如果数组已经排序,则没有意义。无需计算所有偏差。例如,在 100 万个元素的有序集合中,您只需要计算 ~19 个偏差(最多(即可找到您的匹配项。您可以使用二进制搜索方法完成此操作:

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;
        }
    }
}

结果:

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

对于 O(n( 时间复杂度,一种更简单的方法是在数组的一次迭代中执行此操作。此方法适用于未排序的数组。

下面是一个javascript示例,从数组中我们找到最接近"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

这也适用于数、数、十进制数。

Math.min()会返回Infinity.

result将存储最接近搜索元素的值。

我对类似问题的回答也是解释关系,它是普通的Javascript,尽管它不使用二进制搜索,所以它是O(N(而不是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

我不知道

我是否应该回答一个老问题,但是由于这篇文章首先出现在Google搜索上,我希望您能原谅我在这里添加我的解决方案和2c。

由于懒惰,我不敢相信这个问题的解决方案会是一个 LOOP,所以我搜索了更多,回来了过滤功能:

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);

就这样!

我喜欢

Fusion的方法,但它有一个小错误。像这样是正确的:

    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];
    }

它也更快一些,因为它使用了改进的for循环。

最后,我像这样编写了我的函数:

    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;
    };

我用console.time()测试了它,它比其他功能略快。

最有效的是二叉搜索。但是,当下一个数字与当前数字进一步匹配时,即使是简单的解决方案也可以退出。这里几乎所有的解决方案都没有考虑到数组是有序的,并且在整个事情中迭代:/

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);

这也可以在非原语上运行,例如 closest(data, 21, item => item.age)

find更改为findIndex以返回数组中的索引。

如果数组像您的示例一样排序,则可以使用二进制搜索来获得更好的 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);

工作原理 :

  1. 它将目标值与数组的中间元素进行比较。如果中间元素更大,我们可以忽略它之后的每个元素,因为它们会更大。如果中间元素较小,我们也可以忽略它之前的每个元素。

  2. 如果找到目标值,我们返回它,否则我们将测试的最后一个值与其周围的邻居进行比较,因为最接近的值只能在这 3 个值之间。

这里的另一种变体是圆形范围,从头到脚连接,并且只接受给定输入的最小值。这帮助我获得了其中一种加密算法的字符代码值。

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;
  });
}

您可以使用以下逻辑来查找最接近的数字,而无需使用reduce函数

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);

对于一个小范围,最简单的方法是有一个地图数组,例如,第 80 个条目中的值为 82,使用您的示例。 对于更大、更稀疏的范围,可能要走的路是二叉搜索。

使用查询语言,您可以查询输入数字两侧一定距离的值,然后对生成的缩减列表进行排序。 但是SQL没有一个"下一个"或"上一个"的好概念,不能给你一个"干净"的解决方案。