在JavaScript中检查一个数组是否包含另一个数组的任何元素

916

我有一个目标数组 ["apple","banana","orange"],我想检查其他数组是否包含目标数组中的任何一个元素。

例如:

["apple","grape"] //returns true;

["apple","banana","pineapple"] //returns true;

["grape", "pineapple"] //returns false;

我该如何用JavaScript实现它?


5
使用 for 循环遍历目标数组。如果每个元素都包含在当前数组中(使用 current.indexOf(elem) !== -1),那么它们都在其中。 - Blender
4
我不同意。我不会为了执行此操作而导入一个库。 - Patricio Vargas
3
@devpato,是的,我改变主意了,ES6 的解决方案是我最喜欢的。 - Leon Gaban
如果你想获取元素而不仅仅是 truefalse,那么你需要使用 .filter() :: JavaScript 算法:查找数组中不在另一个数组中的元素 - whoami - fakeFaceTrueSoul
32个回答

1310

原生JS

ES2016:

const found = arr1.some(r=> arr2.includes(r))

工作原理

some(..)会对数组的每个元素进行测试,并在数组中的任何元素通过测试函数时返回true,否则返回false。includes(..)会在给定的参数存在于数组中时返回true。


1
我的结果是 [false, false, false] 而不是一个空数组 [],这是否符合预期? - Batman
1
不得不这样做以过滤掉一个巨大的 CSV 文件,以寻找某些内容。我使用了 .filter() 方法来获取适用于手头任务的行列表,并且 some() 方法非常有用。谢谢。var searchTerms = ['term1', 'term2', 'term3', 'term4']; var results = csvRows.filter(row => searchTerms.some(value => row.column1.includes(value))); - Chazt3n
1
我怎样才能获取那个数字的值? - huykon225
10
arr1和arr2这样的命名在教学中不太合适。 - Max
我们该如何进一步确定或返回两个数组之间匹配的相似值? - Gel
显示剩余2条评论

343

纯 JavaScript

/**
 * @description determine if an array contains one or more items from another array.
 * @param {array} haystack the array to search.
 * @param {array} arr the array providing items to check for in the haystack.
 * @return {boolean} true|false if haystack contains at least one item from arr.
 */
var findOne = function (haystack, arr) {
    return arr.some(function (v) {
        return haystack.indexOf(v) >= 0;
    });
};

正如@loganfsmyth所指出的那样,在ES2016中,您可以将其缩短为

/**
 * @description determine if an array contains one or more items from another array.
 * @param {array} haystack the array to search.
 * @param {array} arr the array providing items to check for in the haystack.
 * @return {boolean} true|false if haystack contains at least one item from arr.
 */
const findOne = (haystack, arr) => {
    return arr.some(v => haystack.includes(v));
};

或者简单地使用 arr.some(v => haystack.includes(v));

如果你想确定数组是否包含另一个数组的所有项,请将some()替换为every() 或者使用arr.every(v => haystack.includes(v));


19
好的解决方案!some()很棒。一旦匹配到任何内容就停止搜索。 - averydev
6
像这样使事件更整洁:arr.some(v=> haystack.indexOf(v) >= 0) - Paul Grimshaw
98
也可以在ES2016中使用arr.some(v => haystack.includes(v)) - loganfsmyth
6
在一行代码中,判断数组arr1中是否存在与数组arr2相同的元素: arr1.some(v => arr2.indexOf(v) >= 0) - webjay
4
目前最好避免使用 includes,因为显然它在 IE 中不受支持:https://dev59.com/eVoV5IYBdhLWcg3wL8Ww - Shafique Jamal
这应该是正确的答案,因为它在IE中也可以工作。 - Carlo Fontanos

153
ES6解决方案:
let arr1 = [1, 2, 3];
let arr2 = [2, 3];

let isFound = arr1.some( ai => arr2.includes(ai) );

或者,如果必须包含所有值:
let allFound = arr2.every( ai => arr1.includes(ai) );

有没有办法从array1中获取arra2值的索引? - Anzil khaN
1
在这种情况下,我们可以使用“filter”而不是“some”。然后它将返回一个数组而不是布尔值,您可以轻松地从中访问值。 - protanvir993
2
在我的情况下,只有在函数内部返回时才有效,例如:let allFounded = arr2.every( ai => return arr1.includes(ai) ); - Fed C

81
如果你不介意使用一个库,http://underscorejs.org/ 提供了一个交集方法(intersection method),可以简化这个过程。
var _ = require('underscore');

var target = [ 'apple', 'orange', 'banana'];
var fruit2 = [ 'apple', 'orange', 'mango'];
var fruit3 = [ 'mango', 'lemon', 'pineapple'];
var fruit4 = [ 'orange', 'lemon', 'grapes'];

console.log(_.intersection(target, fruit2)); //returns [apple, orange]
console.log(_.intersection(target, fruit3)); //returns []
console.log(_.intersection(target, fruit4)); //returns [orange]

交集函数将返回一个新的数组,其中包含匹配的项,如果没有匹配,则返回空数组。

