You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 11:20:37