【问题标题】:how to adapt an unordered STL container to store only Values of a Key-Value pair?如何调整无序 STL 容器以仅存储键值对的值?
【发布时间】:2025-12-23 11:05:12
【问题描述】:

新的 C++11 标准具有无序容器。特别是,std::unordered_map<Key, Value>std::pair<Key, Value> 存储在基于std::hash<Key>(默认散列函数)的位置中。同样,std::unordered_set<Key> 将 Key 存储在基于std::hash<Key> 的位置中。

我的问题是:如何在基于std::hash<Key> 的位置存储键值对的值?如果使用完美的散列函数,即不同的键映射到不同的散列索引(因此永远不需要解决冲突),这将很有用。

unordered_set 只使用键,而 unordered_map 既使用键又使用值,因此新 C++11 标准中的无序 STL 容器似乎不允许这种自定义。从现有的 STL 容器中获取这种数据结构的好方法是什么?

更一般地说,如何将std::pair<T, Value> 存储在基于std::hash<Key> 的位置中,其中T 是表示密钥签名的类型?例如。如果 Key 是一个大数据结构,我想计算一个 64 位哈希键并将其分成两个 32 位部分:高 32 位与 Value 一起形成std::pair<uint32_t, Value>,低 32 位确定位置这对存储在哪里。

这很有用的应用程序是,例如计算机国际象棋,其中一个位置(在某些程序中为几千字节)作为 Key 类型被散列成一个 64 位的密钥,其中只有高 32 位和一些作为 Value 类型的搜索相关信息存储为std::pair(通常总共只有 16 个字节)在基于散列密钥的低 32 位的位置中。

【问题讨论】:

  • 如果Key 是您所假设的一个大型数据结构,那么它现在是如何存储的?实际存储它没有价值吗?构建hash<Key> 的密集程度如何?鉴于Key 作为价值的一部分,我看不到你不能用set 得到什么,或者比map 有什么好处。您保存 16 位 64 位值的示例不足以让我相信它值得付出努力。
  • 所以你想要一些使用 hash 来确定放置 Value 的位置,而不在任何地方存储 hash 的东西?
  • 存储密钥是实现要求。没有它就做一个哈希表是不合理的,因为你需要检查多个哈希到一个桶。
  • @rhalbersma :您描述的是“可扩展”,而不是“通用”。后者仅仅意味着大多数时候对大多数人来说已经足够好了。您正在寻找的内容远远超出此范围。
  • @rhalbersma : "用于从值类型中提取键的特征类" 如果这是您所追求的,请查看 Boost.MultiIndex ,因为这正是它的工作原理。 :-]

标签: c++ stl c++11 unordered-map unordered-set


【解决方案1】:

如果不连续访问散列值,就没有通用的方法来对散列执行操作。例如,假设哈希在内部使用树。要将新节点添加到哈希中,您需要将其哈希值与树上现有节点的哈希值进行比较。如果你不将它们的值存储在树中,你怎么能做到这一点?

您所要求的可能并非不可能,但没有一个典型的散列算法可以做到。而且似乎没有任何意义,您必须存储 something 以使集合可遍历,而且很难看出除了哈希之外的其他东西如何与哈希一样工作,因为这就是您要搜索的内容。

如果散列“太大”,请使用散列的散列。 (当然,那你必须处理哈希冲突。)

【讨论】:

  • 据我了解,OP 没有使用 hash<Key> 作为实际密钥,而是使用它的低 32 位,同时将高 32 位与值一起存储成对.
  • 然后将hash<Key> 的低32 位设为Key。不过,您仍然必须将其存储在某个地方,否则无法遍历哈希进行插入、查找和删除。
  • @David Schwartz:对不起,如果我对基本数据结构一无所知,但是除了使用随机访问数组之外​​,如何使用树实现哈希表并且仍然有 O(1) 访问时间/vector 作为内部数据结构?
  • @rhalbersma 如果您使用 n 位哈希,则无论存储的元素数量如何,树分支的数量都不会超过 n。 (就存储在其中的条目数而言,它是 O(1)。)