4
我已经使用过这个功能好几次,但需要注意的是,这个问题是关于检查是否存在于另一个数组中,而不是产生整个交集。就性能而言,如果数组很大,两种情况之间有着巨大的区别,因为在第一种情况下,一旦找到一个匹配项,就可以立即退出。 - JHH
1
Lodash比原生JavaScript更易读,像Ramda这样的库应该始终使用,而不是原生。对于所有开发人员来说都更好... - Leon Gaban
_.some()结合使用,即_.some(_.intersection(target, fruit2)) - Quitch
或者是 _.isEmpty(_.intersection(target, fruit2))。感谢 @willz - Jose Velasco

74

我知道我来晚了,但是要检查JSFiddle控制台,请添加JQuery Edge并打开Firebug Lite。 - Rojo
JSperf链接已损坏。 - DarckBlezzer
时间和空间复杂度有什么区别吗?关于复杂度,什么是最佳解决方案? - nacho

53

如果您不需要类型强制转换(因为使用了indexOf),您可以尝试以下操作:

var arr = [1, 2, 3];
var check = [3, 4];

var found = false;
for (var i = 0; i < check.length; i++) {
    if (arr.indexOf(check[i]) > -1) {
        found = true;
        break;
    }
}
console.log(found);

arr包含目标项,最终found将会显示第二个数组是否与目标项至少存在一个匹配。

当然,你可以使用任何你想要使用的东西替换数字-像你的例子中一样,字符串可以正常使用。

在我的具体示例中,结果应该为true,因为第二个数组中的3存在于目标项中。


更新:

以下是我如何将其组织成一个函数(与之前相比进行了一些微小的更改):

var anyMatchInArray = (function () {
    "use strict";

    var targetArray, func;

    targetArray = ["apple", "banana", "orange"];
    func = function (checkerArray) {
        var found = false;
        for (var i = 0, j = checkerArray.length; !found && i < j; i++) {
            if (targetArray.indexOf(checkerArray[i]) > -1) {
                found = true;
            }
        }
        return found;
    };

    return func;
}());

演示: http://jsfiddle.net/u8Bzt/

在这种情况下,函数可以修改为将targetArray作为参数传递而不是硬编码在闭包中。


更新2:

虽然我上面的解决方案可能有效并且更易读(希望如此),但我认为处理我描述的概念的“更好”方法是稍微有些不同。上面解决方案的“问题”在于循环内部的indexOf会导致目标数组在其他数组的每个项目上完全循环一次。这可以通过使用“查找”(一个映射...JavaScript对象文字)来轻松地“修复”。这允许对每个数组进行两个简单的循环。以下是示例:

var anyMatchInArray = function (target, toMatch) {
    "use strict";

    var found, targetMap, i, j, cur;

    found = false;
    targetMap = {};

    // Put all values in the `target` array into a map, where
    //  the keys are the values from the array
    for (i = 0, j = target.length; i < j; i++) {
        cur = target[i];
        targetMap[cur] = true;
    }

    // Loop over all items in the `toMatch` array and see if any of
    //  their values are in the map from before
    for (i = 0, j = toMatch.length; !found && (i < j); i++) {
        cur = toMatch[i];
        found = !!targetMap[cur];
        // If found, `targetMap[cur]` will return true, otherwise it
        //  will return `undefined`...that's what the `!!` is for
    }

    return found;
};

演示: http://jsfiddle.net/5Lv9v/

这种解决方案的缺点是只能正确使用数字和字符串(以及布尔值),因为这些值被(隐式地)转换为字符串并设置为查找表中的键。对于非文字值,这并不完全好/可能/容易实现。


你为什么要使用for循环,而不是使用some或findIndex呢? - It's me ... Alex
1
"some"大大简化了代码。此外,anyMatchInArray([1,2,3,"cats","4"], ["1",4]) 也会返回true。 最后,如果您有大量查找并缓存目标映射,则可能会更快。即便如此,还是可以提高性能。例如,我猜测“found = toMatch[i] !== undefined”可能会更加高效,在某些情况下更好(以便不将""或0评估为false)。 - csga5000
“否则它将返回undefined...这就是!!的作用” - 这是错误的。它将返回!的布尔反义。 - AlienWebguy

34

使用 filter/indexOf 方法:

function containsAny(source,target)
{
    var result = source.filter(function(item){ return target.indexOf(item) > -1});   
    return (result.length > 0);  
}    


//results

var fruits = ["apple","banana","orange"];


console.log(containsAny(fruits,["apple","grape"]));

console.log(containsAny(fruits,["apple","banana","pineapple"]));

console.log(containsAny(fruits,["grape", "pineapple"]));


这个与库函数(如 _.intersection)有相同的问题,即在找到一个匹配项后它仍将继续寻找。不过对于小数组来说,显然没问题。 - JHH

32

你可以使用lodash并执行:

_.intersection(originalTarget, arrayToCheck).length > 0

对两个集合执行交集运算,得到一个由相同元素组成的数组。


从性能的角度来看,这并不是最优的解决方案,因为对于这个问题,找到第一个匹配项就足够了,而intersection会继续比较以找到所有匹配项。这就像在只需要find时使用filter一样。 - Alexander

