比较ECMA6集是否相等


102

您如何比较两个JavaScript集?我尝试使用=====但都返回false。

a = new Set([1,2,3]);
b = new Set([1,3,2]);
a == b; //=> false
a === b; //=> false

这两个集合是等效的,因为根据定义,集合没有顺序(至少不是通常如此)。我查看了“在MDN上设置”文档,发现没有任何用处。有人知道怎么做吗?


两组是两个不同的对象。===是为了价值平等,而不是对象平等。
elclanrs

3
迭代并比较每个成员的值(如果全部相同),设置为“相同”
dandavis 2015年

1
@dandavis使用集合时,成员值。

2
集和地图确实有秩序,这是插入顺序-无论出于何种原因:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...
CodeManX

8
最糟糕的是,甚至new Set([1,2,3]) != new Set([1,2,3])。这使得Javascript Set集合无效,因为超集将包含重复的子集。我想到的唯一解决方法是将所有子集转换为数组,对每个数组进行排序,然后将每个数组编码为字符串(例如JSON)。
7vujy0f0hy

Answers:


73

试试这个:

var a = new Set([1,2,3]);
var b = new Set([1,3,2]);

alert(eqSet(a, b)); // true

function eqSet(as, bs) {
    if (as.size !== bs.size) return false;
    for (var a of as) if (!bs.has(a)) return false;
    return true;
}

一个更实用的方法是:

var a = new Set([1,2,3]);
var b = new Set([1,3,2]);

alert(eqSet(a, b)); // true

function eqSet(as, bs) {
    return as.size === bs.size && all(isIn(bs), as);
}

function all(pred, as) {
    for (var a of as) if (!pred(a)) return false;
    return true;
}

function isIn(as) {
    return function (a) {
        return as.has(a);
    };
}

all函数适用于所有可迭代对象(例如SetMap)。

如果Array.from得到更广泛的支持,那么我们可以将all功能实现为:

function all(pred, as) {
    return Array.from(as).every(pred);
}

希望能有所帮助。


2
我想你应该改变名称has,以isPartOfisInelem
BERGI

@Bergi我将函数的名称更改hasisIn
Aadit M Shah

1
:@DavidGiven是,套在JavaScript中的迭代中插入顺序developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...
Aadit中号沙阿

48
每天,我都会更加放心JavaScript是有史以来最糟糕的语言。每个人都必须发明自己的基本功能来应对其局限性,这是在ES6中实现的,我们将在2017年!他们为什么不能在Set对象的规范中添加如此频繁的功能!
Ghasan

2
@ GhasanAl-Sakkaf,我同意,也许TC39由科学家组成,但没有实用主义者……
Marecky

59

您也可以尝试:

var a = new Set([1,2,3]);
var b = new Set([1,3,2]);

isSetsEqual = (a, b) => a.size === b.size && [...a].every(value => b.has(value));

console.log(isSetsEqual(a,b)) 


最终确定一个更好的解决方案,因为它适合if条件
Hugodby

我喜欢这种解决方案的惯用法,并且可读性强!感谢@Max
daydreamer

29

lodash提供了_.isEqual(),可以进行深入的比较。如果您不想自己编写,这非常方便。从lodash 4开始,_.isEqual()正确比较Set。

const _ = require("lodash");

let s1 = new Set([1,2,3]);
let s2 = new Set([1,2,3]);
let s3 = new Set([2,3,4]);

console.log(_.isEqual(s1, s2)); // true
console.log(_.isEqual(s1, s3)); // false

7

另一个答案会很好;这是另一种选择。

// Create function to check if an element is in a specified set.
function isIn(s)          { return elt => s.has(elt); }

// Check if one set contains another (all members of s2 are in s1).
function contains(s1, s2) { return [...s2] . every(isIn(s1)); }

// Set equality: a contains b, and b contains a
function eqSet(a, b)      { return contains(a, b) && contains(b, a); }

// Alternative, check size first
function eqSet(a, b)      { return a.size === b.size && contains(a, b); }

但是,要知道,这并没有做深相等比较。所以