【解决方案2】:

由于 C++11 哈希实际上是 size_t 类型,因此您可以执行以下操作:

template <typename T>
struct with_hash
{
    size_t hash;
    T value;
};

template<> struct std::hash<with_hash>
{
    typedef size_t result_type;
    typedef with_hash argument_type;
    size_t operator()(const with_hash &x)
    {
         return x.hash;
    }
};

template <typename T>
using perfectly_hashed = std::unordered_set< with_hash<T> >;

这里和那里还有一些语法糖......

【讨论】:

  • 好主意,但它仍然存储完整的哈希。根据 David Schwartz 的回答,我现在确信我想要的只能从头开始编码,而不是通过无序容器的简单 typedef。
【解决方案3】:

为要用作键的类型实现散列函数,然后创建一个类型来保存散列值并专门化该类型的 std::hash 以仅返回散列值。现在您可以计算散列,丢弃用于计算散列的数据,并将值及其散列粘贴到映射中。

要检索一个值,您可以以某种方式重建关键数据,然后您可以重新计算散列值,然后在映射中搜索该散列。

【讨论】:

    【解决方案4】:

    我可能完全弄错了,但为什么不只是一个 std::unordered_map&lt;uint32_t, std::pair&lt;uint32_t, Value&gt;&gt; 具有一些不错的插入和提取实用功能?

    // demonstration with 32bit 'hash' and 16bit 'lo' and 'hi'
    #include <unordered_map>
    #include <string>
    #include <stdint.h>
    #include <iostream>
    
    int main(){
        typedef std::unordered_map<uint16_t, std::pair<uint16_t, std::string>> map_type;
        map_type m;
        std::string key = "hello", value = "world";
        uint32_t hash = std::hash<std::string>()(key);
        uint16_t lo = hash & 0xFFFF, hi = hash >> 16; // make a nice function for this
        m.insert(std::make_pair(lo, std::make_pair(hi, value))); // and this
        auto it = m.find(lo); // and this
        std::cout << "hash: " << hash << '\n'
                  << "lo: " << it->first << '\n'
                  << "hi: " << it->second.first << '\n'
                  << "lo | (hi << 16): " << (it->first | (uint32_t(it->second.first) << 16)) << '\n'
                  << "value: " << it->second.second << '\n';
    }
    

    Live demo on Ideone.

    输出:

    hash: 1335831723
    lo: 11435
    hi: 20383
    lo | (hi << 16): 1335831723
    value: world
    

    【讨论】:

    • 这不是我想要的。在您的示例中,我只想存储std::make_pair(hi, value) 并将其存储在lo 的位置。您的代码获取位置lo,但同时存储hilovalue
    • @rhalbersma:那些 32 位真的会影响你的表现吗?还是有其他原因你不想要它们?
    • 在 16 字节哈希条目(签名 + 值)上,另外 4 字节意味着内存增加 25%。不会杀了我,但一切都很重要。我自己的实现使用了一种两层方法,首先将 Key 散列为 64 位整数,然后将其拆分为 32 位高/低部分并将std::pair&lt;hi, Value&gt; 存储到 std::vector 的低位条目中。它只有 150 行代码,但我有点恼火,它不能只用几个 typedef 和重新使用 std::unordered_map 来完成。
    【解决方案5】:

    我的问题是:如何仅将键值对的值存储在基于 std::hash 的位置?如果使用完美的哈希函数,即不同的键映射到不同的哈希索引的哈希函数(因此永远不需要解决冲突),这将很有用。

    完美的哈希函数是不够的。您不仅要保证没有哈希冲突,还必须确保没有 bucket 冲突。哎呀,您甚至必须确保桶的数量永远不会改变,因为您的数据结构无法发现键的哈希值。

    【讨论】: