创建unordered_set的unordered_set

时间:2015-01-03 16:52:36

标签: c++ c++11 hash

我想创建一个容器,用于在其中存储唯一的整数集。

我想创建类似于

的东西
std::unordered_set<std::unordered_set<unsigned int>>

但是g ++并没有让我这样做并说:

invalid use of incomplete type 'struct std::hash<std::unordered_set<unsigned int> >'

我想要实现的是拥有独特的无符号整数集。

我该怎么做?

6 个答案:

答案 0 :(得分:7)

我正在为这个问题添加另一个答案,因为目前还没有人触及过一个关键点。

每个人都在告诉你需要为unordered_set<unsigned>创建一个哈希函数,这是正确的。你可以通过专门化std::hash<unordered_set<unsigned>>来实现,或者你可以创建自己的仿函数并像这样使用它:

unordered_set<unordered_set<unsigned>, my_unordered_set_hash_functor> s;

无论哪种方式都没问题。 然而您需要注意一个很大的问题:

对于比较相等(unordered_set<unsigned>)的任何两个x == y,他们必须哈希到相同的值:hash(x) == hash(y)。如果您未遵循此规则,则会出现运行时错误。另请注意,以下两个unordered_set比较相等(为清晰起见,此处使用伪代码):

{1, 2, 3} == {3, 2, 1}

因此hash({1, 2, 3}) 必须等于hash({3, 2, 1})。换句话说,无序容器具有相等运算符,其中顺序无关紧要。因此,无论如何构造哈希函数,其结果必须独立于容器中元素的顺序。

或者,您可以替换unordered_set中使用的等式谓词,使其确实遵守顺序:

unordered_set<unordered_set<unsigned>, my_unordered_set_hash_functor,
                                       my_unordered_equal> s;

让所有这一切变得正确的负担使得:

unodered_set<set<unsigned>, my_set_hash_functor>
看起来很有吸引力。您仍然需要为set<unsigned>创建哈希仿函数,但现在您不必担心为{1, 2, 3}{3, 2, 1}获取相同的哈希码。相反,您必须确保这些哈希码不同。

我注意到Walter's answer给出了一个具有正确行为的哈希函子:它忽略了计算哈希码的顺序。但他的回答(目前)告诉你,这不是一个好的解决方案。 :-)它实际上 是无序容器的一个很好的解决方案。更好的解决方案是返回单个散列的总和,而不是散列元素的总和。

答案 1 :(得分:4)

您可以这样做,但是与每个unsorted_set/map元素类型一样,内部unsorted_set现在需要定义一个Hash函数。它默认没有,但你可以自己写一个。

答案 2 :(得分:3)

您需要做的是为std::unordered_set<unsigned int>类型的键定义适当的哈希值(因为operator==对于此键是already defined,您不需要提供{{ 1}} EqualKey的模板参数。

一个简单的(尽管效率低下)选项是对集合中所有元素的总和进行散列。这看起来类似于:

std::unordered_set<std::unordered_set<unsigned int>, Hash, EqualKey>

然而,虽然简单,但这并不好,因为它不能保证以下要求。 对于不相等的两个不同参数k1和k2,template<typename T> struct hash_on_sum : private std::hash<typename T::element_type> { typedef T::element_type count_type; typedef std::hash<count_type> base; std::size_t operator()(T const&obj) const { return base::operator()(std::accumulate(obj.begin(),obj.end(),count_type())); } }; typedef std::unordered_set<unsigned int> inner_type; typedef std::unordered_set<inner_type, hash_on_sum<inner_type>> set_of_unique_sets; 应该非常小,接近std::hash<Key>()(k1) == std::hash<Key>()(k2)的概率。

答案 3 :(得分:2)

std::unordered_set<unsigned int>>不符合std::unordered_set元素的要求,因为没有默认的哈希函数(即std::hash<>不适用于std::unordered_set<unsigned int>>)。< / p>

你可以提供一个(它应该很快,并尽可能避免碰撞):

class MyHash
{
public:
    std::size_t operator()(const std::unordered_set<unsigned int>& s) const 
    {
        return ... // return some meaningful hash of the et elements
    }
};

int main() {

    std::unordered_set<std::unordered_set<unsigned int>, MyHash> u;

}

您可以在this answer中看到非常好的哈希函数示例。

你应该真正提供两个一个Hash和一个满足无序关联容器标准要求的Equality函数。

答案 4 :(得分:0)

Hash()创建集合元素哈希的默认函数不知道如何将整个集合作为元素处理。创建一个哈希函数,为每个唯一的集合创建一个唯一的值,然后你就可以了。

这是unordered_set

的构造函数

explicit unordered_set( size_type bucket_count = /*implementation-defined*/, const Hash& hash = Hash(), const KeyEqual& equal = KeyEqual(), const Allocator& alloc = Allocator() ); http://en.cppreference.com/w/cpp/container/unordered_set/unordered_set

也许最简单的事情是为unordered_set<unsigned int>

创建一个哈希函数
unsigned int my_hash(std::unordered_set<unsigned int>& element)
{
  for( e : element )
  {
     some sort of math to create a unique hash for every unique set
  }
}

编辑:如另一个答案中所见,我完全忘了,散列函数必须在Hash对象中。至少根据我在答案中粘贴的构造函数。

答案 5 :(得分:0)

unordered_set没有哈希的原因。默认情况下,unordered_set是可变序列。只要对象在unordered_set中,哈希就必须保持相同的值。因此,您的元素必须是不可变的。使用修饰符const&无法保证这一点,因为它只保证只有主unordered_set及其方法不会修改子unordered_set。不使用引用可能是一个安全的解决方案(你仍然需要编写哈希函数)但是你真的想要移动/复制unordered_set的开销吗?

你可以使用某种指针。这可以;指针只是一个内存地址,而你的unordered_set本身不会重定位(它可能会重新分配它的元素池,但谁在乎?)。因此,您的指针是常量,并且它可以在unordered_set中保留其生命周期中的相同哈希值。 (编辑:正如霍华德指出的那样,你必须确保你的元素的任何订单都存储在你的集合中,如果两个集合具有相同的元素,则认为它们是相同的。通过强制执行订单来存储你的整数,你自由地得到两个集合对应两个相等的向量。)

作为奖励,您现在可以在主集中使用智能指针来管理子unordered_set的内存(如果您在堆上分配它们)。

请注意,这仍然不是获取int集合的最有效实现。为了使你成为子集,你可以在std::vector周围编写一个快速包装器来存储int,按值排序。 int int比较小且便宜,使用dichotomic search的复杂性仅为O(log n)std::unordered_set是一个沉重的结构,你从O(1)O(log n)失去了什么,你可以通过为每个集合提供紧凑的内存来获得它。这不应该太难实现,但几乎可以保证在性能上更好。

更难实现解决方案将涉及trie