简体   繁体   English

比较 ECMA6 集合是否相等

[英]comparing ECMA6 sets for equality

How do you compare two javascript sets?你如何比较两个 javascript 集? I tried using == and === but both return false.我尝试使用=====但都返回 false。

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

These two sets are equivalent, because by definition, sets do not have order (at least not usually).这两个集合是等价的,因为根据定义,集合没有顺序(至少通常没有)。 I've looked at the documentation for Set on MDN and found nothing useful.我查看了 MDN 上 Set 的文档,但没有发现任何有用的信息。 Anyone know how to do this?有人知道怎么做吗?

Try this:尝试这个:

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

A more functional approach would be:更实用的方法是:

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

The all function works for all iterable objects (eg Set and Map ). all函数适用于所有可迭代对象(例如SetMap )。

If Array.from was more widely supported then we could have implemented the all function as:如果Array.from得到更广泛的支持,那么我们可以将all函数实现为:

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

Hope that helps.希望有帮助。

You can also try:你也可以试试:

 var a = new Set([1,2,3]); var b = new Set([1,3,2]); let areSetsEqual = (a, b) => a.size === b.size && [...a].every(value => b.has(value)); console.log(areSetsEqual(a,b))

lodash provides _.isEqual() , which does deep comparisons. lodash提供_.isEqual() ,它进行深度比较。 This is very handy if you don't want to write your own.如果您不想自己编写,这非常方便。 As of lodash 4, _.isEqual() properly compares Sets.从 lodash 4 开始, _.isEqual() . _.isEqual()正确地比较了 Sets。

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

None of these solutions bring “back” the expected functionality to a data structure such as set of sets.这些解决方案都没有将预期的功能“恢复”到诸如集合之类的数据结构中。 In its current state, the Javascript Set is useless for this purpose because the superset will contain duplicate subsets, which Javascript wrongly sees as distinct.在其当前状态下,Javascript 对于此目的是无用的,因为超集将包含重复的子集,Javascript 错误地将其视为不同的子集。 The only solution I can think of is converting each subset to Array , sorting it and then encoding as String (for example JSON).我能想到的唯一解决方案是将每个子集转换为Array ,对其进行排序,然后编码为String (例如 JSON)。

Solution解决方案

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

Basic usage基本用法

 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>

Ultimate test: set of sets终极测试:套组

 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>

The other answer will work fine;另一个答案可以正常工作; here is another alternative.这是另一种选择。

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

However, be aware that this does not do deep equality comparison.但是,要知道,这不会做深相等比较。 So所以

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

will return false.将返回假。 If the above two sets are to be considered equal, we need to iterate through both sets doing deep quality comparisons on each element.如果要认为上述两个集合相等,我们需要遍历两个集合,对每个元素进行深度质量比较。 We stipulate the existence of a deepEqual routine.我们规定了一个deepEqual例程的存在。 Then the logic would be那么逻辑将是

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

What this does: for each member of s1, look for a deeply equal member of s2.这是做什么的:对于 s1 的每个成员,寻找 s2 的一个非常平等的成员。 If found, delete it so it can't be used again.如果找到,请将其删除,以免再次使用。 The two sets are deeply equal if all the elements in s1 are found in s2, and s2 is exhausted.如果 s1 中的所有元素都在 s2 中找到,并且s2 已用尽,则这两个集合深度相等。 Untested.未经测试。

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

Maybe a little late, but I usually do the following:也许有点晚了,但我通常会做以下事情:

 const a = new Set([1,2,3]); const b = new Set([1,3,2]); // option 1 console.log(a.size === b.size && new Set([...a, ...b]).size === a.size) // option 2 console.log([...a].sort().join() === [...b].sort().join())

Very slight modification based on @Aadit M Shah's answer:基于@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
);

If anyone else is having an issue as I did due to some quirk of the latest babel, had to add an explicit conditional here.如果其他人因为最新 babel 的一些怪癖而像我一样遇到问题,则必须在此处添加明确的条件。

(Also for plural I think are is just a bit more intuitive to read aloud 🙃) (对于复数,我认为are只是大声朗读更直观一些🙃)

The reason why your approach returns false is because you are comparing two different objects (even if they got the same content), thus comparing two different objects (not references, but objects) always returns you falsy.您的方法返回 false 的原因是因为您正在比较两个不同的对象(即使它们具有相同的内容),因此比较两个不同的对象(不是引用,而是对象)总是会返回 false。

The following approach merges two sets into one and just stupidly compares the size.以下方法将两组合并为一组,只是愚蠢地比较大小。 If it's the same, it's the same:如果相同,则相同:

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

Upside : It's very simple and short.好处:它非常简单和简短。 No external library only vanilla JS没有外部库只有vanilla JS

Downside : It's probably going to be a slower than just iterating over the values and you need more space.缺点:它可能比仅迭代值要慢,并且您需要更多空间。

Comparing two objects with ==, ===用 ==, === 比较两个对象

When using == or === operator to compare two objects, you will always get false unless those object reference the same object .当使用=====运算符比较两个对象时, 除非这些对象引用相同的对象,否则您将始终得到false For example:例如:

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

Otherwise, == equates to false even though the object contains the same values:否则,即使对象包含相同的值, == 也等于 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

You may need to consider manual comparison您可能需要考虑手动比较

In ECMAScript 6, you may convert sets to arrays beforehand so you can spot the difference between them:在 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;
}

NOTE: Array.from is one of the standard ECMAScript 6 features but it is not widely supported in modern browsers.注意: Array.from是 ECMAScript 6 的标准特性之一,但在现代浏览器中并未得到广泛支持。 Check the compatibility table here : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from#Browser_compatibility在此处检查兼容性表: https : //developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from#Browser_compatibility

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

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

I follow this approach in tests :我在测试中遵循这种方法:

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

I created a quick polyfill for Set.prototype.isEqual()我为 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 Github Gist - Set.prototype.isEqual

Based on the accepted answer, assuming support of Array.from , here is a one-liner:根据接受的答案,假设支持Array.from ,这里是一个单行:

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

With Ramda : equals(set1, set2)使用Ramdaequals(set1, set2)

 const s1 = new Set([1, 2, 3]); const s2 = new Set([3, 1, 2]); console.log( R.equals(s1, s2) );
 <script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js"></script>

I believe this is the most performant version because it's not creating a new array, it's using the Set's own iterator:我相信这是性能最高的版本,因为它没有创建新数组,而是使用 Set 自己的迭代器:

function isEqualSets(a, b) {
  if (a === b) return true;
  if (a.size !== b.size) return false;
  for (const value of a) if (!b.has(value)) return false;
  return true;
}

None of the existing answers check the Set's insertion order, so here's one that does.现有的答案都没有检查 Set 的插入顺序,所以这里有一个答案。 It uses lodash _.isEqualWith to do shallow checking (since _.isEqual does deep checking and is slow for sets )它使用 lodash _.isEqualWith进行浅层检查(因为_.isEqual深度检查并且对于集合来说很慢

import isEqualWith from 'lodash/isEqualWith';

export function setsAreEqual (setA: Set<unknown>, setB: Set<unknown>): boolean {
    return isEqualWith(setA, setB, (a: unknown, b: unknown) => {
        if (a === setA) return undefined;
        return a === b;
    });
}

1) Check if sizes are equal . 1) 检查大小是否相等。 If not, then they are not equal.如果不是,则它们不相等。

2) iterate over each elem of A and check in that exists in B. If one fails return unequal 2) 迭代 A 的每个元素并检查 B 中存在的元素。如果失败返回unequal

3) If the above 2 conditions fails that means they are equal. 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) Method 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);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM