如何创建一个包含元组的集合,其中每个元组必须是唯一的?



我正在使用TypeScript,想要创建一个对象的集合。每个对象都有一些属性。属性的组合在集合中必须是唯一的。

因此,这些示例组合将是有效的

[
[ 1, 2 ],
[ 2, 1 ],
]

但添加另一种组合,例如[ 1, 2 ]会抛出"密钥已存在"错误。

作为旁注:我的问题假设有 3 个键代表"复合键"。如果有更灵活的解决方案...为什么不呢。

我试图实现我自己的"类似地图"的结构作为JavaScript的展示

class MyCollection {
constructor() {
this.items = [];
}
add(firstTupleItem, secondTupleItem, thirdTupleItem) {
if (this.has(firstTupleItem, secondTupleItem, thirdTupleItem)) {
console.log(`ERR: Combination of [${firstTupleItem}, ${secondTupleItem}, ${thirdTupleItem}] already exists!`);
return;
}
console.log(`Added combination of [${firstTupleItem}, ${secondTupleItem}, ${thirdTupleItem}]`);
this.items.push([firstTupleItem, secondTupleItem, thirdTupleItem]);
}
has(firstTupleItem, secondTupleItem, thirdTupleItem) {
return this.items.some(item =>
item[0] === firstTupleItem &&
item[1] === secondTupleItem &&
item[2] === thirdTupleItem);
}
}
const myCollection = new MyCollection();
/* passes as expected */
myCollection.add(1, 2, 3);
myCollection.add(2, 1, 3);
myCollection.add(3, 1, 2);
myCollection.add(1, 3, 2);
/* fails as expected */
myCollection.add(1, 2, 3);
console.log(myCollection.items);

使用地图可能更快,但价值方面似乎被浪费了

class MyCustomMap extends Map<[number, number, number], [number, number, number]> {
addItem(item: [number, number, number]) {
super.set(item, item);
}
}

我是否必须自己实现这样的集合,或者是否有更好的解决方案?(使用打字稿)

你基本上想要一个Set,一个最多包含一个给定值的JavaScript集合;如果你add()到一个Set的值与Set中已经存在的值相同,则没有任何变化。 不幸的是,使两个值"相同"的定义并不是您想要的。Set和相关Map集合使用"同值零"相等。 对于像stringnumber这样的原语来说,这种相等性很好,但对于像[1, 2]这样的对象(是的,Arrays是JS中的对象),它相当于对象身份相等,类似于你得到的===(差异只有NaN左右):

const a = [1, 2];
const b = [1, 2];
console.log(a === b); // false
const c = a;
console.log(a === c); // true
const a = [1, 2];

这种说法是有道理的,特别是考虑到可能的属性写入:

a[1] = 100;
console.log(a); // [1, 100]
console.log(b); // [1, 2]
console.log(c); // [1, 100]

但是,由于您不打算保留数组引用并修改其内容(是吗?),因此您宁愿使用类似于您自己的自定义相等函数之类的函数,其中两个数组如果内容相等,则相等。

不幸的是,SetMap并不直接支持这一点。


如果你想要这样的东西,你需要自己实现它。 一种方法是提出一个函数f(),它将您的对象转换为原始键值,以便当且仅当o1o2应被视为"相等"时f(o1) === f(o2)。 对于基元数组执行此操作的最简单方法是使用JSON.stringify()

因此,如果您的对象属于Props类型:

type Props = [number, number, number];

那么转换函数f()可以写成propsToKey()

function propsToKey(props: Props): string {
return JSON.stringify(props);
}

现在,在您的类中,您保留了这些键的Set而不是对象。 或者,您可以保留由值为对象的键控的Map,因此您仍然可以根据需要返回原始对象。 你把你关心的每一个Set方法都包装在适当地调用propsToKey()的东西中。 哦,既然你似乎希望你的add()方法采用可变参数数(例如,3 表示[number, number, number])而不是数组,那么我们应该在适当的时候使用 rest/spread 语法。

好的,让我们这样做来实现MyCollection

class MyCollection {
private items: Map<string, Props> = new Map();
add(...props: Props) {
this.items.set(propsToKey(props), props);
return this;
}
clear() {
this.items.clear();
}
delete(...props: Props) {
return this.items.delete(propsToKey(props));
}
forEach(cb: (...props: Props) => void) {
return this.items.forEach(v => cb(...v));
}
has(...props: Props) {
return this.items.has(propsToKey(props));
}
get size() {
return this.items.size;
}
values() {
return this.items.values();
}
}

让我们测试一下:

const myCollection = new MyCollection();
myCollection.add(1, 2, 3);
myCollection.add(2, 1, 3);
myCollection.add(3, 1, 2);
myCollection.add(1, 3, 2);
console.log(Array.from(myCollection.values())) // [[1, 2, 3], [2, 1, 3], [3, 1, 2], [1, 3, 2]] 
myCollection.add(1, 2, 3);
console.log(Array.from(myCollection.values())) // [[1, 2, 3], [2, 1, 3], [3, 1, 2], [1, 3, 2]] 

看起来不错!

操场链接到代码

最新更新