检查一个数组中的每个元素是否都在第二个数组中。

49
我有两个数组,我想要检查arr2中的每个元素是否都在arr1中出现。如果元素的值在arr2中重复出现,那么它在arr1中需要相等次数出现。如何最好地实现这个功能?
arr1 = [1, 2, 3, 4]
arr2 = [1, 2]

checkSuperbag(arr1, arr2)
> true //both 1 and 2 are in arr1

arr1 = [1, 2, 3, 4]
arr2 = [1, 2, 5]

checkSuperbag(arr1, arr2)
> false //5 is not in arr1

arr1 = [1, 2, 3]
arr2 = [1, 2, 3, 3]

checkSuperbag(arr1, arr2)
> false //3 is not in arr1 twice

最后一个例子应该返回false。如果两个数组的长度相同,则没有超集/子集。http://mathworld.wolfram.com/Superset.html - Bakudan
集合不能包含重复元素,因此在这些条件下确定何时为超集的概念并不太有意义。 - Adam Rackis
最后一个例子应该是“true”,有两个原因:(1)在集合中重复无关紧要:“{1,1} = {1}”。(2)一个集合是它自己的子集和超集;如果两者不应该相等,则称为“真子集”和“真超集”。 - outis
2
“Bag”有时用于指允许重复的无序集合。 - outis
@Harry:如果“bag”是更好的术语,请编辑您的问题以反映这一点。 - outis
显示剩余3条评论
10个回答

35

您是否需要支持过时的浏览器?如果不需要,every函数可以很容易地实现这一点。

如果arr1是arr2的超集,则arr1中必须包含arr2中的每个成员。

var isSuperset = arr2.every(function(val) { return arr1.indexOf(val) >= 0; });

这里有一个示例: fiddle 编辑:
因此,您将超集定义为arr2中的每个元素在arr1中出现相同的次数吗?我认为filter会帮助您做到这一点(从前面的MDN链接中获取shim以支持旧版浏览器):
var isSuperset = arr2.every(function (val) { 
    var numIn1 = arr1.filter(function(el) { return el === val;  }).length;
    var numIn2 = arr2.filter(function(el) { return el === val;  }).length;
    return numIn1 === numIn2;   
});

更新的Fiddle

编辑结束


如果您确实想要支持旧版浏览器,上面提到的MDN链接中有一个您可以添加的shim,以下是为了方便起见复制的内容:

if (!Array.prototype.every)  
{  
  Array.prototype.every = function(fun /*, thisp */)  
  {  
    "use strict";  

    if (this == null)  
      throw new TypeError();  

    var t = Object(this);  
    var len = t.length >>> 0;  
    if (typeof fun != "function")  
      throw new TypeError();  

    var thisp = arguments[1];  
    for (var i = 0; i < len; i++)  
    {  
      if (i in t && !fun.call(thisp, t[i], i, t))  
        return false;  
    }  

    return true;  
  };  
}  

编辑

请注意,这将是一个O(N2)算法,因此避免在大型数组上运行它。


@AdamRackis - 当然没问题。 ;) 顺便说一下,如果您想从测试重复项的解决方案中挤出更多性能,可以维护一个重复值表以避免在第二次通过时重复执行相同的测试。不过,如果我们只处理小数组,则可能不值得这样做。 - user1106925
我知道你不是在钓鱼...或者说我知道吗?... ;) - user1106925
在 ES6 中:arr2.every((i) => arr1.indexOf(i) != -1 ) - vsync
@vsync - 当然。我相信很多我的旧答案都可以从一些ES6中受益 :) - Adam Rackis
@AdamRackis - 就像每个人一样 ;) - vsync
显示剩余5条评论

25

一种选项是对这两个数组进行排序,然后同时遍历比较元素。如果一个子集合候选元素在超集合中找不到,那么前者不是子集合。排序通常是O(n*log(n))的,比较是O(max(s,t))的,其中st是数组大小,总时间复杂度为O(m*log(m)),其中m=max(s,t)。

function superbag(sup, sub) {
    sup.sort();
    sub.sort();
    var i, j;
    for (i=0,j=0; i<sup.length && j<sub.length;) {
        if (sup[i] < sub[j]) {
            ++i;
        } else if (sup[i] == sub[j]) {
            ++i; ++j;
        } else {
            // sub[j] not in sup, so sub not subbag
            return false;
        }
    }
    // make sure there are no elements left in sub
    return j == sub.length;
}

