C++中std::map自定義排序與std::unordered_map自定義哈希函數


  前面部分轉自C++ STL map的自定義排序

  std::map 的定義與特性,用法詳解參考C++ map用法詳解

1 //所在頭文件:<map>, std::map 類模板, std::map 通常由二叉搜索樹實現。
2 template < class Key,                                     // map::key_type
3            class T,                                       // map::mapped_type
4            class Compare = less<Key>,                     // map::key_compare
5            class Alloc = allocator<pair<const Key,T> >    // map::allocator_type
6            > class map;

  std::unorder_map的定義如下

1 //頭文件unorder_map,
2 template<class Key,
3     class Ty,
4     class Hash = std::hash<Key>,
5     class Pred = std::equal_to<Key>,
6     class Alloc = std::allocator<std::pair<const Key, Ty> > >
7     class unordered_map;
8     > class unordered_map

一、map按鍵值Key排序

1. 默認按照less<key>升序排列

  輸入8,Key升序,Value隨機:

 1 #include<iostream>
 2 #include<map>
 3 using namespace std;
 4 int main()
 5 {
 6     srand((unsigned)time(NULL));
 7     multimap<int,int>mp;
 8 // multimap第三個參數默認為less<Key>,即 less<int>
 9     int n;
10     cin>>n;
11     int a,b;
12     for(int i=0; i<n; i++)
13     {
14         a=rand()%4;
15         b=rand()%4;
16         //插入
17         mp.insert(pair<int,int>(a,b));
18     }
19     map<int,int>::iterator iter;
20     //遍歷輸出
21     for(iter=mp.begin(); iter!=mp.end(); iter++)
22         cout<<iter->first<<" "<<iter->second<<endl;
23     return 0;
24 }
View Code

2. 定義map時,用greater< Key>實現按Key值遞減插入數據

1 multimap<int,int,greater<int> >mp;
2 //注意<int>后空一格

3. 當Key值為自定義的類時

  方法1:寫一個函數對象1(仿函數),重載operator()

  注意:函數對象:即調用操作符的類,其對象常稱為函數對象(function object),它們是行為類似函數的對象。表現出一個函數的特征,就是通過“對象名+(參數列表)”的方式使用一個 類,其實質是對operator()操作符的重載

 1 #include<iostream>
 2 #include<map>
 3 using namespace std;
 4 typedef struct tagIntPlus
 5 {
 6     int num,i;
 7 }IntPlus;
 8 //自定義比較規則
 9 //注意operator是(),不是<
10 struct Cmp
11 {
12     bool operator () (IntPlus const &a,IntPlus const &b)const
13     {
14         if(a.num!=b.num)
15             return a.num<b.num;
16         else return a.i<b.i;
17     }
18 };
19 int main()
20 {
21     srand((unsigned)time(NULL));
22     //注意此處一定要有Cmp,否則無法排序會報錯
23     multimap<IntPlus,int,Cmp>mp;
24     int n;
25     cin>>n;
26     int a,b;
27     IntPlus intplus;
28     for(int i=0; i<n; i++)
29     {
30         a=rand()%4;
31         b=rand()%4;
32         intplus.num=a;
33         intplus.i=b;
34         mp.insert(pair<IntPlus,int>(intplus,i));
35     }
36     map<IntPlus,int>::iterator iter;
37     for(iter=mp.begin(); iter!=mp.end(); iter++)
38         cout<<iter->first.num<<" "<<iter->first.i<<" "<<iter->second<<endl;
39     return 0;
40 }
View Code

  方法2:在類里重載重載operator<

 1 typedef struct tagIntPlus
 2 {
 3     int num,i;
 4     bool operator < (tagIntPlus const& intplus)const
 5     {
 6         //當num不等時
 7         //前一個對象的num>后一個時返回true,降序排列。
 8         //反之升序排列
 9         if(num!=intplus.num)
10             return num>intplus.num;
11         //當num相等時
12         //前一個對象的i<后一個時返回true,升序排列
13         else return i<intplus.i;
14     }
15 }IntPlus;
View Code

  注意只重載小於號,不要去重載大於號,如果想改變為 升 / 降序列,只需改變判斷條件即可。

