有boost.container
flat_map
和其他人,以及Loki AssocVector
和其他许多其他类似的元素,可以对元素进行排序。
是否有适合作为地图/集的未分类矢量的现代(c ++ 11移动启用等)实现?
这个想法是将它用于非常小的地图/集(少于20个元素)和简单的键(散列不总是有意义)
答案 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_set
和std::unordered_map
但据我所知,它们并未使用向量实现。
一个可能的选择是编写自己的哈希向量并使用std::hash<Key>
对密钥进行哈希,然后将结果数量作为向量的长度的索引,但是你必须找到一种方法来处理冲突以及手动产生的所有问题。不确定我是否推荐过。
另一种方法是将自定义分配器传递给std::unordered_set
和std::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
。