如果实际代码中的元素是整数,则可以使用特定的整数排序算法(例如基数排序)来实现O(max(s,t))的时间复杂度,但如果包很小,则内置的Array.sort可能比自定义整数排序更快。
具有潜在较小时间复杂度的解决方案是创建一个包类型。整数包特别容易。翻转包的现有数组:创建一个对象或一个数组,将整数作为键,重复计数作为值。使用数组不会浪费空间,因为Javascript中的数组是稀疏的。您可以使用包操作进行子包或超包检查。例如,从子候选项中减去超级候选项并测试结果是否非空。或者,contains操作应该是O(1)(或可能是O(log(n))),因此循环遍历子包候选项并测试超级包含是否超过每个子包元素的子包包含应该是O(n)或O(n*log(n))。
以下内容未经测试。实现isInt留作练习。
function IntBag(from) {
    if (from instanceof IntBag) {
        return from.clone();
    } else if (from instanceof Array) {
        for (var i=0; i < from.length) {
            this.add(from[i]);
        }
    } else if (from) {
        for (p in from) {
            /* don't test from.hasOwnProperty(p); all that matters
               is that p and from[p] are ints
             */
            if (isInt(p) && isInt(from[p])) {
                this.add(p, from[p]);
            }
        }
    }
}
IntBag.prototype=[];
IntBag.prototype.size=0;
IntBag.prototype.clone = function() {
    var clone = new IntBag();
    this.each(function(i, count) {
        clone.add(i, count);
    });
    return clone;
};
IntBag.prototype.contains = function(i) {
    if (i in this) {
        return this[i];
    }
    return 0;
};
IntBag.prototype.add = function(i, count) {
    if (!count) {
        count = 1;
    }
    if (i in this) {
        this[i] += count;
    } else {
        this[i] = count;
    }
    this.size += count;
};
IntBag.prototype.remove = function(i, count) {
    if (! i in this) {
        return;
    }
    if (!count) {
        count = 1;
    }
    this[i] -= count;
    if (this[i] > 0) {
        // element is still in bag
        this.size -= count;
    } else {
        // remove element entirely
        this.size -= count + this[i];
        delete this[i];
    }
};
IntBag.prototype.each = function(f) {
    var i;
    foreach (i in this) {
        f(i, this[i]);
    }
};
IntBag.prototype.find = function(p) {
    var result = [];
    var i;
    foreach (i in this.elements) {
        if (p(i, this[i])) {
            return i;
        }
    }
    return null;
};
IntBag.prototype.sub = function(other) {
    other.each(function(i, count) {
        this.remove(i, count);
    });
    return this;
};
IntBag.prototype.union = function(other) {
    var union = this.clone();
    other.each(function(i, count) {
        if (union.contains(i) < count) {
            union.add(i, count - union.contains(i));
        }
    });
    return union;
};
IntBag.prototype.intersect = function(other) {
    var intersection = new IntBag();
    this.each(function (i, count) {
        if (other.contains(i)) {
            intersection.add(i, Math.min(count, other.contains(i)));
        }
    });
    return intersection;
};
IntBag.prototype.diff = function(other) {
    var mine = this.clone();
    mine.sub(other);
    var others = other.clone();
    others.sub(this);
    mine.union(others);
    return mine;
};
IntBag.prototype.subbag = function(super) {
    return this.size <= super.size
       && null !== this.find(
           function (i, count) {
               return super.contains(i) < this.contains(i);
           }));
};

如果您想禁止元素的重复出现,可以参考 "比较JavaScript数组" 中的示例实现一个对象集合。


“is left as an exercise” = “我懒得做了” :) - derekdreery
5
不要认为攻击我的自尊心会让我交出我布置的作业的答案;我已经看透了你的把戏 ;) - outis

5

还没有人发布递归函数,而这些总是很有趣的。像这样调用它:arr1.containsArray( arr2 )

演示:http://jsfiddle.net/ThinkingStiff/X9jed/

Array.prototype.containsArray = function ( array /*, index, last*/ ) {

    if( arguments[1] ) {
        var index = arguments[1], last = arguments[2];
    } else {
        var index = 0, last = 0; this.sort(); array.sort();
    };

    return index == array.length
        || ( last = this.indexOf( array[index], last ) ) > -1
        && this.containsArray( array, ++index, ++last );

};

