我希望从一个dynamic_bitset
到另一个dynamic_bitset
使用由Boost的unordered_map
实现的缓存。问题在于,位集没有默认的哈希函数。这似乎不是一个概念上的问题,但我不知道如何解决技术细节。我该怎么做?
我希望从一个dynamic_bitset
到另一个dynamic_bitset
使用由Boost的unordered_map
实现的缓存。问题在于,位集没有默认的哈希函数。这似乎不是一个概念上的问题,但我不知道如何解决技术细节。我该怎么做?
#define BOOST_DYNAMIC_BITSET_DONT_USE_FRIENDS
。当定义了这个选项后,包括m_bits
在内的私有成员变量就变成了公共的(我认为这是为了处理旧编译器或其他问题)。namespace boost {
template <typename B, typename A>
std::size_t hash_value(const boost::dynamic_bitset<B, A>& bs) {
return boost::hash_value(bs.m_bits);
}
}
这是一个功能请求。
可以通过将位集转换为向量临时变量来实现一个不太高效的唯一哈希:
namespace boost {
template <typename B, typename A>
std::size_t hash_value(const boost::dynamic_bitset<B, A>& bs) {
std::vector<B, A> v;
boost::to_block_range(bs, std::back_inserter(v));
return boost::hash_value(v);
}
}
to_block_range
的函数,它将位集包含的单词复制到某个缓冲区中。为了避免实际复制,您可以定义自己的“输出迭代器”,仅处理单个单词并从中计算哈希值。关于如何计算哈希:例如,参见FNV哈希函数。dynamic_bitset
的设计在我看来很愚蠢,因为它不直接提供对底层缓冲区的访问权限(甚至没有作为const
)。dynamic_bitset
中的底层数据是私有的(m_bits
)。BOOST_DYNAMIC_BITSET_DONT_USE_FRIENDS
)to_block_range
,它是dynamic_bitset
的朋友。因此,该函数的特化也可以访问其私有数据(即m_bits
)。namespace boost {
// specialise dynamic bitset for size_t& to return the hash of the underlying data
template <>
inline void
to_block_range(const dynamic_bitset<>& b, size_t& hash_result)
{
hash_result = boost::hash_value(bs.m_bits);
}
std::size_t hash_value(const boost::dynamic_bitset<B, A>& bs)
{
size_t hash_result;
to_block_range(bs, hash_result);
return hash_result;
}
}
boost::dynamic_bitset
声明为:
template <typename Block = unsigned long, typename Allocator = std::allocator<Block> > class dynamic_bitset;
- Leo Goodstadttemplate <typename B, typename A, typename BlockOutputIterator> friend void to_block_range(const dynamic_bitset<B, A>& b, BlockOutputIterator result);
因此,在
template <> inline void to_block_range(const dynamic_bitset<>&, tuple<unsigned, unsigned, unsigned long&>)
中的特化意味着 typename B = unsigned long
,typename A = std::allocator<Block>
,typename BlockOutputIterator = tuple<unsigned, unsigned, unsigned long&>
。看起来像作弊,非常淘气...但是这是合法的C++。 - Leo Goodstadt在以下情况下,所提出的解决方案会生成相同的哈希值。
#define BOOST_DYNAMIC_BITSET_DONT_USE_FRIENDS
namespace boost {
template <typename B, typename A>
std::size_t hash_value(const boost::dynamic_bitset<B, A>& bs) {
return boost::hash_value(bs.m_bits);
}
}
boost::dynamic_biset<> test(1,false);
auto hash1 = boost::hash_value(test);
test.push_back(false);
auto hash2 = boost::hash_value(test);
// keep continue...
test.push_back(false);
auto hash31 = boost::hash_value(test);
// magically all hash1 to hash31 are the same!
对于哈希映射,所提出的解决方案有时不合适。
我阅读了dynamic_bitset的源代码,了解到dynamic_bitset与vector<bool>
一样,每个值存储一个位。例如,您调用dynamic_bitset<> test(1, false)
,那么dynamic_bitset最初分配4个字节,全部为零,并且它保存位的大小(在这种情况下,大小为1)。请注意,如果位的大小变得大于32,则会再次分配4个字节并将其推回到dynamic_bitsets<>::m_bits
中(因此m_bits是4字节块的向量)。
如果我调用test.push_back(x)
,它将第二位设置为x,并将位的大小增加到2。如果x
为false,则m_bits[0]
根本不会改变!为了正确计算哈希,我们需要在哈希计算中考虑m_num_bits。
那么问题是如何解决?
1:使用boost::hash_combine
这种方法简单直接。我没有检查这是否编译。
namespace boost {
template <typename B, typename A>
std::size_t hash_value(const boost::dynamic_bitset<B, A>& bs) {
size_t tmp = 0;
boost::hash_combine(tmp,bs.m_num_bits);
boost::hash_combine(tmp,bs.m_bits);
return tmp;
}
}
2:翻转 m_num_bits % bits_per_block 位。 根据位数翻转一个位。我相信这种方法比1更快。
namespace boost {
template <typename B, typename A>
std::size_t hash_value(const boost::dynamic_bitset<B, A>& bs) {
// you may need more sophisticated bit shift approach.
auto bit = 1u << (bs.m_num_bits % bs.bits_per_block);
auto return_val = boost::hash_value(bs.m_bits);
// sorry this was wrong
//return (return_val & bit) ? return_val | bit : return_val & (~bit);
return (return_val & bit) ? return_val & (~bit) : return_val | bit;
}
}