eqSet(Set([{ a: 1 }], Set([{ a: 1 }])

将返回false。如果以上两个集合被认为是相等的,我们需要遍历两个集合对每个元素进行深层质量比较。我们规定了deepEqual例程的存在。那么逻辑将是

// Find a member in "s" deeply equal to some value
function findDeepEqual(s, v) { return [...s] . find(m => deepEqual(v, m)); }

// See if sets s1 and s1 are deeply equal. DESTROYS s2.
function eqSetDeep(s1, s2) {
  return [...s1] . every(a1 => {
    var m1 = findDeepEqual(s2, a1);
    if (m1) { s2.delete(m1); return true; }
  }) && !s2.size;
}

它的作用是:对于s1的每个成员,寻找一个与s2完全相等的成员。如果找到,请将其删除,以便不再使用。如果在s2中找到s1中的所有元素,并且 s2用尽,则这两个集合是完全相等的。未经测试。

您可能会发现这很有用:http : //www.2ality.com/2015/01/es6-set-operations.html


6

这些解决方案都无法将预期功能“带回”到数据集(集合集)中。在当前状态下,Javascript 对此无用,因为超集将包含重复的子集,而Javascript错误地将其视为不同的子集。我能想到的唯一解决方案是将每个子集转换为Array,对其进行排序,然后编码为String(例如JSON)。

var toJsonSet = aset /* array or set */ => JSON.stringify([...new Set(aset)].sort()); 
var fromJsonSet = jset => new Set(JSON.parse(jset));

基本用法

var toJsonSet = aset /* array or set */ => JSON.stringify([...new Set(aset)].sort()); 
var fromJsonSet = jset => new Set(JSON.parse(jset));

var [s1,s2] = [new Set([1,2,3]), new Set([3,2,1])];
var [js1,js2] = [toJsonSet([1,2,3]), toJsonSet([3,2,1])]; // even better

var r = document.querySelectorAll("td:nth-child(2)");
r[0].innerHTML = (toJsonSet(s1) === toJsonSet(s2)); // true
r[1].innerHTML = (toJsonSet(s1) == toJsonSet(s2)); // true, too
r[2].innerHTML = (js1 === js2); // true
r[3].innerHTML = (js1 == js2); // true, too

// Make it normal Set:
console.log(fromJsonSet(js1), fromJsonSet(js2)); // type is Set
<style>td:nth-child(2) {color: red;}</style>

<table>
<tr><td>toJsonSet(s1) === toJsonSet(s2)</td><td>...</td></tr>
<tr><td>toJsonSet(s1) == toJsonSet(s2)</td><td>...</td></tr>
<tr><td>js1 === js2</td><td>...</td></tr>
<tr><td>js1 == js2</td><td>...</td></tr>
</table>

终极测试:套套

var toSet = arr => new Set(arr);
var toJsonSet = aset /* array or set */ => JSON.stringify([...new Set(aset)].sort()); 
var toJsonSet_WRONG = set => JSON.stringify([...set]); // no sorting!

var output = document.getElementsByTagName("code"); 
var superarray = [[1,2,3],[1,2,3],[3,2,1],[3,6,2],[4,5,6]];
var superset;

Experiment1:
    superset = toSet(superarray.map(toSet));
    output[0].innerHTML = superset.size; // incorrect: 5 unique subsets
Experiment2:
    superset = toSet([...superset].map(toJsonSet_WRONG));
    output[1].innerHTML = superset.size; // incorrect: 4 unique subsets
Experiment3:
    superset = toSet([...superset].map(toJsonSet));
    output[2].innerHTML = superset.size; // 3 unique subsets
Experiment4:
    superset = toSet(superarray.map(toJsonSet));
    output[3].innerHTML = superset.size; // 3 unique subsets
code {border: 1px solid #88f; background-color: #ddf; padding: 0 0.5em;}
<h3>Experiment 1</h3><p>Superset contains 3 unique subsets but Javascript sees <code>...</code>.<br>Let’s fix this... I’ll encode each subset as a string.</p>
<h3>Experiment 2</h3><p>Now Javascript sees <code>...</code> unique subsets.<br>Better! But still not perfect.<br>That’s because we didn’t sort each subset.<br>Let’s sort it out...</p>
<h3>Experiment 3</h3><p>Now Javascript sees <code>...</code> unique subsets. At long last!<br>Let’s try everything again from the beginning.</p>
<h3>Experiment 4</h3><p>Superset contains 3 unique subsets and Javascript sees <code>...</code>.<br><b>Bravo!</b></p>


1
很好的解决方案!而且,如果您知道只有一组字符串或数字,它就变成了[...set1].sort().toString() === [...set2].sort().toString()

3

您的方法返回false的原因是因为您正在比较两个不同的对象(即使它们具有相同的内容),因此比较两个不同的对象(不是引用,而是对象)总是会使您错误。

以下方法将两个集合合并为一个,然后愚蠢地比较大小。如果相同,则相同:

const a1 = [1,2,3];
const a2 = [1,3,2];
const set1 = new Set(a1);
const set2 = new Set(a2);

const compareSet = new Set([...a1, ...a2]);
const isSetEqual = compareSet.size === set2.size && compareSet.size === set1.size;
console.log(isSetEqual);

好处:这很简单而且简短。没有外部库,只有香草JS

缺点:这可能比仅遍历值更慢,并且需要更多空间。


1

用==,===比较两个对象

当使用=====运算符比较两个对象时,false 除非这些对象引用相同的对象,否则您总是会得到的。例如:

var a = b = new Set([1,2,3]); // NOTE: b will become a global variable
a == b; // <-- true: a and b share the same object reference

否则,即使对象包含相同的值,==也等于false:

var a = new Set([1,2,3]);
var b = new Set([1,2,3]);
a == b; // <-- false: a and b are not referencing the same object

您可能需要考虑手动比较

在ECMAScript 6中,您可以预先将集转换为数组,以便发现它们之间的差异:

function setsEqual(a,b){
    if (a.size !== b.size)
        return false;
    let aa = Array.from(a); 
    let bb = Array.from(b);
    return aa.filter(function(i){return bb.indexOf(i)<0}).length==0;
}

注意:这 Array.from是标准ECMAScript 6功能之一,但在现代浏览器中未得到广泛支持。在此处检查兼容性表:https : //developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/from#Browser_compatibility


1
这不是无法识别b其中哪些成员不在a吗?

1
@torazaburo确实。跳过检查成员b是否不在的最佳方法a是检查是否a.size === b.size
Aadit M Shah 2015年

1
a.size === b.size如果没有必要先短路各个元素的比较呢?

2
如果大小不同,则根据定义,集合不相等,因此最好先检查该条件。

1
好吧,这里的另一个问题是,由于集合的性质,集合上的has操作被设计为非常高效,这与indexOf数组上的操作不同。因此,将您的过滤器功能更改为是有意义的return !b.has(i)。这也将消除转换b为数组的需要。

1

我为Set.prototype.isEqual()创建了一个快速的polyfill

Set.prototype.isEqual = function(otherSet) {
    if(this.size !== otherSet.size) return false;
    for(let item of this) if(!otherSet.has(item)) return false;
    return true;
}

Github Gist-Set.prototype.isEqual


1

根据公认的答案,假设支持Array.from,则采用单行代码:

function eqSet(a, b) {
    return a.size === b.size && Array.from(a).every(b.has.bind(b));
}

或一个真正的单线假设箭头功能和散布算子: eqSet = (a,b) => a.size === b.size && [...a].every(b.has.bind(b))
John Hoffer '18

1

如果集合仅包含原始数据类型,或者集合内部的对象具有引用相等性,则有更简单的方法

const isEqualSets = (set1, set2) => (set1.size === set2.size) && (set1.size === new Set([...set1, ...set2]).size);


0

我在测试中遵循这种方法:

let setA = new Set(arrayA);
let setB = new Set(arrayB);
let diff = new Set([...setA].filter(x => !setB.has(x)));
expect([...diff].length).toBe(0);

5
请稍等...这仅检查A是否具有B没有的元素?它不会检查B是否具有A所没有的元素。如果尝试使用a=[1,2,3]b=[1,2,3,4],则说明它们是相同的。因此,我想您需要像这样的额外检查setA.size === setB.size

0

根据@Aadit M Shah的答案进行了非常小的修改:

/**
 * check if two sets are equal in the sense that
 * they have a matching set of values.
 *
 * @param {Set} a 
 * @param {Set} b
 * @returns {Boolean} 
 */
const areSetsEqual = (a, b) => (
        (a.size === b.size) ? 
        [...a].every( value => b.has(value) ) : false
);

如果由于最新babel的一些怪异而导致我遇到其他问题,则必须在此处添加一个明确的条件。

(对于复数,我认为are大声朗读也更直观)


-1

1)检查尺寸是否相等。如果不是,则它们不相等。

2)遍历A的每个元素,并检查B中是否存在。如果失败,则返回 unequal

3)如果上述2个条件失败,则表示它们相等。

let isEql = (setA, setB) => {
  if (setA.size !== setB.size)
    return false;
  
  setA.forEach((val) => {
    if (!setB.has(val))
      return false;
  });
  return true;
}

let setA = new Set([1, 2, {
  3: 4
}]);
let setB = new Set([2, {
    3: 4
  },
  1
]);

console.log(isEql(setA, setB));

2)方法2

let isEql = (A, B) => {
  return JSON.stringify([...A].sort()) == JSON.stringify([...B].sort());
}

let res = isEql(new Set([1, 2, {3:4}]), new Set([{3:4},1, 2]));
console.log(res);


这个答案是完全错误的。方法中的return语句forEach不会使父函数返回。
xaviert
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.