Skip to content

map/unordered_map

unordered_map

Custom Class as key

// Example custom class
struct Foo
{
    string a;
    int b;
};

作為 key 的 class 需要有: 1. hash function 2. operator==

hash function 可以是去實作 struct std::hash<T>

namespace std {
template <>
struct hash<Foo>
{
    std::size_t operator()(const Foo& foo) const
    {
        return hash<string>{}(foo.a) ^
               hash<int>{}(foo.b);
    }
};
}

unordered_map<Foo, int> m; // ok

或是自己定義一個 function object 或是 lambda 然後丟到 unordered_map 的第三個 template 參數

std::unordered_map<Foo, int> m; // error

struct FooHash
{
    size_t operator()(const Foo& foo) const
    {
        return std::hash<string>{}(foo.a) ^
               std::hash<int>{}(foo.b);
    }
};
std::unordered_map<Foo, int, FooHash> m; // ok

auto FooHashLambda = [](const Foo& foo) -> size_t {
    return std::hash<string>{}(foo.a) ^
           std::hash<int>{}(foo.b);
};
std::unordered_map<Foo, int, decltype(FooHashLambda)> m2(1, FooHashLambda); // ok

https://ianyepan.github.io/posts/cpp-custom-hash/ https://marknelson.us/posts/2011/09/03/hash-functions-for-c-unordered-containers.html

以上範例的 hash 方法是直接 XOR 而這樣很不好,為了避免 hash collision應該用更好的方法: hash combine

template <class T>
inline void hash_combine(std::size_t & seed, const T & v)
{
  std::hash<T> hasher;
  seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}
// 以上實作其實就是 boost::hash_combine
// #include <boost/functional/hash.hpp>
// boost::hash_combine(seed, x);

namespace std
{
template<typename S, typename T>
struct hash<pair<S, T>>
{
inline size_t operator()(const pair<S, T> & v) const
{
  size_t seed = 0;
  ::hash_combine(seed, v.first);
  ::hash_combine(seed, v.second);
  return seed;
}
};
}
unordered_map<pair<int,int>,int> m; // ok

// 或是
struct pair_hash {
    template <class T1, class T2>
    std::size_t operator () (const std::pair<T1,T2> &p) const {
        auto h1 = std::hash<T1>{}(p.first);
        auto h2 = std::hash<T2>{}(p.second);
        return h1 ^ h2;  
    }
};
unordered_map<pair<int,int>,int, pair_hash> m; // ok

https://www.boost.org/doc/libs/1_35_0/doc/html/hash/combine.html https://gist.github.com/eugene-malashkin/884e225ff57aca1b9cbe

// boost example
#include <boost/functional/hash.hpp>
struct KeyHasher
{
  std::size_t operator()(const Key& k) const
  {
      using boost::hash_value;
      using boost::hash_combine;
.
      std::size_t seed = 0;
      hash_combine(seed,hash_value(k.first));
      hash_combine(seed,hash_value(k.second));
      hash_combine(seed,hash_value(k.third));
      return seed;
  }
};

或是有支援 64-bit 的版本:

size_t hash_combine( size_t lhs, size_t rhs ) {
  if constexpr (sizeof(size_t) >= 8) {
    lhs ^= rhs + 0x517cc1b727220a95 + (lhs << 6) + (lhs >> 2);
  } else {
    lhs ^= rhs + 0x9e3779b9 + (lhs << 6) + (lhs >> 2);
  }
  return lhs;
}

https://stackoverflow.com/questions/5889238/why-is-xor-the-default-way-to-combine-hashes/27952689#27952689 https://stackoverflow.com/questions/17016175/c-unordered-map-using-a-custom-class-type-as-the-key