1 multimap<IntPlus,int>mp;

  主函數只需將第一種方法中的map中的Cmp去掉即可。

4. 用char*類型作為map的主鍵

  find或count時,默認使用== 進行判斷,char*只是指針,如果兩個字符串值相同,但是地址不同,是無法匹配的。所以最好使用std::string。如果非要用char*,需要使用find_if函數並且用bind2sd函數指定比較函數。

 1 #include <map>
 2 #include <algorithm>
 3 #include <iostream>
 4  
 5 using namespace std;
 6  
 7 bool search(pair<char*, int> a, const char* b)
 8 {
 9     return strcmp(a.first, b) == 0 ? true : false;
10 }
11 int main()
12 {
13     map<char*, int> test;
14     test.insert(pair<char*, int>("abc", 1));
15  
16     map<char*, int>::const_iterator iter = find_if(test.begin(), test.end(),  bind2nd(ptr_fun(search), "abc"));
17  
18     if (iter != test.end())
19     {
20         cout<< "find : " << iter->first << endl;
21     }
22  
23     return 0;
24 }

二、map按值Value排序

  再次強調不能用sort,只能將map中數據壓入能用sort的容器,如vector

 1 #include<iostream>
 2 #include<map>
 3 #include<vector>
 4 #include<algorithm>//sort
 5 using namespace std;
 6 
 7 typedef struct tagIntPlus
 8 {
 9     int num,i;
10 } IntPlus;
11 
12 typedef pair<tagIntPlus,int> PAIR;

  必須有Cmp。雖然之后會sort,map的排序並不重要,但是map輸入數據時需要比較Key值,沒有會報錯。注意這里說的是自定義類型作為key需要加Cmp函數。

1 struct Cmp
2 {
3     bool operator () (IntPlus const &a,IntPlus const &b)const
4     {
5         if(a.num!=b.num)
6             return a.num<b.num;
7         else return a.i<b.i;
8     }
9 };
View Code

  map會按鍵值Key升序排列,Value值無要求。定義vector的排序接口如下

 1 bool vec_cmp(PAIR const&a,PAIR const&b)
 2 {
 3     if(a.first.num!=b.first.num)
 4         return a.first.num<b.first.num;
 5     else
 6     {
 7         if(a.first.i!=b.first.i)
 8             return a.first.i<b.first.i;
 9         else return a.second>b.second;
10     }
11 }

  上面需重新定義Key升序排列,否則sort之后僅按Value降序排列,Key值被打亂。

 1 int main()
 2 {
 3     srand((unsigned)time(NULL));
 4     multimap<IntPlus,int,Cmp>mp;
 5     int n;
 6     cin>>n;
 7     int a,b;
 8     IntPlus intplus;
 9     for(int i=0; i<n; i++)
10     {
11         a=rand()%4;
12         b=rand()%4;
13         intplus.num=a;
14         intplus.i=b;
15         mp.insert(pair<IntPlus,int>(intplus,i));
16     }
17     map<IntPlus,int>::iterator iter;
18     cout<<"排序前:"<<endl;
19     for(iter=mp.begin(); iter!=mp.end(); iter++)
20         cout<<iter->first.num<<"|"<<iter->first.i<<"|"<<iter->second<<endl;
View Code

  排序前:

1     cout<<"排序后:"<<endl;
2     vector<PAIR>vec(mp.begin(),mp.end());
3     sort(vec.begin(),vec.end(),vec_cmp);
4     int size=vec.size();
5     for(int i=0;i<size;i++)
6         cout<<vec[i].first.num<<"|"<<vec[i].first.i<<"|"<<vec[i].second<<endl;
7     return 0;
8 }

 三、std::unorder_map自定義鍵值類型(轉載)

  對於unordered_map而言,當我們插入<key, value>的時候,需要哈希函數的函數對象對key進行hash,又要利用等比函數的函數對象確保插入的鍵值對沒有重復。然而,當我們自定義類型時,c++標准庫並沒有對應的哈希函數和等比函數的函數對象。因此需要分別對它們進行定義。

  因為都是函數對象,它們兩個的實際定義方法並沒有很大差別。不過后者比前者多了一個方法。因為等比函數的函數對象默認值std::equal_to<key>內部是通過調用操作符"=="進行等值判斷,因此我們可以直接在自定義類里面進行operator==()重載(成員和友元都可以)。

  因此,如果要將自定義類型作為unordered_map的鍵值,需如下兩個步驟:

    a-定義哈希函數的函數對象;

    b-定義等比函數的函數對象或者在自定義類里重載operator==()。

  為了避免重復,下文以討論哈希函數的函數對象為主,參數4則是通過直接在自定義類里面對operator==()進行重載。

  首先簡要介紹一下函數對象 (在一、二部分已介紹) 的概念:在《C++ Primer Plus》里面,函數對象是可以以函數方式與()結合使用的任意對象。這包括函數名、指向函數的指針和重載了“operator()”操作符的類對象。基於此,我們提出3個方法

