如何调整无序STL容器以仅存储键值对的值

how to adapt an unordered STL container to store only Values of a Key-Value pair?

本文关键字:存储 键值对 STL 何调整 调整 无序      更新时间:2023-10-16

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

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

一个无序集只使用键,而一个无序映射同时使用键和值,因此新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类型的一些搜索相关信息被存储为基于散列密钥的低32位的位置中的std::pair(通常总共只有16个字节)。

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

你所要求的可能并非不可能,但没有一种典型的哈希算法能做到这一点。无论如何,似乎没有任何意义,你必须存储东西才能使集合可遍历,而且很难看到除了哈希之外的其他东西如何能像哈希一样工作,因为这就是你所搜索的。

如果散列"太大",请使用散列的散列。(当然,您必须处理散列冲突。)

由于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> >;

这里和那里再加一些罪恶的糖。。。

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

要检索一个值,您可以以某种方式重建密钥数据,然后重新计算哈希值,然后在映射中搜索该哈希。

我可能完全错了,但为什么不只是一个具有一些用于插入和提取的实用函数的std::unordered_map<uint32_t, std::pair<uint32_t, Value>>呢?

// 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';
}

Ideone上的现场演示。

输出:

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

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

一个完美的散列函数是不够的。您不仅必须保证没有哈希冲突,还必须确保没有bucket冲突。见鬼,你甚至必须确保bucket的数量永远不会改变,因为你的数据结构无法发现密钥的哈希。