我是 Angular2 和 TypeScript 的新手。我在创建像 Set<> 这样的独特集合时遇到问题。 我想避免集合中重复的对象,为此目的,尝试使用 一组数据类型,如以下代码:
private cardItems = new Set<MyBean>([]);
MyBean 是一个对象。
export class MyBean {
id:integer
ownerId:integer
ownerName:string
img: string;
constructor() {
}
public equals(obj: MyBean) {
console.log(obj.id);
if (this.id == obj.id) {
console.log(obj.id);
return true;
}
if (obj == null)
return false;
return true;
}
public hashCode(obj: MyBean) {
return obj.id
}
}
但是 equals 和 hashCode 不会以这种方式运行。并且我在 set 中有重复的对象。
Set的实现方案是什么?
非常感谢
如何扩展
Set
类,然后重写 add
方法:
interface SetItem {
equals(other: SetItem): boolean;
}
class MySet<T extends SetItem> extends Set<T> {
add(value: T): this {
let found = false;
this.forEach(item => {
if (value.equals(item)) {
found = true;
}
});
if (!found) {
super.add(value);
}
return this;
}
}
(操场上的代码)
实现Set的解决方案是什么
JavaScript
Set
使用与 ===
相同的算法,并且无法在 JavaScript 类中覆盖。
您可以使用利用可重写函数的东西,例如https://github.com/basarat/typescript-collections使用
toString
。
当两个对象相似时,您可以使用类似于比较的技术:
JSON.stringify(obj)
但是,这需要您在阅读时
JSON.parse()
您的集合中的成员,并且需要一些额外的打字。
const obj1 = {id: 1, data: 'blue'};
const obj2 = {id: 2, data: 'red'};
const obj3 = {id: 1, data: 'blue'};
const myStringifiedObjSet: Set<string> = new Set([]);
myStringifiedObjSet.add(JSON.stringify(obj1));
// Set { '{"id":1,"data":"blue"}' }
myStringifiedObjSet.add(JSON.stringify(obj2));
// Set { '{"id":1,"data":"blue"}', '{"id":2,"data":"red"}' }
myStringifiedObjSet.add(JSON.stringify(obj3));
// Set { '{"id":1,"data":"blue"}', '{"id":2,"data":"red"}' }
myStringifiedObjSet.has(JSON.stringify(obj1));
// true
const objArray: object[] = Array.from(myStringifiedObjSet).map(el => JSON.parse(el));
// [ { id: 1, data: 'blue' }, { id: 2, data: 'red' } ]
自定义
Set
实现还应该实现 has
等其他方法,并且应该保留 O(1) 查找和插入时间。
将
Set<string>
与 JSON.stringify(obj)
一起使用会丢失原始类型。
这是一种替代方案,使用 fast-equals 实现深度相等,使用 object-hash 实现 O(1) 查找和插入:
import { deepEqual } from 'fast-equals'
import objectHash, { type NotUndefined } from 'object-hash'
/**
* TypeScript compares objects by reference, not by value.
* The same behavior applies to Set, which is not unique by value.
* This Set implementation uses value equality and preserves O(1) lookup and insertion time.
*/
class ValueSet<T extends NotUndefined> implements Set<T> {
[Symbol.toStringTag]: string = 'ValueSet'
private hashMap = new Map<string, T>()
constructor(
items: T[] = [],
private toKey: (item: T) => string = objectHash,
) {
for (const item of items) {
this.add(item)
}
}
get size(): number {
return this.hashMap.size
}
has(item: T): boolean {
const hash = this.toKey(item)
const existingItem = this.hashMap.get(hash)
return deepEqual(item, existingItem)
}
add(item: T): this {
const hash = this.toKey(item)
if (!this.hashMap.has(hash)) {
this.hashMap.set(hash, item)
return this
}
const existingItem = this.hashMap.get(hash)
if (!deepEqual(item, existingItem)) {
this.hashMap.set(hash, item)
}
return this
}
delete(item: T): boolean {
const hash = this.toKey(item)
const existingItem = this.hashMap.get(hash)
if (deepEqual(item, existingItem)) {
return this.hashMap.delete(hash)
}
return false
}
clear(): void {
this.hashMap.clear()
}
forEach(
callbackfn: (value: T, value2: T, set: Set<T>) => void,
thisArg?: unknown,
): void {
this.hashMap.forEach(value => {
callbackfn.call(thisArg, value, value, this)
})
}
[Symbol.iterator](): SetIterator<T> {
return this.hashMap.values()
}
keys(): SetIterator<T> {
// Keys and values of a Set are the same thing.
return this.hashMap.values()
}
values(): SetIterator<T> {
return this.hashMap.values()
}
entries(): SetIterator<[T, T]> {
return new Set(this.hashMap.values()).entries()
}
}
export default ValueSet
这里有一些使用 vitest 进行的测试(与 jest 相同的语法):
import { describe, expect, it } from 'vitest'
import ValueSet from '../ValueSet'
interface SomeType {
foo: number
}
describe('ValueSet', () => {
describe('constructor', () => {
it('initializes the set with items', () => {
const set = new ValueSet<SomeType>([{ foo: 0 }, { foo: 1 }])
expect(set.size).toBe(2)
})
})
describe('size', () => {
it('returns the current number of items', () => {
const set = new ValueSet<SomeType>()
expect(set.size).toBe(0)
set.add({ foo: 0 })
expect(set.size).toBe(1)
set.delete({ foo: 0 })
expect(set.size).toBe(0)
})
})
describe('has', () => {
it('returns true for existing items', () => {
const set = new ValueSet<SomeType>([{ foo: 0 }])
expect(set.has({ foo: 0 })).toBe(true)
})
it('returns false for non-existent items', () => {
const set = new ValueSet<SomeType>([{ foo: 0 }])
expect(set.has({ foo: 1 })).toBe(false)
})
})
describe('add', () => {
it('adds unique items to the set', () => {
const set = new ValueSet<SomeType>([{ foo: 0 }])
set.add({ foo: 1 })
expect(set.size).toBe(2)
expect(set.has({ foo: 0 })).toBe(true)
expect(set.has({ foo: 1 })).toBe(true)
})
it('ignores duplicate items', () => {
const set = new ValueSet<SomeType>([{ foo: 0 }])
set.add({ foo: 0 })
expect(set.size).toBe(1)
expect(set.has({ foo: 0 })).toBe(true)
})
it('overwrites items with the same hash/key but different value', () => {
const set = new ValueSet<SomeType>([{ foo: 0 }], () => 'constant_hash')
set.add({ foo: 1 })
expect(set.size).toBe(1)
expect(set.has({ foo: 0 })).toBe(false)
expect(set.has({ foo: 1 })).toBe(true)
})
})
describe('delete', () => {
it('deletes existing items', () => {
const set = new ValueSet<SomeType>([{ foo: 0 }, { foo: 1 }])
expect(set.delete({ foo: 0 })).toBe(true)
expect(set.size).toBe(1)
expect(set.has({ foo: 0 })).toBe(false)
expect(set.has({ foo: 1 })).toBe(true)
})
it('ignores non-existent items', () => {
const set = new ValueSet<SomeType>([{ foo: 0 }])
expect(set.delete({ foo: 1 })).toBe(false)
expect(set.size).toBe(1)
expect(set.has({ foo: 0 })).toBe(true)
})
})
describe('clear', () => {
it('removes all items from the set', () => {
const set = new ValueSet<SomeType>([{ foo: 0 }, { foo: 1 }])
expect(set.size).toBe(2)
set.clear()
expect(set.size).toBe(0)
})
})
describe('forEach', () => {
it('preserves the order of insertion', () => {
const set = new ValueSet<SomeType>()
set.add({ foo: 0 }).add({ foo: 2 }).add({ foo: 1 })
const items: SomeType[] = []
set.forEach(value => {
items.push(value)
})
expect(items).toEqual([{ foo: 0 }, { foo: 2 }, { foo: 1 }])
})
})
describe('for-of', () => {
it('preserves the order of insertion', () => {
const set = new ValueSet<SomeType>()
set.add({ foo: 0 }).add({ foo: 2 }).add({ foo: 1 })
const items: SomeType[] = []
for (const item of set) {
items.push(item)
}
expect(items).toEqual([{ foo: 0 }, { foo: 2 }, { foo: 1 }])
})
})
describe('keys', () => {
it('returns all items', () => {
const items = [{ foo: 0 }, { foo: 1 }]
const set = new ValueSet<SomeType>(items)
expect(Array.from(set.keys())).toEqual(items)
})
})
describe('values', () => {
it('returns all items', () => {
const items = [{ foo: 0 }, { foo: 1 }]
const set = new ValueSet<SomeType>(items)
expect(Array.from(set.values())).toEqual(items)
})
})
describe('entries', () => {
it('returns the entries with the item as key and value', () => {
const set = new ValueSet<SomeType>([{ foo: 0 }, { foo: 1 }])
expect(Array.from(set.entries())).toEqual([
[{ foo: 0 }, { foo: 0 }],
[{ foo: 1 }, { foo: 1 }],
])
})
})
})