方法1:std::function

  利用std::function為person_hash()構建函數實例。初始化時,這個函數實例就會被分配那個指向person_hash()的指針(通過構造函數實現),如下所示。

 1 #include <iostream>
 2 #include <unordered_map>
 3 #include <string>
 4 #include <functional>
 5 
 6 using namespace std;
 7 
 8 class Person{
 9 public:
10     string name;
11     int age;
12 
13     Person(string n, int a){
14         name = n;
15         age = a;
16     }
17     bool operator==(const Person & p) const 
18     {
19        return name == p.name && age == p.age;
20     }
21 };
22 
23 size_t person_hash( const Person & p ) 
24 {
25     return hash<string>()(p.name) ^ hash<int>()(p.age);
26 }
27 
28 int main(int argc, char* argv[])
29 {
30     //ERRO: unordered_map<Person,int,decltype(&person_hash)> ids;
31     //ERRO: unordered_map<Person,int,person_hash> ids(100, person_hash );
32     //OK: unordered_map<Person, int, decltype(&person_hash)> ids(100, person_hash );
33     unordered_map<Person,int,function<size_t( const Person& p )>> ids(100, person_hash); //需要把person_hash傳入構造函數
34     ids[Person("Mark", 17)] = 40561;
35     ids[Person("Andrew",16)] = 40562;
36     for ( auto ii = ids.begin() ; ii != ids.end() ; ii++ )
37         cout << ii->first.name 
38         << " " << ii->first.age 
39         << " : " << ii->second 
40         << endl;
41         return 0;
42 }
View Code

  因為std::function構建對象的表達過於復雜,我們可以利用C++11新增的關鍵字decltype。它可以直接獲取自定義哈希函數的類型,並把它作為參數傳送。因此,ids的聲明可以改成下面這樣。

unordered_map<Person,int,decltype(&person_hash)> ids(100, person_hash);

  另外,我們還可以引入c++11新支持的lambda expression,程序如下。

 1 #include <iostream>
 2 #include <unordered_map>
 3 #include <string>
 4 #include <functional>
 5 
 6 using namespace std;
 7 
 8 class Person{
 9 public:
10     string name;
11     int age;
12 
13     Person(string n, int a){
14         name = n;
15         age = a;
16     }
17 
18     bool operator==(const Person & p) const 
19     {
20         return name == p.name && age == p.age;
21     }
22 };
23 
24 int main(int argc, char* argv[])
25 {
26     unordered_map<Person,int,std::function<size_t (const Person & p)>>
27     ids(100, []( const Person & p)
28              {
29                  return hash<string>()(p.name) ^ hash<int>()(p.age);
30              } );
31     ids[Person("Mark", 17)] = 40561;
32     ids[Person("Andrew",16)] = 40562;
33     for ( auto ii = ids.begin() ; ii != ids.end() ; ii++ )
34         cout << ii->first.name 
35         << " " << ii->first.age
36         << " : " << ii->second 
37         << endl;
38         return 0;
39 }
View Code

  但是,使用lambda有2個弊端:

  1. 我們就無法使用decltype獲取函數對象的類型,而只能用更復雜的std::function方法。

  2. 程序的可讀性下降。

 

