【问题标题】:Using tuple in unordered_map在 unordered_map 中使用元组
【发布时间】:2014-01-17 01:28:02
【问题描述】:

我想在我的unordered_map 中使用由int,char,char 组成的元组。我是这样做的:

#include <string>
#include <unordered_map>
#include <cstring>
#include <iostream>
#include <tuple>

using namespace std;

tuple <int,char,char> kk;
unordered_map<kk,int> map;

int main()
{
    map[1,"c","b"]=23;
    return 0;
}

但这给了我以下错误:

map.cpp:9:21: error: type/value mismatch at argument 1 in template parameter list     for ‘template<class _Key, class _Tp, class _Hash, class _Pred, class _Alloc> class    std::unordered_map’
map.cpp:9:21: error:   expected a type, got ‘kk’
map.cpp:9:21: error: template argument 3 is invalid
map.cpp:9:21: error: template argument 4 is invalid
map.cpp:9:21: error: template argument 5 is invalid
map.cpp:9:26: error: invalid type in declaration before ‘;’ token
map.cpp: In function ‘int main()’:
map.cpp:14:16: error: assignment of read-only location ‘"b"[map]’

我做错了什么?

【问题讨论】:

    标签: c++ hashmap unordered-map stdtuple


    【解决方案1】:

    使用 std::integer_sequence 会有所帮助:

    struct hash_tuple {
        template <std::size_t...Index>
        size_t recursive_hash(const auto &x) const{
            return (boost::get<Index>(x) ^ ... );
        }
    
        template <template <typename> class Ts,typename...Args>
        size_t operator()(const Ts<Args...>& x) const{
            return recursive_hash<std::make_integer_sequence<int,sizeof...(Args)>>(x);
        }
    };
    
    using Map = std::unordered_map<Key, int, hash_tuple>;
    

    此代码对于所有用作键的元组都是通用的

    【讨论】:

      【解决方案2】:

      对于那些使用 boost 的人,他们可以使用此方法重新路由散列以实现 boost 的实现

      #include "boost/functional/hash.hpp"
      #include <string>
      #include <unordered_map>
      #include <cstring>
      #include <iostream>
      #include <tuple>
      
      
      using Key = std::tuple<int, char, char>;
      
      struct KeyHash {
          std::size_t operator()(const Key & key) const
          {
              return boost::hash_value(key);
          }
      };
      
      using Map = std::unordered_map<Key, int, KeyHash>;
      
      int main()
      {
          Map map;
          map[1,"c","b"] = 23;
          return 0;
      }
      

      【讨论】:

      • 谢谢!顺便说一句,功能哈希已经移动。我不确定何时,但在 boost 1.72 中它位于 #include &lt;boost/container_hash/extensions.hpp&gt; 我不确定为什么元组的 boost 哈希函数没有记录在某处。再次感谢您的提示!
      【解决方案3】:

      在阅读了fewotherposts 之后,我最终得到了这个。它使用高效的哈希组合算法,并且不专门化 std 命名空间中的内容。如果您希望此代码通常适用于任何可散列元素的元组,则需要做更多的工作。

      这适用于 C++11 及更高版本。在 C++03 中,您可以使用 boost::hash 代替 std::hash

      typedef tuple<int, char, char> MyTuple;
      
      // define a hash function for this tuple
      struct KeyHash : public std::unary_function<MyTuple, std::size_t> {
          std::size_t operator()(const MyTuple& k) const {
              // the magic operation below makes collisions less likely than just the standard XOR
              std::size_t seed = std::hash<int>()(std::get<0>(k));
              seed ^= std::hash<char>()(std::get<1>(k)) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
              return seed ^ (std::hash<char>()(std::get<2>(k)) + 0x9e3779b9 + (seed << 6) + (seed >> 2));
          }
      };
      
      // define the comparison operator for this tuple
      struct KeyEqual : public std::binary_function<MyTuple, MyTuple, bool> {
          bool operator()(const MyTuple& v0, const MyTuple& v1) const {
              return (std::get<0>(v0) == std::get<0>(v1) && std::get<1>(v0) == std::get<1>(v1) &&
                      std::get<2>(v0) == std::get<2>(v1));
          }
      };
      
      typedef unordered_map<MyTuple, int, KeyHash, KeyEqual> MyMap;
      

      【讨论】:

        【解决方案4】:

        这是一种使用元组作为 unordered_map 的键而不使用散列特化的方法:

        #include <string>
        #include <tuple>
        #include <sstream>
        #include <iostream>
        #include <iomanip>
        #include <vector>
        #include <unordered_map>
        using namespace std;
        
        string fToStr(unordered_map<double,int>& dToI,float x)
        {
           static int keyVal=0;
           stringstream ss;
           auto iter = dToI.find(x);
           if(iter == dToI.end()) {
              dToI[x]=++keyVal;
              ss << keyVal;
           } else {
              ss <<  iter->second;
           }
           return ss.str();
        }
        
        typedef tuple<int,char,char> TICC;
        const char ReservedChar=',';
        string getKey(TICC& t)
        {
           stringstream ss;
           ss << get<0>(t) << ReservedChar << get<1>(t) << ReservedChar << get<2>(t);
           return ss.str();
        }
        
        int main()
        {
           unordered_map< string,TICC > tupleMp;
           vector<TICC> ticc={make_tuple(1, 'a', 'b'),make_tuple(1, 'b', 'c'),make_tuple(2, 'a', 'b')};
           for(auto t : ticc)
              tupleMp[getKey(t)]=t;
        
           for(auto t : ticc) {
              string key = getKey(t);
              auto val = tupleMp[key];
              cout << "tupleMp[" << key << "]={" << get<0>(val) << "," << get<1>(val) <<  ","<< get<2>(val) << "} ";
           }
           cout << endl;
        
           //for float tuple elements use a second float to int key map 
           unordered_map< double,int > dToI;
           vector<float> v{1.234,1.234001,1.234001};
           cout << "\nfloat keys: ";
           for(float f : v)
              cout <<  setprecision(7) << f << "=" << fToStr(dToI,f) << " ";
           cout << endl;
           return 0;
        }
        

        输出是:

        tupleMp[1,a,b]={1,a,b} tupleMp[1,b,c]={1,b,c} tupleMp[2,a,b]={2,a,b}
        
        float keys: 1.234=1 1.234001=2 1.234001=2
        

        【讨论】:

          【解决方案5】:

          我需要地图而不是无序地图:
          键是三元组和
          value 是一个 4 元组

          看到所有答案,我正要换成对

          但是,下面对我有用:

          // declare a map called map1
          map <
            tuple<short, short, short>,
            tuple<short, short, short, short>
          > map1;
          
          // insert an element into map1
          map1[make_tuple(1, 1, 1)] = make_tuple(0, 0, 1, 1);
          
          // this also worked
          map1[{1, 1, 1}] = { 0, 0, 1, 1 };
          

          我正在使用 Visual Studio Community 2015 IDE

          【讨论】:

            【解决方案6】:

            正如所指出的,std::hash 不是专门用于元组的。但是,如果您的元组由字符串和 int 等标准可散列类型组成,则来自generic-hash-for-tuples-in-unordered-map-unordered-set 的以下代码将自动在 c++11 中添加此类支持。

            只需将代码粘贴到头文件中,并在需要时包含它:

            #include <tuple>
            // function has to live in the std namespace 
            // so that it is picked up by argument-dependent name lookup (ADL).
            namespace std{
                namespace
                {
            
                    // Code from boost
                    // Reciprocal of the golden ratio helps spread entropy
                    //     and handles duplicates.
                    // See Mike Seymour in magic-numbers-in-boosthash-combine:
                    //     https://stackoverflow.com/questions/4948780
            
                    template <class T>
                    inline void hash_combine(std::size_t& seed, T const& v)
                    {
                        seed ^= hash<T>()(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
                    }
            
                    // Recursive template code derived from Matthieu M.
                    template <class Tuple, size_t Index = std::tuple_size<Tuple>::value - 1>
                    struct HashValueImpl
                    {
                      static void apply(size_t& seed, Tuple const& tuple)
                      {
                        HashValueImpl<Tuple, Index-1>::apply(seed, tuple);
                        hash_combine(seed, get<Index>(tuple));
                      }
                    };
            
                    template <class Tuple>
                    struct HashValueImpl<Tuple,0>
                    {
                      static void apply(size_t& seed, Tuple const& tuple)
                      {
                        hash_combine(seed, get<0>(tuple));
                      }
                    };
                }
            
                template <typename ... TT>
                struct hash<std::tuple<TT...>> 
                {
                    size_t
                    operator()(std::tuple<TT...> const& tt) const
                    {                                              
                        size_t seed = 0;                             
                        HashValueImpl<std::tuple<TT...> >::apply(seed, tt);    
                        return seed;                                 
                    }                                              
            
                };
            }
            

            【讨论】:

              【解决方案7】:

              unordered_map 的模板参数如下所示:

              template<
              
                  class Key,
                  class T,
                  class Hash = std::hash<Key>,
                  class KeyEqual = std::equal_to<Key>,
                  class Allocator = std::allocator< std::pair<const Key, T> >
              > class unordered_map;
              

              std::hashnot specialized for tuples(向下滚动到库类型的标准特化)。因此,您需要提供自己的,如下所示:

              typedef std::tuple<int, char, char> key_t;
              
              struct key_hash : public std::unary_function<key_t, std::size_t>
              {
               std::size_t operator()(const key_t& k) const
               {
                 return std::get<0>(k) ^ std::get<1>(k) ^ std::get<2>(k);
               }
              };
              // ..snip..
              typedef std::unordered_map<const key_t,data,key_hash,key_equal> map_t;
              //                                             ^ this is our custom hash
              

              最后,正如 Benjamin Lindley 已经回答的那样,您需要使用 std::make_tuple

              // d is data
              m[std::make_tuple(1, 'a', 'b')] = d;
              auto itr = m.find(std::make_tuple(1, 'a', 'b'));
              

              代码来自Using a std::tuple as key for std::unordered_map,这里是Live Example

              【讨论】:

              • 你能解释一下你是如何定义key_hash的吗?或者 key_hash 内部正在做什么?
              • @Zara 查看链接了解更多信息。 key_hash 在上面定义。稍后,我们将其用作模板参数之一(其中 unordered_map 需要一个 Hash)。
              • 对字段进行异或是一个糟糕的哈希函数。请不要按原样使用此代码。参见例如open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3876.pdf
              【解决方案8】:

              第一个错误:

              map.cpp:9:21: error:   expected a type, got ‘kk’
              

              正如错误清楚地表明,模板参数需要是一个类型。 kk 不是类型,它是一个对象。也许您打算将其设为 typedef?

              typedef tuple <int,char,char> kk;
              unordered_map<kk,int> map;
              

              第二个错误:

              map[1,"c","b"]=23;
              

              这里有两个问题。首先,在值之间放置逗号不会将它们组成一个元组。您需要明确说明,要么调用元组类型的构造函数,要么使用返回元组的函数(例如std::make_tuple)。其次,您的元组期待字符 ('c','b'),而不是字符串 ("c","b")。

              map[std::make_tuple(1,'c','b')] = 23;
              

              【讨论】:

                猜你喜欢
                • 2011-10-29
                • 2012-04-08
                • 2015-01-17
                • 2013-03-10
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多