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

noo*_*oob 142 javascript arrays

我有一个从-1000到1000的数字,我有一个数字的数组.像这样:

[2, 42, 82, 122, 162, 202, 242, 282, 322, 362]
Run Code Online (Sandbox Code Playgroud)

我希望我的数字更改为最接近的数组.

例如,我得到80我希望得到的数字82.

小智 174

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);
Run Code Online (Sandbox Code Playgroud)

  • 这非常简单. (7认同)
  • @7yl4r 还是将它包装在一个函数中?;) (5认同)
  • 可以使用更高阶的函数来做到这一点. (4认同)
  • @7yl4r 不是真的...你可以使用bind来实现这个... ---------- // reducer.js function reducer(goal, prev, curr) { return (Math.abs(curr - goal) &lt; Math.abs(prev - goal) ? curr : prev); } // main.js var counts = [4, 9, 15, 6, 2], goal = 5; counts.reduce(reducer.bind(null, goal)); ---------- 不知道怎么把代码放在注释里哈哈哈。 (2认同)
  • 这会迭代每个项目,如果列表是有序的,这不是最佳的,但对于小列表来说是可以的。即使没有二分查找,如果下一个数字更远,循环也可能退出。 (2认同)

pax*_*blo 140

这是伪代码,应该可以转换成任何过程语言:

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
Run Code Online (Sandbox Code Playgroud)

它只是计算出给定数字和每个数组元素之间的绝对差异,并返回其中一个具有最小差异的那些.

对于示例值:

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.
Run Code Online (Sandbox Code Playgroud)

作为一个概念证明,这里是我用来显示这个代码的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)
Run Code Online (Sandbox Code Playgroud)

而且,如果您真的需要它在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>
Run Code Online (Sandbox Code Playgroud)

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

你也应该记住,除非你需要做很多次每秒,效率改进将主要容易被忽视的,除非你的数据集得到很多大.

如果你想尝试一下这种方式(并能保证阵列按升序排序),这是一个很好的起点:

<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>
Run Code Online (Sandbox Code Playgroud)

它主要使用包围和检查中间值来将每个迭代的解空间减少一半,这是一种经典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
Run Code Online (Sandbox Code Playgroud)

如上所述,这对于小型数据集或不需要非常快速的事情来说不应该有太大差别,但它是您可能想要考虑的选项.

  • @ ylun.ca,因为在问题中没有_explicit_语句对数据进行排序(示例已排序但可能是巧合),因此无法获得比O(n)更高的效率.在任何情况下,对于大小的数据集,效率大多是无关紧要的.但你的观点是有效的,所以我会添加注释,希望能让答案更加完整. (3认同)
  • @micha,我已经在答案中添加了等效的 JS 代码,我花了一段时间才将语言从我更习惯的语言更改为 :-) (2认同)
  • 如果您有大型数据集,则此算法的运行时间很差. (2认同)
  • 这似乎是最有效的答案.谢谢! (2认同)

小智 40

ES6(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);
Run Code Online (Sandbox Code Playgroud)

为了可重用,您可以包含支持占位符的咖喱函数(http://ramdajs.com/0.19.1/docs/#curryhttps://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 closestTo5 = getClosest(_, 5);
const closestTo = getClosest([4, 9, 15, 6, 2]);
Run Code Online (Sandbox Code Playgroud)


Ume*_*til 23

工作代码如下:

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));
Run Code Online (Sandbox Code Playgroud)

  • 越多越好. (8认同)
  • 我认为这是一个更好的解决方案,因为它只使用JavaScript.接受的答案使用jQuery,原始问题中没有提到,以及其他看这个问题的人可能没有使用. (6认同)

Dan*_*dru 16

适用于未排序的数组

虽然这里发布了一些很好的解决方案,但JavaScript是一种灵活的语言,它为我们提供了以多种不同方式解决问题的工具.当然,这完全取决于你的风格.如果您的代码更具功能性,您会发现适当的reduce变化,即:

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

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

  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
Run Code Online (Sandbox Code Playgroud)

与使用最小值的其他方法相反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).

我已经创建了一个展示它的垃圾箱.

  • 您可以使findClosest返回一个reduce回调函数,使其在所有数组上都可重用:`const findClosest = goal =>(a,b)=> Math.abs(a - goal)<Math.abs(b - goal)?a:b;` (2认同)

Hub*_*iak 10

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

到目前为止,所有答案都集中在搜索整个阵列.考虑到你的数组已经排序,你真的只想要最接近的数字,这可能是最快的解决方案:

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));
Run Code Online (Sandbox Code Playgroud)

注意,例如使用二叉树可以极大地改进算法.


Gaj*_*jus 7

所有解决方案都经过精心设计。

它很简单:

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

// 5
Run Code Online (Sandbox Code Playgroud)

  • 这根本没有效率 (2认同)
  • 我需要在 20k+ 数字的列表中找到最接近的数字,并且我可能需要经常这样做(可能每次用户得分时)。我还需要在二维上进行操作,因此两个非常长的列表中最接近的两个。“过度设计”是相对于需求而言的。我发现这里的一切都设计不足。 (2认同)

Séb*_*ien 7

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

Run Code Online (Sandbox Code Playgroud)

例子:

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

Run Code Online (Sandbox Code Playgroud)


bvd*_*vdb 7

其他答案表明您需要迭代整个数组

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

如果数组已经排序,那就没有意义了。无需计算所有偏差。例如,在 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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

结果:

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
Run Code Online (Sandbox Code Playgroud)

  • 很想知道为什么这被否决了;- 我试图改进答案,解释为什么这是一个更好的答案。 (2认同)

Nin*_*olz 6

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

与相对Array#reduce,它不需要为一个结果迭代所有元素。

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

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

最后,采用具有相等增量的较小值,如以下示例中的22,结果为2

如果优先级较高,则必须将增量检查从以下位置更改:

if (delta >= lastDelta) {
Run Code Online (Sandbox Code Playgroud)

至:

if (delta > lastDelta) {
//       ^^^ without equal sign
Run Code Online (Sandbox Code Playgroud)

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

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


优先级较小的代码:

if (delta >= lastDelta) {
Run Code Online (Sandbox Code Playgroud)

优先级更高的代码:

if (delta > lastDelta) {
//       ^^^ without equal sign
Run Code Online (Sandbox Code Playgroud)


Hot*_*cks -3

对于小范围,最简单的事情是拥有一个映射数组,例如,第 80 个条目的值为 82,以使用您的示例。对于更大、稀疏的范围,可能的方法是二分搜索。

使用查询语言,您可以查询输入数字两侧一定距离的值,然后对生成的简化列表进行排序。但是 SQL 没有“下一个”或“上一个”的良好概念,无法为您提供“干净”的解决方案。