是否存在平面未排序的地图/集实现?

时间:2015-06-19 12:19:50

标签: c++ c++11 boost stl containers

boost.container flat_map和其他人,以及Loki AssocVector和其他许多其他类似的元素,可以对元素进行排序。

是否有适合作为地图/集的未分类矢量的现代(c ++ 11移动启用等)实现?

这个想法是将它用于非常小的地图/集(少于20个元素)和简单的键(散列不总是有意义)

4 个答案:

答案 0 :(得分:7)

这样的东西?

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) );
    }
  }
};

我将容器类型作为模板参数保留,因此如果您选择,可以使用类似SBO矢量的结构。

理论上,我应该公开一个模板参数来替换键上的等号。但是,我确实使密钥搜索功能透明化。

答案 1 :(得分:4)

如果集合肯定很小,那么您可以使用std::vector(或std::deque)并使用线性搜索进行查找。在较小的矢量上进行O(n)线性搜索可以比在更复杂的结构(如红黑树)上进行O(log(n))搜索更快。

所以你可以在vector中放置元素而不对它们进行排序。如果删除元素,你仍然需要进行一些改组,但是对于类似于平面的矢量结构,无论它是否已经排序,它总是如此,除非你只删除了后面的元素。无论如何,为什么它很扁平很重要?

答案 2 :(得分:1)

std::unordered_setstd::unordered_map但据我所知,它们并未使用向量实现。

一个可能的选择是编写自己的哈希向量并使用std::hash<Key>对密钥进行哈希,然后将结果数量作为向量的长度的索引,但是你必须找到一种方法来处理冲突以及手动产生的所有问题。不确定我是否推荐过。

另一种方法是将自定义分配器传递给std::unordered_setstd::unordered_map,它们在向量上执行分配(例如通过拥有内部向量),如@BeyelerStudios所示。

答案 3 :(得分:1)

Evgeny Panasyuk是正确的,我相信你想要的是开放地址哈希地图
这完全符合您的要求,只有1个扁平缓冲区,没有节点分配,没有指针可以跟随,并且未排序。

否则您还有flat_map / AssocVector,但它们会按照您的要求进行排序。

对于OAHM,我在这里有一个类似STL的通用实现:
https://sourceforge.net/projects/cgenericopenaddresshashmap/

另外,您可能需要查看flat_map的基准页面:
boost::flat_map and its performance compared to map and unordered_map
除迭代外,OAHM在所有测试中的表现都非常接近flat_map