【问题标题】:Storing arrays in ES6 Set and accessing them by value在 ES6 Set 中存储数组并按值访问它们
【发布时间】:2015-06-27 22:21:32
【问题描述】:

有没有一种简单的方法来验证 ES6 Set 是否包含一个特定数组的值?我想要一个不需要我使用参考的解决方案:

var set = new Set();

var array = [1, 2];
set.add(array);
set.has(array); // true

set.add([3, 4]);
set.has([3, 4]); // false

到目前为止,我的解决方案是将所有内容存储为字符串,但这很烦人:

set.add([3, 4].toString());
set.has([3, 4].toString()); // true

【问题讨论】:

  • 似乎添加文字数组会有所不同,例如:jsfiddle.net/BloodyKnuckles/hm0b9j76/1
  • 我建议不要使用.toString,而是使用JSON.stringify()
  • @bloodyKnuckles 您的示例“看起来”正在工作,因为它正在检查相同的参考。如果您不是在寻找引用并传入一个具有相同值的新数组,它将找不到它。

标签: javascript arrays set ecmascript-6


【解决方案1】:

没有。

Set 适用于对象和基元,可用于防止相同的基元和重新添加相同的对象实例。

每个数组都是自己的对象,所以实际上可以添加两个具有相同值的不同数组。

var set = new Set();
set.add([3, 4]);
set.add([3, 4]);
console.log(set.size);//2

此外,没有什么可以阻止对象在集合中被更改一次。

var set = new Set();
var a1 = [3, 4];
var a2 = [3, 4];
set.add(a1);
set.add(a2);
a2.push(5);
for (let a of set) {
    console.log(a);
}
//Outputs:
// [3, 4]
// [3, 4, 5]

一个集合does not have a mechanism 用于检查集合中对象的值。由于一个对象的值可以随时改变,它不会比自己简单地循环它们更有效。

您正在寻找的功能已在各种 ECMAScript 提案中被提及,但它似乎不会很快出现。

【讨论】:

  • 这似乎破坏了我期望从集合中获得的许多功能......例如,使用有序对,或使用包含其他集合的集合。我最初的计划是实现一种查找两组笛卡尔积的方法,但看起来我必须将有序对存储为我可以唯一识别的东西。
【解决方案2】:

显然,set.has() 不会像那样检查数组元素。但是,它们已添加:

var set = new Set([1, 2]);
set.add([3, 4]);
console.log(Array.from(set));

