关于 Map
和 Set
是两个抽象数据结构,Map
存储一个键值对集合,其中键不重复,Set
存储一个不重复的元素集合。本质上 Set
可以视为一种特殊的 Map
,Set
其实就是 Map
中的键。
这里我们用 TypeScript 使用 Map
来实现 Set
。核心的思路非常简单,创建一个 Map
,我们只要用 Map
的键就行,键对应的值全部填充 undefined
:
class NewSet<T extends unknown> {
private collection: Map<T, undefined>;
constructor(iterable: T[] = []) {
this.collection = new Map(
iterable.map(it => [it, undefined])
);
}
}
注意在 TS 中使用 参数默认值 的时候,TS 会自动将这个参数推导为可变参数,例如上面这个会推导为
NewSet<T>(iterable?: T[]): NewSet<T>
接来下补充一些添加、删除的方法:
class NewSet<T extends unknown> {
private collection: Map<T, undefined>;
constructor(iterable: T[] = []) {
this.collection = new Map(
iterable.map(it => [it, undefined])
);
}
public add(value: T): NewSet<T> {
this.collection.set(value, undefined);
return this;
}
public delete(value: T): boolean {
return this.collection.delete(value);
}
public has(value: T): boolean {
return this.collection.has(value);
}
public size(): number {
return this.collection.size;
}
}
注意原本
Set
的size
是一个属性,这里为了符合面向对象编程中属性私有、方法公有的原则,我们将size
变为了一个函数
到这里主要的功能已经都实现了,但是还差一个遍历元素的方法。我们知道 Set
对象提供了 keys
、values
、entries
三个迭代器,但实际上 keys
和 entries
都和 values
一致,因此我们只要实现 values
就行:
class NewSet<T extends unknown> {
private collection: Map<T, undefined>;
constructor(iterable: T[] = []) {
this.collection = new Map(
iterable.map(it => [it, undefined])
);
}
public add(value: T): NewSet<T> {
this.collection.set(value, undefined);
return this;
}
public delete(value: T): boolean {
return this.collection.delete(value);
}
public has(value: T): boolean {
return this.collection.has(value);
}
public size(): number {
return this.collection.size;
}
public *values() {
for (let [key,] of this.collection) {
yield key;
}
}
}
注意
Map
的默认迭代器是entries
,即遍历的是每一个 entry。如果想直接遍历 key,可以使用this.collection.keys()
当然迭代器除了自己实现,还有一种比较 hack 的方法是借用 map
的迭代器:
public values = this.collection.keys();
最后,我们知道 Set
对象是可迭代的,也就是可以被扩展运算符展开到数组中,或者可以使用 for...of
遍历。为了让我们实现的 Set
结构可迭代,我们需要实现 Symbol.iterator
接口,只要让它指向刚才实现的 values
方法即可,完整实现如下:
class NewSet<T extends unknown> {
private collection: Map<T, undefined>;
constructor(iterable: T[] = []) {
this.collection = new Map(
iterable.map(it => [it, undefined])
);
}
public add(value: T): NewSet<T> {
this.collection.set(value, undefined);
return this;
}
public delete(value: T): boolean {
return this.collection.delete(value);
}
public has(value: T): boolean {
return this.collection.has(value);
}
public size(): number {
return this.collection.size;
}
public *values() {
for (let [key,] of this.collection) {
yield key;
}
}
public [Symbol.iterator] = this.values;
}