【问题标题】:unordered_map hash function returnunordered_map 哈希函数返回
【发布时间】:2014-07-17 21:44:09
【问题描述】:

我想要一个unordered_map 和一个struct,我想用作多个std::set< std::string > 的键。

我看到一个自定义哈希函数is required 并且一个字符串可以应用std::hash;但是,我无法确定应该返回什么来满足 unordered_map 的这些集合的哈希函数的目的。

自定义散列函数应该如何返回?

【问题讨论】:

  • 你想要的数据结构的描述有点模糊。要清除,您想要一个std::unordered_map<myStruct>,其中myStruct 是一个具有多个std::set<std::string> 成员的自定义?并且您需要有关如何为 myStruct 创建 散列函数 的建议?
  • @Snps 是的,但我将myStruct 创建为struct 而不是class,但我会尽我所能。感谢您的关注!
  • 在 C++ 中 struct 表示 class,默认可见性设置为 public
  • structclass 之间没有真正的区别;)
  • 谢谢大家!我只是在关注这个,直到我发现需要做其他事情stackoverflow.com/a/54596/1382306我猜那是现在? ;))

标签: c++ struct set unordered-map hash-function


【解决方案1】:

我认为这可能是Snps' answer 的更好选择。这为用户定义的类型实现了 std::hash 的特化,它在不创建临时字符串的情况下对结构进行哈希处理。

我从 Boost 中复制了两个函数 hash_combinehash_range,以计算来自两个容器的单个哈希值。

#include <iostream>
#include <functional>
#include <set>
#include <unordered_map>

// user-defined type
struct myStruct {
    std::set<std::string> s1;
    std::set<std::string> s2;

    bool operator==(const myStruct &other) const {
        return (s1 == other.s1) && (s2 == other.s2);
    }
};

// hash helper functions plagiarized from Boost
template <typename T>
void hash_combine(size_t &seed, const T &v)
{
    using std::hash;
    seed ^= hash<T>()(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}

template <typename It>
void hash_range(size_t &seed, It first, It last)
{
    for (; first != last; ++first) {
        hash_combine(seed, *first);
    }
}

// std::hash specialization
namespace std
{
    template<> struct hash<myStruct> {
        size_t operator()(const myStruct &key) const {
            size_t seed = 0;
            hash_range(seed, key.s1.begin(), key.s1.end());
            hash_range(seed, key.s2.begin(), key.s2.end());
            return seed;
        }
    };
}

int main()
{
    std::unordered_map<myStruct, int> myMap;

    myStruct ms1{ { "apple", "pear", "orange" }, { "red", "green", "blue" } };
    myStruct ms2{ { "pear", "apple", "orange" }, { "red", "green", "blue" } };
    myStruct ms3{ { "apple", "banana", "orange" }, { "red", "green", "blue" } };

    myMap[ms1] = 1;
    myMap[ms2] = 2;
    myMap[ms3] = 3;

    std::cout << myMap.size() << '\n'; // output: 2
}

【讨论】:

  • 谢谢 Blastfurnace!两个问题:能否详细说明hash_combine 的最后一行,myMap.size() 为什么要输出2?不应该是“3”吗?这不是批评。它来自于缺乏经验。提前非常感谢您!
  • @Gracchus:hash_combine 函数来自Boost library,用于从不同的值增量构建散列。方法和数字常量是由比我聪明得多的人推导出来的,以产生统一的结果。输出为 2,因为 ms1ms2 包含相同的 std::set 值,因此产生相同的哈希值。 std::unordered_map 仅保存唯一键,因此第二次插入失败,因为它将是重复键。 std::unordered_multimap 允许重复键。
  • 至于第一个答案:哇!第二个:facepalm duh。非常感谢!
  • @Gracchus "facepalm duh",我确实努力帮助你。最终结果/最佳答案是否重要? :)
  • 这是一个很好的答案。确实boost::hash_combine 并不理想(或者,甚至在许多情况下都很好)。组合哈希不是一个简单的问题;您可能会发现 @HowardHinnant 的论文 'Types don't know #' 很有帮助:isocpp.org/blog/2014/05/n3980
