TypeScript中如何让对象在Map/Set中实现Java式的去重?
问题原因
TypeScript/JavaScript 中的 Set 默认采用 SameValueZero 规则判断元素是否相等:对于对象类型,仅比较引用是否一致——哪怕两个对象的属性值完全相同,只要是不同实例,就会被视为独立元素。你自定义的 hashCode 和 toString 方法不会被 Set 的默认逻辑调用,因此添加两个内容相同的 Coord 实例后,size 仍为 2。
解决方案
方案1:转换为原始值存入Set(简单直接)
如果不需要在 Set 中保存 Coord 对象本身,可以将对象转换为唯一的原始值(如字符串),利用原始值的相等性实现去重:
class Coord { x: number; y: number; constructor(x: number, y: number) { this.x = x; this.y = y; } // 生成唯一标识字符串,避免哈希碰撞 toUniqueKey(): string { return `${this.x},${this.y}`; } } const f = () => { let visited: Set<string> = new Set(); visited.add(new Coord(1, 1).toUniqueKey()); visited.add(new Coord(1, 1).toUniqueKey()); console.log(visited.size); // 输出1 } f()
方案2:自定义基于内容比较的Set(保留对象实例)
如果需要在 Set 中存储 Coord 对象,可以扩展 Set 类,重写 add 方法实现内容比对:
class Coord { x: number; y: number; constructor(x: number, y: number) { this.x = x; this.y = y; } // 自定义内容相等判断逻辑 equals(other: Coord): boolean { return this.x === other.x && this.y === other.y; } } class ContentSet<T extends {equals(other: T): boolean}> extends Set<T> { add(value: T): this { // 遍历检查是否已有内容相同的元素 for (const item of this) { if (item.equals(value)) return this; } return super.add(value); } } const f = () => { let visited: ContentSet<Coord> = new ContentSet(); visited.add(new Coord(1, 1)); visited.add(new Coord(1, 1)); console.log(visited.size); // 输出1 } f()
方案3:模拟Java的hashCode+equals逻辑(高效去重)
为了接近Java中「哈希分组+内容比对」的高效逻辑,可以结合 Map 按哈希值分组,减少遍历次数:
class Coord { x: number; y: number; constructor(x: number, y: number) { this.x = x; this.y = y; } // 更可靠的哈希计算,降低碰撞概率 hashCode(): number { let hash = 17; hash = hash * 31 + this.x; hash = hash * 31 + this.y; return hash; } equals(other: Coord): boolean { return this.x === other.x && this.y === other.y; } } class HashContentSet<T extends {hashCode(): number; equals(other: T): boolean}> extends Set<T> { private hashGroups = new Map<number, T[]>(); add(value: T): this { const hash = value.hashCode(); const group = this.hashGroups.get(hash) || []; // 仅在同哈希组内检查内容是否重复 if (!group.some(item => item.equals(value))) { group.push(value); this.hashGroups.set(hash, group); super.add(value); } return this; } } const f = () => { let visited: HashContentSet<Coord> = new HashContentSet(); visited.add(new Coord(1, 1)); visited.add(new Coord(1, 1)); console.log(visited.size); // 输出1 } f()
注意:你最初的
hashCode公式存在哈希碰撞问题(比如Coord(2,0)和Coord(1,1)的哈希值均为3),建议使用方案3中的哈希计算方式降低碰撞概率。
内容的提问来源于stack exchange,提问作者mertvy
相关产品推荐
相关产品推荐

