C++ 是否有平面未排序映射/集合实现?
有C++ 是否有平面未排序映射/集合实现?,c++,c++11,boost,stl,containers,C++,C++11,Boost,Stl,Containers,有boost.containerflat\u map等,还有LokiAssocVector等,它们可以保持元素的有序性 是否有一个适合作为地图/集合的未排序向量的现代(c++11移动启用等)实现 其思想是将其用于非常小的映射/集(少于20个元素)和简单的键(哈希不总是有意义)有std::unordered\u set和std::unordered\u map,但据我所知,它们不是使用向量实现的 一个可能的选择是编写您自己的哈希向量,并使用std::hash对密钥进行哈希,然后根据向量的长度对生成
boost.container
flat\u map
等,还有LokiAssocVector
等,它们可以保持元素的有序性
是否有一个适合作为地图/集合的未排序向量的现代(c++11移动启用等)实现
其思想是将其用于非常小的映射/集(少于20个元素)和简单的键(哈希不总是有意义)有
std::unordered\u set
和std::unordered\u map
,但据我所知,它们不是使用向量实现的
一个可能的选择是编写您自己的哈希向量,并使用std::hash
对密钥进行哈希,然后根据向量的长度对生成的数字进行索引,但随后您必须找到一种手动处理冲突和所有生成问题的方法。我不确定我是否推荐了
另一种方法是将自定义分配器传递给
std::unordered_set
和std::unordered_map
,它们对向量执行分配(例如,通过拥有一个内部向量),如所建议的那样。如果集合一定很小,那么您可以只使用std::vector
(或std::deque
)并使用线性搜索进行查找。在小向量上的O(n)线性搜索可能比在更复杂的结构(如红黑树)上的O(log(n))搜索更快
因此,您可以将元素放入向量中,而无需对它们进行排序。如果删除元素,您仍然需要执行一些洗牌操作,但是对于类似于平面向量的结构,无论它是否已排序,这始终是正确的,除非您只删除后面的元素。为什么它是平的很重要?像这样的东西
template<class Key, class Value, template<class...>class Storage=std::vector>
struct flat_map {
struct kv {
Key k;
Value v;
template<class K, class V>
kv( K&& kin, V&& vin ):k(std::forward<K>(kin)), v(std::forward<V>(vin)){}
};
using storage_t = Storage<kv>;
storage_t storage;
// TODO: adl upgrade
using iterator=decltype(std::begin(std::declval<storage_t&>()));
using const_iterator=decltype(std::begin(std::declval<const storage_t&>()));
// boilerplate:
iterator begin() {
using std::begin;
return begin(storage);
}
const_iterator begin() const {
using std::begin;
return begin(storage);
}
const_iterator cbegin() const {
using std::begin;
return begin(storage);
}
iterator end() {
using std::end;
return end(storage);
}
const_iterator end() const {
using std::end;
return end(storage);
}
const_iterator cend() const {
using std::end;
return end(storage);
}
size_t size() const {
return storage.size();
}
bool empty() const {
return storage.empty();
}
// these only have to be valid if called:
void reserve(size_t n) {
storage.reserve(n);
}
size_t capacity() const {
return storage.capacity();
}
// map-like interface:
// TODO: SFINAE check for type of key
template<class K>
Value& operator[](K&& k){
auto it = find(k);
if (it != end()) return it->v;
storage.emplace_back( std::forward<K>(k), Value{} );
return storage.back().v;
}
private: // C++14, but you can just inject the lambda at point of use in 11:
template<class K>
auto key_match( K& k ) {
return [&k](kv const& kv){
return kv.k == k;
};
}
public:
template<class K>
iterator find(K&& k) {
return std::find_if( begin(), end(), key_match(k) );
}
template<class K>
const_iterator find(K&& k) const {
return const_cast<flat_map*>(this)->find(k);
}
// iterator-less query functions:
template<class K>
Value* get(K&& k) {
auto it = find(std::forward<K>(k));
if (it==end()) return nullptr;
return std::addressof(it->v);
}
template<class K>
Value const* get(K&& k) const {
return const_cast<flat_map*>(this)->get(std::forward<K>(k));
}
// key-based erase: (SFINAE should be is_comparible, but that doesn't exist)
template<class K, class=std::enable_if_t<std::is_converible<K, Key>{}>>
bool erase(K&& k) {
auto it = std::remove(
storage.begin(), storage.end(), key_match(std::forward<K>(k))
);
if (it == storage.end()) return false;
storage.erase( it, storage.end() );
return true;
}
// classic erase, for iterating:
iterator erase(const_iterator it) {
return storage.erase(it);
}
template<class K2, class V2,
class=std::enable_if_t<
std::is_convertible< K2, Key >{}&&
std::is_convertible< V2, Value >{}
>
>
void set( K2&& kin, V2&& vin ) {
auto it = find(kin);
if (it != end()){
it->second = std::forward<V2>(vin);
return;
} else {
storage.emplace_back( std::forward<K2>(kin), std::forward<V2>(vin) );
}
}
};
模板
结构平面图{
结构千伏{
键k;
v值;
模板
千伏(K&&kin,V&&vin):K(std::forward(kin)),V(std::forward(vin)){}
};
使用存储=存储;
储存(t)储存;;
//TODO:adl升级
使用迭代器=decltype(std::begin(std::declval());
使用const_迭代器=decltype(std::begin(std::declval());
//样板:
迭代器begin(){
使用std::begin;
返回开始(存储);
}
常量迭代器begin()常量{
使用std::begin;
返回开始(存储);
}
常量迭代器cbegin()常量{
使用std::begin;
返回开始(存储);
}
迭代器结束(){
使用std::end;
返回端(存储);
}
常量迭代器end()常量{
使用std::end;
返回端(存储);
}
常量迭代器cend()常量{
使用std::end;
返回端(存储);
}
大小\u t大小()常量{
返回storage.size();
}
bool empty()常量{
return storage.empty();
}
//这些仅在调用时有效:
无效储备(大小){
储存.储备(n);
}
大小\u t容量()常数{
返回存储容量();
}
//地图式界面:
//TODO:SFINAE检查密钥类型
模板
值和运算符[](K&&K){
自动it=查找(k);
如果(it!=end())返回它->v;
放置在后面(std::forward(k),值{});
返回存储.back().v;
}
private://C++14,但您可以在11中的使用点注入lambda:
模板
自动键匹配(K&K){
返回[&k](千伏常数和千伏){
返回kv.k==k;
};
}
公众:
模板
迭代器查找(K&&K){
返回std::find_if(begin(),end(),key_match(k));
}
模板
常量迭代器查找(K&&K)常量{
return const_cast(this)->find(k);
}
//无迭代器查询函数:
模板
值*获取(K&&K){
autoit=find(std::forward(k));
if(it==end())返回nullptr;
返回std::addressof(it->v);
}
模板
值常量*获取(K&&K)常量{
return const_cast(this)->get(std::forward(k));
}
//基于键的擦除:(SFINAE应该是可比较的,但不存在)
模板
布尔擦除(K&&K){
自动it=std::删除(
storage.begin()、storage.end()、键匹配(std::forward(k))
);
if(it==storage.end())返回false;
storage.erase(it,storage.end());
返回true;
}
//经典擦除,用于迭代:
迭代器擦除(常量迭代器){
返回存储。擦除(它);
}
模板{}&&
std::是否可转换{}
>
>
无效集(K2和kin、V2和vin){
auto it=查找(kin);
如果(it!=end()){
it->second=std::forward(vin);
返回;
}否则{
存储。向后放置(标准::向前(kin),标准::向前(vin));
}
}
};
我将容器类型保留为模板参数,因此如果您愿意,可以使用类似SBO向量的结构
理论上,我应该公开一个模板参数来替换键上的equals。但是,我确实使密钥搜索功能透明。Evgeny Panasyuk是正确的,我相信您需要的是一个开放地址哈希映射
这完全符合您的需求,只有1个平面缓冲区,没有节点分配,没有要遵循的指针,并且没有排序
否则,您也有flat\u map
/AssocVector
,但它们是排序的,与您的需求不同
对于OAHM,这里有一个类似于通用STL的实现:
此外,您可能还需要查看flat\u map
:
OAHM在所有测试中的性能都非常接近于平面图
,但迭代除外。我不确定您到底想要什么。为什么将其存储为平面(即向量)很重要?要使用未排序的向量作为映射/集,您需要一些已排序的索引,您是否计划将该索引保留在容器外部?@onqtam,但这样您的键将未排序,您必须进行线性搜索,而擦除元素仍然需要对元素进行混洗。如果可以接受,只需使用std::vector
(请参阅我的答案)。如果您关心缓存位置,则应将键和值保留在不同的向量中,除非所有内容都适合