【解决方案2】:

std::hash的要求如下:(http://en.cppreference.com/w/cpp/utility/hash)

哈希模板定义了一个实现哈希函数的函数对象。该函数对象的实例满足 Hash。特别是,他们定义了一个 operator()

  1. 接受Key 类型的单个参数。
  2. 返回一个size_t 类型的值,表示参数的哈希值。
  3. 调用时不抛出异常。
  4. 对于两个相等的参数k1k2std::hash&lt;Key&gt;()(k1) == std::hash&lt;Key&gt;()(k2)
  5. 对于两个不同的参数k1k2不相等,std::hash&lt;Key&gt;()(k1) == std::hash&lt;Key&gt;()(k2)的概率应该很小,接近1.0 / std::numeric_limits&lt;size_t&gt;::max()

哈希模板既是CopyConstructible又是Destructible

所以你需要的基本上是一个函数,它返回一个std::size_t,它对于每个myStruct 对象都是唯一的,并且对于被认为是等效的对象返回相同的值。

编辑:以下可能不是生成哈希的最可靠的方法,但它将作为如何完成它的基本示例。

一种方法是使用 std::hash&lt;std::string&gt; 的标准特化,通过使用 分隔符序列 连接每个 std::set 成员中的所有字符串,然后将所有生成的合并字符串连接成一个并使用标准哈希函数返回哈希值。

如果成员 std::sets 不同,则合并的“超级”字符串对于每个 myStruct 对象都是唯一的,并且当成员不不同时仍然相同,因为 std::set 是有序容器。

struct myStruct {
    std::set<std::string> s1;
    std::set<std::string> s2;
};

std::string mergeAllStrings(const myStruct& ms) {
    static const std::string SEPARATOR = "#¤%&"; // Some uncommon sequence.
    std::string super;
    for (const auto& s : ms.s1) {
        super += s + SEPARATOR; // Append separator.
    }
    for (const auto& s : ms.s2) {
        super += s + SEPARATOR; // Append separator.
    }
    return super;
}

int main() {
    myStruct ms1{{"apple", "pear", "orange"}, {"red", "green", "blue"}};
    myStruct ms2{{"pear", "apple", "orange"}, {"red", "green", "blue"}};
    myStruct ms3{{"apple", "banana", "orange"}, {"red", "green", "blue"}};

    std::cout << std::hash<std::string>()(mergeAllStrings(ms1)) << std::endl;
    std::cout << std::hash<std::string>()(mergeAllStrings(ms2)) << std::endl;
    std::cout << std::hash<std::string>()(mergeAllStrings(ms3)) << std::endl;
}

输出:

2681724430859750844 // Same
2681724430859750844 // Same
2942368903851914580 // Different

您现在可以创建一个哈希函子,例如如:

struct MyHash {
    std::size_t operator()(const myStruct& ms) const {
        return std::hash<std::string>()(mergeAllStrings(ms));
    }
};

并将其与std::unordered_map 一起使用:

std::unordered_map<myStruct, myValue, MyHash> m;

请注意,您还应该提供自定义的 equal_to 函子。

【讨论】:

  • myStruct ms4{{"applebananaorange"}, {"redgreenblue"}}; // whoops
  • @LightnessRacesinOrbit 嗯,你是对的。此解决方案不起作用。也许它可以在每个std::string 之间添加一个很少出现的分隔符:)
  • 如果std::set插入unordered_map后修改了怎么办?
  • @CahitGungor std::hashoperator() 每次调用时都需要一个参数Key,因此哈希总是从更新的myStruct 对象生成。
  • @LightnessRacesinOrbit “为什么不正确地散列两个容器”,这不是问题。
猜你喜欢
  • 2011-11-05
  • 2016-06-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-13
  • 2020-11-12
  • 2021-07-01
相关资源
最近更新 更多