无序容器

1. 无序容器

https://stackoverflow.com/questions/15869066/inserting-into-an-unordered-set-with-custom-hash-function

https://stackoverflow.com/questions/17016175/c-unordered-map-using-a-custom-class-type-as-the-key

https://en.cppreference.com/w/cpp/container/unordered_set

https://blog.csdn.net/vevenlcf/article/details/51743058

https://marknelson.us/posts/2011/09/03/hash-functions-for-c-unordered-containers.html

https://blog.csdn.net/cabgeinmars/article/details/51726540

https://en.cppreference.com/w/cpp/utility/hash

http://www.cplusplus.com/reference/unordered_set/unordered_set/find/

https://stackoverflow.com/questions/18704129/unordered-set-non-const-iterator

https://stackoverflow.com/questions/47658296/non-const-find-in-stdunordered-set

https://stackoverflow.com/questions/6963894/how-to-use-range-based-for-loop-with-stdmap

https://stackoverflow.com/questions/52914597/range-based-for-loop-on-unordered-map-and-references

2. 自定义类型

2.1. 问题

#include <iostream>
#include <algorithm>
#include <unordered_map>
​
​
class Node
{
public:
 Node() {}
​
 Node(std::vector<int> vec)
 {
 if (vec.size() < 3)
 {
 return;
 }
​
 std::sort(vec.begin(), vec.end());
 numA = vec[0];
 numB = vec[1];
 numC = vec[2];
 }
​
 bool operator ==(const Node& rhs)
 {
 return rhs.numA == numA && rhs.numB == numB && rhs.numC == numC;
 }
​
public:
 int numA;
 int numB;
 int numC;
};
​
​
int main()
{
​
 std::vector<int> vec{ 3, 8, 9 };
 Node currNode(vec);
​
 std::unordered_map<Node, int> mapInstance;
 mapInstance[currNode] = 0;
​
 for (auto pos = mapInstance.begin(); pos != mapInstance.end(); ++pos)
 {
 std::cout << "\tsecond: " << pos->second << std::endl;
 }
​
 getchar();
​
 return 0;
}

提示

C2338  The C++ Standard doesn't provide a hash for this type.

2.2. 解决方案

To be able to use std::unordered_map (or one of the other unordered associative containers) with a user-defined key-type, you need to define two things:

  1. A hash function; this must be a class that overrides operator() and calculates the hash value given an object of the key-type. One particularly straight-forward way of doing this is to specialize the std::hash template for your key-type.
  1. A comparison function for equality; this is required because the hash cannot rely on the fact that the hash function will always provide a unique hash value for every distinct key (i.e., it needs to be able to deal with collisions), so it needs a way to compare two given keys for an exact match. You can implement this either as a class that overrides operator(), or as a specialization of std::equal, or – easiest of all – by overloading operator==() for your key type (as you did already).

为使用户自定义类型可以使用 std::unordered_map,需要定义两个方法:

  1. hash 函数。这是一个重载了 operator() 操作符,并计算给定对象 key 的 hash 值的类。默认使用 std::hash。

  2. 相等比较函数。因为 hash 函数可能会产生碰撞,需要一种方法比较两个 key 是否严格匹配。可以通过重载 operator()、使用 std::equal 的特化版本、或重载 operator== 来实现。

The difficulty with the hash function is that if your key type consists of several members, you will usually have the hash function calculate hash values for the individual members, and then somehow combine them into one hash value for the entire object. For good performance (i.e., few collisions) you should think carefully about how to combine the individual hash values to ensure you avoid getting the same output for different objects too often.

hash 函数的难点在于若 key 值包含多个成员,如何计算单个成员的 hash 值,以及如何计算组合后的 hash 值,以避免碰撞。

2.2.1. 修改 std::hash 方法

A fairly good starting point for a hash function is one that uses bit shifting and bitwise XOR to combine the individual hash values. For example, assuming a key-type like this:

