【问题标题】:Building an unordered map with tuples as keys构建以元组为键的无序映射
【发布时间】:2011-04-06 10:22:38
【问题描述】:

在带有 Boost 的 C++ 程序中,我正在尝试构建一个无序映射,其键是双精度元组:

typedef boost::tuples::tuple<double, double, double, double> Edge;
typedef boost::unordered_map< Edge, int > EdgeMap;

初始化地图可以完成,但是,当我尝试用键和值填充它时

EdgeMap map;
Edge key (0.0, 0.1, 1.1, 1.1);
map[key] = 1;

我遇到以下错误消息:

/usr/include/boost/functional/hash/extensions.hpp:176: error: no matching function for call to ‘hash_value(const boost::tuples::tuple<double, double, double, double, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type>&)’

我认为这是因为我需要为元组键指定一个哈希函数。我该怎么做?

编辑:

根据以下建议,我编写了以下实现:

#include <boost/tuple/tuple.hpp>
#include <boost/unordered_map.hpp>

typedef boost::tuples::tuple<double, double, double, double> Edge;

struct ihash
    : std::unary_function<Edge, std::size_t>
{
    std::size_t operator()(Edge const& e) const
    {
        std::size_t seed = 0;
        boost::hash_combine( seed, e.get<0>() );
        boost::hash_combine( seed, e.get<1>() );
        boost::hash_combine( seed, e.get<2>() );
        boost::hash_combine( seed, e.get<3>() );
        return seed;
    }
};

struct iequal_to
    : std::binary_function<Edge, Edge, bool>
{
    bool operator()(Edge const& x, Edge const& y) const
    {
        return ( x.get<0>()==y.get<0>() &&
                 x.get<1>()==y.get<1>() &&
                 x.get<2>()==y.get<2>() &&
                 x.get<3>()==y.get<3>());
    }
};

typedef boost::unordered_map< Edge, int, ihash, iequal_to > EdgeMap;

int main() {

    EdgeMap map;
    Edge key (0.0, 0.1, 1.1, 1.1);
    map[key] = 1;

    return 0;
}

可以缩短吗?

【问题讨论】:

    标签: c++ boost tuples unordered-map


    【解决方案1】:

    实际上,您可以完美地为boost::tuple 定义一个通用哈希函数。唯一的要求是它位于同一个命名空间中,以便被 ADL 拾取。

    我真的很惊讶他们还没有写一个。

    namespace boost { namespace tuples {
    
      namespace detail {
    
        template <class Tuple, size_t Index = length<Tuple>::value - 1>
        struct HashValueImpl
        {
          static void apply(size_t& seed, Tuple const& tuple)
          {
            HashValueImpl<Tuple, Index-1>::apply(seed, tuple);
            boost::hash_combine(seed, tuple.get<Index>());
          }
        };
    
        template <class Tuple>
        struct HashValueImpl<Tuple,0>
        {
          static void apply(size_t& seed, Tuple const& tuple)
          {
            boost::hash_combine(seed, tuple.get<0>());
          }
        };
      } // namespace detail
    
      template <class Tuple>
      size_t hash_value(Tuple const& tuple)
      {
        size_t seed = 0;
        detail::HashValueImpl<Tuple>::apply(seed, tuple);
        return seed;
      }
    
    } }
    

    注意:我只是证明它是正确的,我没有测试过。

    【讨论】:

    • 对于 boost 1.60.0,“命名空间元组”应该是“命名空间元组”才能使其工作。
    • @Val:我认为实际上所有以前的版本也是如此;谢谢:)
    • "我只证明了它是正确的,我没有测试过。"
    【解决方案2】:

    你需要一点front matter。由于boost::tuples::tuple 的底层实现,将Edge 设为允许重载正确解析的结构。否则,您将找不到匹配项

    • boost::hash_value(const Edge &amp;)
    • operator==(const Edge &amp;, const Edge &amp;)

    代码如下:

    struct Edge {
      Edge(double x1, double x2, double x3, double x4)
        : tuple(x1,x2,x3,x4) {}
      boost::tuples::tuple<double, double, double, double> tuple;
    };
    
    // XXX: less than ideal implementation!
    bool operator==(const Edge &a, const Edge &b)
    {
      return a.tuple.get<0>() == b.tuple.get<0>() &&
             a.tuple.get<1>() == b.tuple.get<1>() &&
             a.tuple.get<2>() == b.tuple.get<2>() &&
             a.tuple.get<3>() == b.tuple.get<3>();
    }
    
    // XXX: me too!
    std::size_t hash_value(const Edge &e)
    {
      std::size_t seed = 0;
      boost::hash_combine(seed, e.tuple.get<0>());
      boost::hash_combine(seed, e.tuple.get<1>());
      boost::hash_combine(seed, e.tuple.get<2>());
      boost::hash_combine(seed, e.tuple.get<3>());
      return seed;
    }
    
    typedef boost::unordered_map< Edge, int > EdgeMap;
    

    【讨论】:

      【解决方案3】:

      这一切都在文档中...

      你需要类似的东西:

      std::size_t hash_value(Edge const& e)
      {
          std::size_t seed = 0;
          boost::hash_combine( seed, e.get<0>() );
          boost::hash_combine( seed, e.get<1>() );
          boost::hash_combine( seed, e.get<2>() );
          boost::hash_combine( seed, e.get<3>() );
          return seed;
      }
      

      ...然后你可以定义:

      boost::unordered_map< Edge, int, boost::hash< Edge > > EdgeMap;
      

      ...实际上这是默认设置,所以它现在应该可以在没有明确的哈希定义的情况下工作:

      boost::unordered_map< Edge, int > EdgeMap;
      

      【讨论】:

        【解决方案4】:

        你试过用这个吗:

        #include "boost/functional/hash.hpp"
        #include <unordered_map>
        #include <tuple>
        
        using Edge = std::tuple<double, double, double, double>;
        
        struct KeyHash {
            std::size_t operator()(const Edge & key) const {
                return boost::hash_value(key);
            }
        };
        
        using EdgeMap = std::unordered_map<Edge, int, KeyHash>;
        
        

        请注意我将std 用于tupleunordered_map

        您可以在 Compiler Explorer link 上使用 even lambda 查看完整代码。

        【讨论】:

          【解决方案5】:

          Boost 文档给出了required interface。如果不了解更多有关所涉及的价值,就很难说更多。给定一个键对象作为输入,它必须产生一个确定性的 size_t —— 即,它是一个纯函数,其结果仅取决于输入值,因此提供相同的输入将始终产生相同的哈希码。

          【讨论】:

            【解决方案6】:

            来自Generic hash for tuples in unordered_map / unordered_set 的这段代码为标准可散列类型(字符串、整数等)的所有 c++11 元组提供了神奇的支持。

            不出所料,它看起来非常像上面的 Matthieu M. 的解决方案,但没有提升依赖项。

            将代码放在头文件中并包含它,无序的元组集将开箱即用:

            #include <tuple>
            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;                                 
                    }                                              
            
                };
            }
            

            【讨论】:

              猜你喜欢
              • 2014-10-06
              • 2015-05-28
              • 2017-03-20
              • 1970-01-01
              • 1970-01-01
              • 2023-03-21
              • 1970-01-01
              • 1970-01-01
              • 2018-03-27
              相关资源
              最近更新 更多