方法2:重載operator()的類

 

  方法2就是利用重載operator()的類,將哈希函數打包成可以直接調用的類。此時,雖然我們仍然需要第3個參數,但是我們不需要將函數對象的引用傳入構造器里。因為unordered_map會追蹤類定義,當需要獲得哈希時,它可以動態地構造對象並傳遞數據,如下所示。

 1 #include <iostream>
 2 #include <string>
 3 #include <unordered_map>
 4 #include <functional>
 5 using namespace std;
 6 
 7 class Person{
 8 public:
 9     string name;
10     int age;
11 
12     Person(string n, int a){
13         name = n;
14         age = a;
15     }
16 
17     bool operator==(const Person & p) const 
18     {
19         return name == p.name && age == p.age;
20     }
21 };
22 
23 struct hash_name{
24     size_t operator()(const Person & p) const{
25         return hash<string>()(p.name) ^ hash<int>()(p.age);
26     }
27 };
28 
29 int main(int argc, char* argv[]){
30     unordered_map<Person, int, hash_name> ids; //不需要把哈希函數傳入構造器
31     ids[Person("Mark", 17)] = 40561;
32     ids[Person("Andrew",16)] = 40562;
33     for ( auto ii = ids.begin() ; ii != ids.end() ; ii++ )
34         cout << ii->first.name 
35         << " " << ii->first.age
36         << " : " << ii->second
37         << endl;
38     return 0;
39 }
View Code

方法3:模板定制

  unordered_map第3個參數的默認參數是std::hash<Key>,實際上就是模板類。那么我們就可以對它進行模板定制,如下所示。

 1 #include <iostream>
 2 #include <unordered_map>
 3 #include <string>
 4 #include <functional>
 5 using namespace std;
 6 
 7 typedef pair<string,string> Name;
 8 
 9 namespace std {
10     template <> //function-template-specialization
11         class hash<Name>{
12         public :
13             size_t operator()(const Name &name ) const
14             {
15                 return hash<string>()(name.first) ^ hash<string>()(name.second);
16             }
17     };
18 };
19 
20 int main(int argc, char* argv[])
21 {
22     unordered_map<Name,int> ids;
23     ids[Name("Mark", "Nelson")] = 40561;
24     ids[Name("Andrew","Binstock")] = 40562;
25     for ( auto ii = ids.begin() ; ii != ids.end() ; ii++ )
26         cout << ii->first.first 
27              << " " << ii->first.second 
28              << " : " << ii->second
29              << endl;
30     return 0;
31 }
View Code

  當我們將模板訂制包含在定義類的頭文件中時,其他人無需額外工作,就可以直接用我們的類作為任何無序容器的鍵。這對於要使用我們自定義類的人來說,絕對是最方便的。因此,如果你想要在多個地方用這個類,方法3是最好的選擇。當然,你要確保自己的hash function不會影響std空間里的其他類。

————————————————

額外案例:等比函數的函數對象

  下例是哈希函數對象和等比函數對象都采用模板定制的方法。

 1 #include <iostream>
 2 #include <string>
 3 #include <unordered_map>
 4 #include <functional>
 5 using namespace std;
 6 
 7 class Person{
 8 public:
 9     string name;
10     int age;
11 
12     Person(string n, int a){
13         name = n;
14         age = a;
15     }
16 };
17 
18 namespace std{
19     template<>
20     struct hash<Person>{//哈希的模板定制
21     public:
22         size_t operator()(const Person &p) const 
23         {
24             return hash<string>()(p.name) ^ hash<int>()(p.age);
25         }
26         
27     };
28     
29     template<>
30     struct equal_to<Person>{//等比的模板定制
31     public:
32         bool operator()(const Person &p1, const Person &p2) const
33         {
34             return p1.name == p2.name && p1.age == p2.age;
35         }
36         
37     };
38 }
39 
40 int main(int argc, char* argv[]){
41     unordered_map<Person, int> ids;
42     ids[Person("Mark", 17)] = 40561;
43     ids[Person("Andrew",16)] = 40562;
44     for ( auto ii = ids.begin() ; ii != ids.end() ; ii++ )
45         cout << ii->first.name 
46         << " " << ii->first.age
47         << " : " << ii->second
48         << endl;
49     return 0;
50 }
View Code

————————————————


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM