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
等,还有Loki
AssocVector
等,它们可以保持元素的有序性

是否有一个适合作为地图/集合的未排序向量的现代(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
(请参阅我的答案)。如果您关心缓存位置,则应将键和值保留在不同的向量中,除非所有内容都适合