【讨论】:

    【解决方案3】:

    它没有使用Set,但可能会解决您的问题。

    如果您想确保一个数组只能在一个集合中存在一次,并且您想要即时查找,您可以通过滥用其键来使用哈希。它们不一定需要一个值。您可以将它们设置为 undefinedtrue 或其他值,如果它能让您晚上睡得更好?。

    灵感来自:javascript search array of arrays

    制作一个包装器很有诱惑力,但它也足够简单,可以处理一项小工作。

    const hash = {};
    
    hash[[1, 2, 3]] = undefined;
    hash[[3, 4]] = undefined;
    hash[[3, 4]] = undefined;
    
    console.log({hash});
    console.log("hash has [3, 4]?", hash.hasOwnProperty([3, 4]));

    对我来说,这可以正常工作,因为我正在检查集合中是否存在坐标。不幸的是,从stackoverflow 看来,Set 似乎不能解决这个问题。

    巧合的是,它似乎隐含地解决了您没有在数组上调用toString 的问题,这似乎是在幕后进行的。我个人不关心hash.hasOwnProperty

    更优雅的选择

    您甚至可以为您的代码编写一个包装器,例如:

    class ArraySet extends Set {
      add(arr) {
        super.add(arr.toString());
      }
      has(arr) {
        return super.has(arr.toString());
      }
    }
    
    const arraySet = new ArraySet();
    
    arraySet.add([1, 2]);
    arraySet.add([3, 4]);
    arraySet.add([3, 4]);
    
    console.log("ArraySet has [3, 4]?", arraySet.has([3, 4]));

    【讨论】:

    • 答案中值得强调的是hash[[3, 4]] 被转换为字符串hash['3,4'][3, 4] in hash 似乎也能正常工作,顺便说一句,而不是 hasOwnProperty
    【解决方案4】:

    代表太低,无法添加评论,所以我在这里添加它作为答案。 提前为文字墙道歉。

    我喜欢 CTS_AE 的回答,也想走同样的路。 但是,有一点需要注意,那就是数组是如何放入 String 的。

    let memory = {};
    memory[[1,2,3]] = "123";
    console.log(memory[[1,2,3]]); // "123"
    console.log([1,2,3].toString(); // "1,2,3"
    console.log(memory["1,2,3"]); // "123"
    

    如果您知道自己放入的内容完全是数组,那么这不会是一个问题......或者会这样吗?

    关于 Array.prototype.toString() 的 MDN 说

    对于 Array 对象,toString 方法连接数组并返回一个字符串,该字符串包含每个数组元素,以逗号分隔。

    这带来了两个大问题:

    • 通过array 索引与通过array.toSring() 索引相同
    • 由于toString() 是递归的,嵌套数组最终会被字符串化为与单级数组相同的格式。
    let memory = {};
    memory[[1,2,3]] = "123";
    console.log(memory[[1,2,3]]); // "123"
    console.log(memory["1,2,3"]); // "123"
    console.log(memory[[[1],[2],[3]]]); // "123"
    console.log(memory[[[[1],2],3]]); // "123"
    

    ...列表还在继续。不难看出这些问题何时会真正破坏您的项目。前段时间我在尝试记忆时遇到了这样的问题

    function doSomeStuff(string, sourceIdxs, targetIdxs) {
        if (memo[[string, sourceIdxs, targetIdxs]])
            return memo[[string, sourceIdxs, targetIdxs]];
        // ...
    }
    

    在这种情况下,例如 ["foo", [1, 3, 5], [6, 10]]["foo", [1, 3], [5, 6, 10]] 指向相同的值,我最终覆盖了现有值,从而有效地破坏了函数内存。

    现在,在上述ArraySet 答案的特定情况下,问题仍然存在。虽然您不介意用另一个“相同”密钥覆盖现有密钥,但最终可能会出现误报。

    那么如何解决这个问题?

    选项 1. 字符串化

    简单的方法是使用JSON.stringify() 编写所有关键数据的“精确”字符串表示。

    let memory = {};
    memory[JSON.stringify([1,2,3])] = "123";
    console.log(memory[JSON.stringify([1,2,3])]); // "123"
    console.log(memory[JSON.stringify([[1],[2,3]])); // undefined
    

    这有助于处理误报……有点。一方面,您不会遇到数组中重叠元素的问题。 [[1,2],[3]] 不再指向 [[1],[2,3]] 所在的位置。

    但是,[1,2,3]"[1,2,3]" 可以。此外(在某些奇怪的情况下),数组元素可能包含 [] 字符,这可能会使问题进一步复杂化。

    而且您可能不关心这种情况。您的钥匙可能仍然受到足够好的约束,不会发生此类事情。你甚至可能想要这样的行为。如果你这样做了,那就去吧。

    好:

    • 修复了ArraySet 的大部分问题
    • 易于实施

    不好:

    • JSON.stringify()相当慢。

    选项 2. 分隔符

    更简单、更快捷,同时与旧解决方案相比仍有一些优势。

    function toSepString(arr) { return arr.join("|") }
    let memory = {};
    memory[toSepString([[1,2],3])] = "123";
    console.log(memory[toSepString([1,[2,3]])]); // undefined
    

    当然,这只对“最外层”有帮助。

    console.log(toSepString([1,[2,[3]]])); // "1|2,3"
    console.log(toSepString([1,[[2],[[3]]]]); // "1|2,3"
    

    所以如果你使用它,你需要确保你的键数组的特定元素在转换为字符串时不会变得模棱两可。

    当然,您可以使函数递归并在每个开头和结尾添加"[", "]",实质上复制了JSON.stringify() 的部分功能。我想这会比直接调用JSON.stringify() 更高效,但这需要测试。

    好:

    • 比其内置的同类产品更快

    不好:

    • 嵌套数组的问题
    • 值中没有出现分隔符的问题

    【讨论】:

      【解决方案5】:

      使用对象键作为一组的 Janky 解决方案:

      let set = {};
      set[[1,4]] = true;
      set[[4,5]] = true;
      set[[4,5]] = true; // Already added, won't make a difference
      
      console.log(set); // "{ '1,4': true, '4,5': true }"
      
      let setAsArr = (Object.keys(set).map(a => a.split(",")));
      console.log(setAsArr) // [ [ '1', '4' ], [ '4', '5' ] ]
      

      这将让您创建一个“集合”,以防止重复的数组项条目并轻松访问项。


      注意:不适用于包含“,”字符的字符串数组

      【讨论】:

        猜你喜欢
        • 2017-10-13
        • 2023-03-12
        • 2013-12-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-07-13
        相关资源
        最近更新 更多