我想创建一个容器,用于在其中存储唯一的整数集。
我想创建类似于
的东西std::unordered_set<std::unordered_set<unsigned int>>
但是g ++并没有让我这样做并说:
invalid use of incomplete type 'struct std::hash<std::unordered_set<unsigned int> >'
我想要实现的是拥有独特的无符号整数集。
我该怎么做?
答案 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。