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

如何为键为std::pair的std::unordered_map编写顺序无关哈希函数

问题描述

我正在尝试创建一个以std::pair为键、size_t为值的std::unordered_map,核心需求是自定义哈希函数,使其忽略键std::pair两个成员的顺序,即满足如下逻辑:

std::pair<int,int> p1 = std::make_pair(3,4);
std::pair<int,int> p2 = std::make_pair(4,3);
std::unordered_map<std::pair<int,int>, int> m;
m[p1] = 3;
// m[p2] 此时也应该返回3!

以下是我从实际程序中截取的相关代码片段,并非完整的最小可运行示例:

#include <vector>
#include <string>
#include <iostream>
#include <algorithm>
#include <memory>
#include <unordered_map>
#include <functional>


class Point
{
public:
    static size_t id_counter;
    size_t id;
    Point()=default;
    ~Point()=default;
    bool operator==(const Point& rhs)
    {
        return id == rhs.id;
    }
    friend std::ostream& operator<<(std::ostream& os, Point& P);
};
size_t Point::id_counter = 0;

class Hasch_point_pair
{
public:
    size_t operator()(const std::pair<Point*, Point*>* p) const
    {
        // XOR哈希,不在乎碰撞,随便写的
        auto h1 = std::hash<size_t>()(p->first->id);
        auto h2 = std::hash<size_t>()(p->second->id);
        return h1^h2;
    }
};


int main(int argc, char const *argv[])
{
    auto p1 = std::make_unique<Point>();
    auto p2 = std::make_unique<Point>();
    auto p3 = std::make_unique<Point>();
    auto p4 = std::make_unique<Point>();
    std::unordered_map<std::pair<Point*, Point*>*, size_t*, Hasch_point_pair> m;
    auto p  = std::make_unique<std::pair<Point*, Point*>>(p1.get(),p2.get());
    auto p_hmm  = std::make_unique<std::pair<Point*, Point*>>(p2.get(),p1.get());
    size_t value = 3;
    m[p.get()] = &value;
    std::cout << "m[p] = " << m.at(p.get()) << std::endl;
    std::cout << "m[p_hmm] = " << m.at(p_hmm.get()) << std::endl;
}

我曾想到一个实现思路:先比较pair中两个Point对象的id,始终将id更大的Point作为哈希计算的第一个输入,但目前没能成功实现,想请问这个思路是否合理,应当如何正确实现该需求?
我尝试修改的哈希函数代码如下:

class Hasch_point_pair
{
public:
    size_t operator()(const std::pair<Point*, Point*>* p) const
    {
        if (p->first->id > p->second->id)
        {
            auto h1 = std::hash<size_t>()(p->first->id);
            auto h2 = std::hash<size_t>()(p->second->id);
            return h1^h2;
        }
        else
        {
            // 注意这里交换了h1和h2的顺序
            auto h2 = std::hash<size_t>()(p->first->id);
            auto h1 = std::hash<size_t>()(p->second->id);
            return h1^h2;
        }
    }
};
解答

你提出的「先排序pair内两个元素,固定顺序后再计算哈希」的思路完全合理,但现有代码无法生效,核心问题有两个,和哈希计算的核心逻辑无关:

  1. 你只自定义了哈希函数,没有自定义键的相等判断逻辑
    unordered_map判定两个键为同一个,除了校验哈希值相等,还会调用键的==运算符做二次确认。你当前使用的键类型是std::pair<Point*, Point*>*即指针类型,默认的指针相等判断只比较地址是否相同——哪怕两个pair指向的内容完全一致,只要是不同的pair对象、地址不一样,就会被判定为不相等,自然查不到对应的值。
  2. 你写的交换顺序异或逻辑本质和直接h1^h2没有区别,因为异或运算满足交换律,a^b和b^a的计算结果完全一致。而且纯异或哈希的碰撞概率极高,比如(a,a)这类两个id相同的pair,哈希值永远为0,很容易触发哈希冲突。

正确实现步骤

  • 不要把pair指针当键,直接用std::pair<Point*, Point*>作为键类型,省去指针地址比较的额外问题。
  • 自定义哈希函数时,先把pair里的两个指针按id大小排序,固定顺序后再做哈希组合,不要用纯异或,改用移位混合的方式降低碰撞概率。
  • 为该pair类型自定义对应的相等判断逻辑,只要两个pair里存储的Point指针集合一致(不考虑顺序),就判定为键相等。

修正后的可运行核心代码如下:

#include <iostream>
#include <memory>
#include <unordered_map>
#include <functional>

class Point
{
public:
    static inline size_t id_counter = 0;
    size_t id;
    Point() : id(id_counter++) {}
    ~Point()=default;
    bool operator==(const Point& rhs) const
    {
        return id == rhs.id;
    }
};

// 无序pair的哈希函数
struct HashUnorderedPointPair
{
    size_t operator()(const std::pair<Point*, Point*>& p) const
    {
        size_t h1, h2;
        // 固定计算顺序:id小的放前面,id大的放后面
        if (p.first->id < p.second->id) {
            h1 = std::hash<size_t>()(p.first->id);
            h2 = std::hash<size_t>()(p.second->id);
        } else {
            h1 = std::hash<size_t>()(p.second->id);
            h2 = std::hash<size_t>()(p.first->id);
        }
        // 哈希组合,碰撞概率远低于纯异或
        return h1 ^ (h2 << 1);
    }
};

// 无序pair的相等判断
struct EqUnorderedPointPair
{
    bool operator()(const std::pair<Point*, Point*>& a, const std::pair<Point*, Point*>& b) const
    {
        // 不考虑顺序,只要两个pair包含的两个Point id完全一致就算相等
        return (a.first->id == b.first->id && a.second->id == b.second->id)
            || (a.first->id == b.second->id && a.second->id == b.first->id);
    }
};

int main()
{
    auto p1 = std::make_unique<Point>(); // id=0
    auto p2 = std::make_unique<Point>(); // id=1
    std::unordered_map<std::pair<Point*, Point*>, size_t, HashUnorderedPointPair, EqUnorderedPointPair> m;
    
    std::pair<Point*, Point*> key1 = {p1.get(), p2.get()};
    std::pair<Point*, Point*> key2 = {p2.get(), p1.get()};
    
    m[key1] = 3;
    std::cout << "m[key1] = " << m[key1] << std::endl; // 输出3
    std::cout << "m[key2] = " << m[key2] << std::endl; // 同样输出3,符合预期
    return 0;
}

补充说明:如果后续需要支持空指针场景,只需要在排序和相等判断逻辑里给空指针指定固定排序优先级(比如空指针永远排在最前面)即可,核心逻辑不变。

内容的提问来源于stack exchange,提问作者Johan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 02:01:07