4

github lodash 库中找到了这个内容。该函数使用内置函数来解决问题:.includes().indexOf().every()

var array1 = ['A', 'B', 'C', 'D', 'E'];
var array2 = ['B', 'C', 'E'];
var array3 = ['B', 'C', 'Z'];
var array4 = [];

function arrayContainsArray (superset, subset) {
  if (0 === subset.length) {
    return false;
  }
  return subset.every(function (value) {
    return (superset.includes(value));
  });
}

 function arrayContainsArray1 (superset, subset) {
   if (0 === subset.length) {
     return false;
   }
   return subset.every(function (value) {
     return (superset.indexOf(value) >= 0);
   });
}

console.log(arrayContainsArray(array1,array2)); //true
console.log(arrayContainsArray(array1,array3)); //false
console.log(arrayContainsArray(array1,array4)); //false

console.log(arrayContainsArray1(array1,array2)); //true
console.log(arrayContainsArray1(array1,array3)); //false
console.log(arrayContainsArray1(array1,array4)); //false


4
使用对象(也就是哈希表)代替排序,可以将平摊复杂度降低到O(m+n):
function bagContains(arr1, arr2) {
    var o = {}
    var result = true;

    // Count all the objects in container
    for(var i=0; i < arr1.length; i++) {
        if(!o[arr1[i]]) {
            o[arr1[i]] = 0;
        }
        o[arr1[i]]++;
    }

    // Subtract all the objects in containee
    // And exit early if possible
    for(var i=0; i < arr2.length; i++) {
        if(!o[arr2[i]]) {
            o[arr2[i]] = 0;
        }
        if(--o[arr2[i]] < 0) {
            result = false;
            break;
        }
    }

    return result;
}

console.log(bagContains([1, 2, 3, 4], [1, 3]));
console.log(bagContains([1, 2, 3, 4], [1, 3, 3]));
console.log(bagContains([1, 2, 3, 4], [1, 3, 7]));

这将产生truefalsefalse


2
如果 arr2 是 arr1 的子集,则 set(arr1 + arr2) 的长度 == set(arr1) 的长度
var arr1 = [1, 'a', 2, 'b', 3];
var arr2 = [1, 2, 3];

Array.from(new Set(arr1)).length == Array.from(new Set(arr1.concat(arr2))).length

1
这并没有考虑到重复项。而且,将其转换为数组会降低时间复杂度。Set有一个size属性。https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Set/size - Andrew

1
这是我的解决方案:
Array.prototype.containsIds = function (arr_ids) {
    var status = true;
    var current_arr = this;
    arr_ids.forEach(function(id) {
        if(!current_arr.includes(parseInt(id))){
            status = false;
            return false; // exit forEach
        }
    });
    return status;
};

// Examples
[1,2,3].containsIds([1]); // true
[1,2,3].containsIds([2,3]); // true
[1,2,3].containsIds([3,4]); // false

0

至于另一种方法,您可以按照以下步骤操作:

function checkIn(a,b){
  return b.every(function(e){
                   return e === this.splice(this.indexOf(e),1)[0];
                 }, a.slice()); // a.slice() is the "this" in the every method
}

var arr1  = [1, 2, 3, 4],
    arr2  = [1, 2],
    arr3  = [1,2,3,3];
console.log(checkIn(arr1,arr2));
console.log(checkIn(arr1,arr3));


-1

这里有一个快速解决方案,使用两个数组。如果ba长,则它不能是超集,因此返回false。然后循环遍历b,查看a是否包含该元素。如果是,则从a中删除它并继续,否则返回false。最坏的情况是b是子集,那么时间复杂度将为b.length

function isSuper(a,b){
  var l=b.length,i=0,c;
  if(l>a.length){return false}
  else{
    for(i;i<l;i++){
      c=a.indexOf(b[i]);
      if(c>-1){
        a.splice(c,1);
      }
      else{return false}
    }
    return true;
  }
}

这假设输入不总是有序的,如果a1,2,3,而b3,2,1,它仍将返回true。


-1

另一个简单的解决方案如下:

let a = [1,2,'a',3,'b',4,5]

let b = [1,2,4]

console.log(b.every((i) => a.includes(i)))

希望有所帮助。

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