在ES6中存储数组设置并按值访问它们

时间:2015-04-21 00:12:37

标签: javascript arrays set ecmascript-6

是否有一种简单的方法可以验证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

4 个答案:

答案 0 :(得分:12)

没有。

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提案中被提及,但它似乎不会很快到来。

答案 1 :(得分:2)

它不使用Set,但可以解决您的问题。

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

灵感来自: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]));

答案 2 :(得分:1)

显然,set.has()看起来不像这样检查数组元素。然而,它们被添加:

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

答案 3 :(得分:0)

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

我喜欢 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() 的性能更高,但这需要测试。

好:

  • 比内置的更快

不好:

  • 嵌套数组的问题
  • 分隔符未出现在值中的问题