30
const areCommonElements = (arr1, arr2) => {
    const arr2Set = new Set(arr2);
    return arr1.some(el => arr2Set.has(el));
};

如果您首先找出这两个数组中哪一个更长,并将Set应用于最长的数组,同时在最短的数组上应用some方法,则甚至可以获得更好的性能:

const areCommonElements = (arr1, arr2) => {
    const [shortArr, longArr] = (arr1.length < arr2.length) ? [arr1, arr2] : [arr2, arr1];
    const longArrSet = new Set(longArr);
    return shortArr.some(el => longArrSet.has(el));
};

4
虽然人们一直在发布嵌套使用 indexOfincludes 的解决方案,但你是第一个使用更高效的基于集合的解决方案回答问题的人,该方案使用了本地的 Set,距其被引入 EcmaScript 已经有4年了。+1 - trincot
请纠正我,但是创建一个Set是否仍然需要迭代(在幕后)?如果是这样,那么哪个数组更短或更长都无所谓,因为时间复杂度仍然是相同的。是或不是? - Molasses
1
@Sal_Vader_808 我的意思是指该代码已编译(实现在浏览器的编译代码中,而不是由解释器运行JS循环)。尝试在浏览器控制台中编写“Set”,然后按Enter键。你会得到这个回应:“ƒ Set() { [native code] }”,而不是它的JS代码实现。虽然某些本地代码可能会更慢,但这只是因为其中一些有很多额外的验证器。 - Alexander
1
: 显然,数组的长度并不是决定将哪个数组转换为 Set 的唯一因素。这取决于代码,例如,如果它总是使用相同的数组检查许多其他不同数组中的公共元素-显然希望将其转换为 Set 一次,并将其存储在函数外的常量中,即使它不是最长的数组。 - Alexander
1
@Alexander 很酷!看起来像是本地代码。又学到了新东西,谢谢!此外,如果预计要多次使用数据,一定要考虑缓存。记忆化胜利! - Molasses
显示剩余2条评论

22

我写了3个解决方案,本质上它们都是一样的。只要它们得到true,它们就会立即返回true。我写这3个解决方案只是为了展示3种不同的做法。现在,它取决于你更喜欢什么。你可以使用performance.now()来检查一个解决方案或另一个解决方案的性能。在我的解决方案中,我还检查哪个数组最大、哪个数组最小,以使操作更加高效。

第3个解决方案可能不是最好的,但它很有效率。我决定添加它,因为在某些编程面试中,你不被允许使用内置方法。

最后,当然……我们可以用两个嵌套的for循环(暴力方式)提出一种解决方案,但你要避免这样做,因为时间复杂度很差O(n^2)

注:

与其他人使用.includes()不同,你可以使用.indexOf()。如果你这样做只需检查值是否大于0。如果该值不存在,将给你-1。如果存在,则会给你大于0的值。

indexOf() vs includes()

哪个更好的性能indexOf()略胜一筹,但是在我看来includes更易读。

如果我没有错,.includes()indexOf() 在幕后使用循环,因此当与.some()一起使用时,你将处于O(n^2)

使用循环

 const compareArraysWithIncludes = (arr1, arr2) => {
     const [smallArray, bigArray] =
        arr1.length < arr2.length ? [arr1, arr2] : [arr2, arr1];

     for (let i = 0; i < smallArray.length; i++) {
       return bigArray.includes(smallArray[i]);
     }

      return false;
    };

使用 .some()

const compareArraysWithSome = (arr1, arr2) => {
  const [smallArray, bigArray] =
    arr1.length < arr2.length ? [arr1, arr2] : [arr2, arr1];
  return smallArray.some(c => bigArray.includes(c));
};

使用地图 时间复杂度 O(2n)=>O(n)

const compararArraysUsingObjs = (arr1, arr2) => {
  const map = {};

  const [smallArray, bigArray] =
    arr1.length < arr2.length ? [arr1, arr2] : [arr2, arr1];

  for (let i = 0; i < smallArray.length; i++) {
    if (!map[smallArray[i]]) {
      map[smallArray[i]] = true;
    }
  }

  for (let i = 0; i < bigArray.length; i++) {
    if (map[bigArray[i]]) {
      return true;
    }
  }

  return false;
};

在我的StackBlitz中的代码:

我不是性能或BigO方面的专家,所以如果我说错了什么,请让我知道。


好的回答,但是: “如果你使用indexOf,只需检查值是否大于0。如果该值不存在,则会给您-1。如果存在,则会给您大于0。” 这应该读作大于等于/大于等于。 - angus l
2
关于使用includes,我认为确定哪个数组更短/更长并不重要。查看Array#includes的实现方式(tc39.es/ecma262/#sec-array.prototype.includes),它仍然看起来像你必须遍历更长的数组。除非我完全误读了includes的实现方式(这是可能的哈哈)。此外,我同意使用maps会是最有效的方法。 - Molasses
嗨@Sal_Vader_808,说得好。让我做些研究。谢谢! - Patricio Vargas
谢谢你提供这段代码!它对我的项目非常有帮助。如果可以的话,能否通过你的.some()解决方案显示两个数组中相同的项? - Jonathin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接