struct Key
{
 std::string first;
 std::string second;
 int         third;
​
 bool operator==(const Key &other) const
 {
 return (first == other.first
 && second == other.second
 && third == other.third);
 }
};
​
namespace std
{
 template <>
 struct hash<Key>
 {
 std::size_t operator()(const Key& k) const
 {
 using std::size_t;
 using std::hash;
 using std::string;
​
 // Compute individual hash values for first,
 // second and third and combine them using XOR
 // and bit shifting:
​
 return ((hash<string>()(k.first)
 ^ (hash<string>()(k.second) << 1)) >> 1)
 ^ (hash<int>()(k.third) << 1);
 }
 };
}
​
​
int main()
{
 std::unordered_map<Key, std::string> m6 = {
 { { "John", "Doe", 12 }, "example" },
 { { "Mary", "Sue", 21 }, "another" }
 };
}

It will automatically use std::hash<Key> as defined above for the hash value calculations, and the operator== defined as member function of Key for equality checks.

一个好的实践是使用位的移动和 XOR 位操作,组合单个的 hash 值。这会自动使用 std::hash<Key> 计算 hash 值。

2.2.2. 定义 hash 函数类

If you don't want to specialize template inside the std namespace (although it's perfectly legal in this case), you can define the hash function as a separate class and add it to the template argument list for the map:

struct KeyHasher
{
 std::size_t operator()(const Key& k) const
 {
 using std::size_t;
 using std::hash;
 using std::string;
​
 return ((hash<string>()(k.first)
 ^ (hash<string>()(k.second) << 1)) >> 1)
 ^ (hash<int>()(k.third) << 1);
 }
};
​
int main()
{
 std::unordered_map<Key, std::string, KeyHasher> m6 = {
 { { "John", "Doe", 12 }, "example" },
 { { "Mary", "Sue", 21 }, "another" }
 };
​
 getchar();
}

如果不想在 std 命名空间里特化 std::hash 模板,可以定义 hash 函数类,并将它作为 unordered_map 的模板类型参数。

2.3. 优良的 hash 函数

How to define a better hash function? As said above, defining a good hash function is important to avoid collisions and get good performance. For a real good one you need to take into account the distribution of possible values of all fields and define a hash function that projects that distribution to a space of possible results as wide and evenly distributed as possible.

定义一个好的 hash 函数。

4. find() 返回值

4.1. iterator 不可修改

iterator find ( const key_type& k );
const_iterator find ( const key_type& k ) const;

Get iterator to element

Searches the container for an element with k as value and returns an iterator to it if found, otherwise it returns an iterator to unordered_set::end (the element past the end of the container).

Another member function, unordered_set::count, can be used to just check whether a particular element exists.

All iterators in an unordered_set have const access to the elements (even those whose type is not prefixed with const_): Elements can be inserted or removed, but not modified while in the container.

Return value

An iterator to the element, if the specified value is found, or unordered_set::end if it is not found in the container.

Member types iterator and const_iterator are forward iterator types. Both may be aliases of the same iterator type.

4.2. 原因及解决方案

Both set and unordered_set have read-only keys. It's easy to see why this is the case - if the key value were to change, the data structure would have it filed in the wrong spot and you wouldn't be able to find it anymore.

It could be possible to change some part of the object that is not used in making the hash key, but that would lead to possible hard to track down bugs. The standards committee decided to eliminate that possibility by making the entire key const.

There are two ways around this restriction.

  • The first is to split the key from the value and use a mapor unordered_map instead.
  • The second is to remove the item from the set and reinsert it after it's modified.

4.3. 接口说明

iterator is the same as const_iterator, why there is non-const version of find()?

Because iterator is not mandatory the same as const_iterator, as stated in documentation:

The member types iterator and const_iterator may be aliases to the same type. Since iterator is convertible to const_iterator, const_iterator should be used in function parameter lists to avoid violations of the One Definition Rule.

emphasis is mine. Since they are not mandatory the same some generic code can depend on particular type of iterator returned by find() and it should be consistent with other containers.

5. Range-based for loop

Each element of the container is a map<K, V>::value_type, which is a typedef for std::pair<const K, V>. Consequently, you'd write this as

for (auto& kv : myMap) {
std::cout << kv.first << " has value " << kv.second << std::endl;
}

For efficiency, it is a good idea to make the parameter in the loop a reference. You could also consider making it const if you want a read-only view of the values.

Starting with C++17, you can also write

for (auto& [key, value]: myMap) {
std::cout << key << " has value " << value << std::endl;
}

which is a lot cleaner.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容