【问题标题】:comparing ECMA6 sets for equality比较 ECMA6 集是否相等
【发布时间】:2015-09-16 16:39:28
【问题描述】:

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

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

这两个集合是等价的,因为根据定义,sets do not have order(至少通常不会)。我查看了the documentation for Set on MDN 并没有发现任何有用的东西。有人知道怎么做吗?

【问题讨论】:

  • 两个集合是两个不同的对象。 === 用于值相等,而不是对象相等。
  • 迭代并比较每个成员的值,如果都相同,则设置为“相同”
  • @dandavis 对于集合,成员值。
  • Sets 和 Maps 确实有一个顺序,即插入顺序 - 无论出于何种原因:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
  • 最糟糕的是,甚至是new Set([1,2,3]) != new Set([1,2,3])。这使得 Javascript Set 对于 sets of sets 毫无用处,因为超集将包含重复的子集。想到的唯一解决方法是将所有子集转换为数组,对每个数组进行排序,然后将每个数组编码为字符串(例如 JSON)。

标签: javascript set ecmascript-6


【解决方案1】:

Ramda : equals(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>

【讨论】:

    【解决方案2】:

    你也可以试试:

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

    【讨论】:

    • 绝对是一个更好的解决方案,因为它适合 if 条件
    • 我喜欢这个解决方案的惯用性和可读性!谢谢@Max
    • 感谢您让我发现Array.every。但是由于它不修改a,为什么要使用[...a],进行复制?
    • every() 是数组 API 的一部分,在 Set 或 Iterator 上不存在
    【解决方案3】:

    可能有点晚了,但我通常会这样做:

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

    【讨论】:

      【解决方案4】:

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

      以下方法将两组合并为一组,只是愚蠢地比较大小。如果相同,则相同:

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

      优势:非常简单和简短。没有外部库,只有 vanilla JS

      缺点:这可能会比仅迭代值要慢,并且您需要更多空间。

      【讨论】:

        【解决方案5】:

        根据@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 只是更直观地大声朗读?)

        【讨论】:

          【解决方案6】:

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

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

          【讨论】:

            【解决方案7】:

            根据接受的答案,假设支持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))
            【解决方案8】:

            我为 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

            【讨论】:

              【解决方案9】:

              这些解决方案都没有将预期的功能“带回”到数据结构中,例如 set of sets。 在当前状态下,Javascript Set 无法用于此目的,因为超集将包含重复的子集,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>

              【讨论】:

              • 很好的解决方案!如果你知道你刚刚得到了一组字符串或数字,它就会变成[...set1].sort().toString() === [...set2].sort().toString()
              • 不幸的是,我现在没有时间查看这个,但是大多数使用内置默认值对 js 集合的键进行排序的解决方案,即 .sort() 是错误的,因为 js 对象没有总顺序,例如NaN!=NaN、'2'
              【解决方案10】:

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

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

              【讨论】:

              • 等一下...这仅检查 A 是否具有 B 没有的元素?它不检查 B 是否具有 A 没有的元素。如果您尝试a=[1,2,3]b=[1,2,3,4],那么它会说它们是相同的。所以我猜你需要一个额外的检查,比如setA.size === setB.size
              【解决方案11】:

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

              【讨论】:

              • 这个答案完全不正确。 forEach 方法中的 return 语句不会使父函数返回。
              【解决方案12】:

              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
              

              【讨论】:

                【解决方案13】:

                试试这个:

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

                希望对您有所帮助。

                【讨论】:

                • 我觉得你应该把has的名字改成isPartOf或者isIn或者elem
                • @Bergi 我把函数的名字has改成了isIn
                • @DavidGiven 是的,JavaScript 中的集合按插入顺序迭代:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
                • 每天我都更加确信 JavaScript 是有史以来最糟糕的语言。每个人都必须发明自己的基本功能来应对它的局限性,这是在 ES6 中,我们在 2017 年!为什么他们不能在 Set 对象的规范中添加如此频繁的功能!
                • @GhasanAl-Sakkaf,我同意,可能 TC39 由科学家组成,但没有实用主义者......
                【解决方案14】:

                另一个答案可以正常工作;这是另一种选择。

                // 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 的一个完全相等的成员。如果找到,请将其删除,以免再次使用。如果 s1 中的所有元素都在 s2 中找到,则这两个集合是完全相等的,并且 s2 已用尽。未经测试。

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

                【讨论】:

                  【解决方案15】:

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

                  当使用===== 运算符比较两个对象时,您将始终得到false unless those object reference the same object。例如:

                  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/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from#Browser_compatibility

                  【讨论】:

                  • 这不会无法识别不在a 中的b 成员吗?
                  • @torazaburo 确实。跳过检查b 的成员是否不在a 中的最好方法是检查a.size === b.size 是否。
                  • 如果没有必要,把a.size === b.size放在前面,以缩短单个元素的比较?
                  • 如果大小不同,根据定义,集合不相等,因此最好先检查该条件。
                  • 嗯,这里的另一个问题是,由于集合的性质,集合上的 has 操作被设计为非常有效,这与数组上的 indexOf 操作不同。因此,将过滤器函数更改为return !b.has(i) 是有意义的。这也将消除将b 转换为数组的需要。
                  猜你喜欢
                  • 2015-03-13
                  • 1970-01-01
                  • 1970-01-01
                  • 2013-08-18
                  • 2016-10-20
                  • 1970-01-01
                  • 1970-01-01
                  • 2021-09-14
                  相关资源
                  最近更新 更多