当使用向量作为键时,C++ unordered_map 失败

Posted

技术标签:

【中文标题】当使用向量作为键时,C++ unordered_map 失败【英文标题】:C++ unordered_map fail when used with a vector as key 【发布时间】:2012-05-11 09:54:21 【问题描述】:

背景:我来自 Java 世界,对 C++ 或 Qt 还很陌生。

为了玩 unordered_map,我写了以下简单的程序:

#include <QtCore/QCoreApplication>
#include <QtCore>
#include <iostream>
#include <stdio.h>
#include <string>
#include <unordered_map>

using std::string;
using std::cout;
using std::endl;
typedef std::vector<float> floatVector;

int main(int argc, char *argv[]) 
    QCoreApplication a(argc, argv);
    
    floatVector c(10);
    floatVector b(10);
    
    for (int i = 0; i < 10; i++) 
        c[i] = i + 1;
        b[i] = i * 2;
    
    
    std::unordered_map<floatVector, int> map;
    
    map[b] = 135;
    map[c] = 40;
    map[c] = 32;
  
    std::cout << "b -> " << map[b] << std::endl;
    std::cout << "c -> " << map[c] << std::endl;
    std::cout << "Contains? -> " << map.size() << std::endl;
    
    return a.exec();

不幸的是,我遇到了以下不鼓舞人心的错误。连行号都没有。

:-1: 错误:collect2: ld 返回 1 个退出状态

知道问题的根源吗?

【问题讨论】:

你需要一个带有vector&lt;float&gt;的哈希函数 这不是运行时故障。 @SethCarnegie 这就是我虽然问题也来了。但是,在我看来,像向量这样基本的类应该有一个默认的散列函数。如果不是这样,您能否解释一下如何提供一个或指向我一些材料。谢谢! 有效且有趣的问题,但我没有看到将列表用作地图中的键的用例。 @UmNyobe int 是大量计算的结果,其中向量是输入。计算后的结果需要多次快速访问。 【参考方案1】:

§23.2.5,第 3 段,说:

每个无序关联容器都由 Key、满足哈希要求 (17.6.3.4) 的函数对象类型 Hash 参数化,并充当 Key 类型的参数值的哈希函数,以及二元谓词PredKey 类型的值产生等价关系。

vector&lt;float&gt; 用作Key 并且不提供显式哈希和等价谓词类型意味着将使用默认的std::hash&lt;vector&lt;float&gt;&gt;std::equal_to&lt;vector&lt;float&gt;&gt;

等价关系的std::equal_to 很好,因为向量有一个运算符==,这就是std::equal_to 使用的。

但是,没有std::hash&lt;vector&lt;float&gt;&gt; 专门化,这可能就是您没有向我们显示的链接器错误所说的。您需要提供自己的哈希器才能使其正常工作。

编写此类哈希的一种简单方法是使用boost::hash_range

template <typename Container> // we can make this generic for any container [1]
struct container_hash 
    std::size_t operator()(Container const& c) const 
        return boost::hash_range(c.begin(), c.end());
    
;

然后你可以使用:

std::unordered_map<floatVector, int, container_hash<floaVector>> map;

当然,如果您需要在映射中使用不同的相等语义,则需要适当地定义散列和等价关系。


1。但是,对无序容器进行哈希处理时要避免这种情况,因为不同的顺序会产生不同的哈希值,并且无序容器中的顺序是不能保证的。

【讨论】:

非常感谢,这确实解决了我的问题。有同样问题的人注意:要使用 boost::hash_range 你需要#include @user1162647 :这实际上是该文档页面上的第一件事。 ;-] @R. Martinho Fernandes:如果你还在看,该页面中的文档说:“hash_range 对元素的顺序很敏感,因此不适合将它与无序容器一起使用。”这是否表明上述用法是错误的? @Dilip 我认为这意味着调用hash_range (unordered_container) 是个坏主意,因为它每次都会产生不同的结果。 @hash3r 这是因为 map 在后端使用了红黑树,并不关心存储的数据类型。而 unordered_map 本质上需要一个散列函数。并且只有在确定了向量中存储的数据类型后才能进行计算。【参考方案2】:

我发现 R. Martinho Fernandes 的答案不适合竞争性编程,因为大多数时候您必须处理提供的 IDE 并且不能使用外部库,例如 boost。如果您想充分利用 STL,可以使用以下方法。

如上所述,您只需要编写一个哈希函数。它应该专门用于存储在向量中的数据类型。下面的散列函数假设int 类型数据:

struct VectorHasher 
    int operator()(const vector<int> &V) const 
        int hash = V.size();
        for(auto &i : V) 
            hash ^= i + 0x9e3779b9 + (hash << 6) + (hash >> 2);
        
        return hash;
    
;

请注意,您可以使用任何类型的操作来生成哈希。您只需要发挥创造力,就可以最大限度地减少冲突。例如,hash^=V[i]hash|=V[i]hash+=V[i]*V[i] 甚至 hash+=(V[i]&lt;&lt;i)*(V[i]&lt;&lt;i)*(V[i]&lt;&lt;i) 都是有效的,当然,您的哈希不会溢出。

最后要将此哈希函数与您的unordered_map 一起使用,请按如下方式对其进行初始化:

unordered_map<vector<int>,string,VectorHasher> hashMap;

【讨论】:

第二个模板参数不应该是int而不是bool吗? @wcochran 它适用于任何允许的数据结构/类型(map、vector、set、queue、stack、int、float 等)。取决于您的用例。 如果您不介意合理的哈希,为什么不一直使用template &lt;typename T&gt; struct AnyHasher int operator()(const T &amp;) return 0; 呢? @Caleth 你觉得现在够明智了吗?

以上是关于当使用向量作为键时,C++ unordered_map 失败的主要内容,如果未能解决你的问题,请参考以下文章

当用户输入“输入”键时停止

将 C# 数组传递给 C++ 向量

当用于模式匹配作为映射中的键时,变量是未绑定的

相对于使用堆,使用带有 insert() 的向量作为优先级队列的开销是多少? (c++)

c++ 使用友好类的类型作为模板参数声明 stl 向量

在 C++ 中只检测一个按键