362 lines
12 KiB
C++
362 lines
12 KiB
C++
#ifndef VTR_FLAT_MAP
|
|
#define VTR_FLAT_MAP
|
|
#include <functional>
|
|
#include <vector>
|
|
#include <algorithm>
|
|
#include <stdexcept>
|
|
|
|
#include "vtr_assert.h"
|
|
|
|
namespace vtr {
|
|
|
|
//Forward declaration
|
|
template<class K, class V, class Compare = std::less<K>>
|
|
class flat_map;
|
|
|
|
template<class K, class V, class Compare = std::less<K>>
|
|
class flat_map2;
|
|
|
|
//Helper function to create a flat map from a vector of pairs
|
|
//without haveing to explicity specify the key and value types
|
|
template<class K, class V>
|
|
flat_map<K, V> make_flat_map(std::vector<std::pair<K, V>>&& vec) {
|
|
return flat_map<K, V>(std::move(vec));
|
|
}
|
|
template<class K, class V>
|
|
flat_map2<K, V> make_flat_map2(std::vector<std::pair<K, V>>&& vec) {
|
|
return flat_map2<K, V>(std::move(vec));
|
|
}
|
|
|
|
//
|
|
// flat_map is a (nearly) std::map compatible container which uses a vector
|
|
// as it's underlying storage. Internally the stored elements are kept sorted
|
|
// allowing efficient look-up in O(logN) time via binary search.
|
|
//
|
|
// This container is typically useful in the following scenarios:
|
|
// * Reduced memory usage if key/value are small (std::map needs to store pointers to
|
|
// other BST nodes which can add substantial overhead for small keys/values)
|
|
// * Faster search/iteration by exploiting data locality (all elments are in continguous
|
|
// memory enabling better spatial locality)
|
|
//
|
|
// The container deviates from the behaviour of std::map in the following important ways:
|
|
// * Insertion/erase takes O(N) instead of O(logN) time
|
|
// * Iterators may be invalidated on insertion/erase (i.e. if the vector is reallocated)
|
|
//
|
|
// The slow insertion/erase performance makes this container poorly suited to maps that
|
|
// frequently add/remove new keys. If this is required you likely want std::map or
|
|
// std::unordered_map. However if the map is constructed once and then repeatedly quieried,
|
|
// consider using the range or vector-based constructors which initializes the flat_map in
|
|
// O(NlogN) time.
|
|
//
|
|
template<class K, class T, class Compare>
|
|
class flat_map {
|
|
public:
|
|
typedef K key_type;
|
|
typedef T mapped_type;
|
|
typedef std::pair<K, T> value_type;
|
|
typedef Compare key_compare;
|
|
typedef value_type& reference;
|
|
typedef const value_type& const_reference;
|
|
typedef typename std::vector<value_type>::iterator iterator;
|
|
typedef typename std::vector<value_type>::const_iterator const_iterator;
|
|
typedef typename std::vector<value_type>::reverse_iterator reverse_iterator;
|
|
typedef typename std::vector<value_type>::const_reverse_iterator const_reverse_iterator;
|
|
typedef typename std::vector<value_type>::difference_type difference_type;
|
|
typedef typename std::vector<value_type>::size_type size_type;
|
|
|
|
class value_compare;
|
|
|
|
public:
|
|
//Standard big 5
|
|
flat_map() = default;
|
|
flat_map(const flat_map&) = default;
|
|
flat_map(flat_map&&) = default;
|
|
flat_map& operator=(const flat_map&) = default;
|
|
flat_map& operator=(flat_map&&) = default;
|
|
|
|
//range constructor
|
|
template<class InputIterator>
|
|
flat_map(InputIterator first, InputIterator last) {
|
|
//Copy the values
|
|
std::copy(first, last, std::back_inserter(vec_));
|
|
|
|
sort();
|
|
uniquify();
|
|
}
|
|
|
|
//direct vector constructor
|
|
explicit flat_map(std::vector<value_type>&& values) {
|
|
//By moving the values this should be more efficient
|
|
//than the range constructor which must copy each element
|
|
vec_ = std::move(values);
|
|
|
|
sort();
|
|
uniquify();
|
|
}
|
|
|
|
iterator begin() { return vec_.begin(); }
|
|
const_iterator begin() const { return vec_.begin(); }
|
|
iterator end() { return vec_.end(); }
|
|
const_iterator end() const { return vec_.end(); }
|
|
reverse_iterator rbegin() { return vec_.rbegin(); }
|
|
const_reverse_iterator rbegin() const { return vec_.rbegin(); }
|
|
reverse_iterator rend() { return vec_.rend(); }
|
|
const_reverse_iterator rend() const { return vec_.rend(); }
|
|
const_iterator cbegin() const { return vec_.begin(); }
|
|
const_iterator cend() const { return vec_.end(); }
|
|
const_reverse_iterator crbegin() const { return vec_.rbegin(); }
|
|
const_reverse_iterator crend() const { return vec_.rend(); }
|
|
|
|
bool empty() const { return vec_.empty(); }
|
|
size_type size() const { return vec_.size(); }
|
|
size_type max_size() const { return vec_.max_size(); }
|
|
|
|
const mapped_type& operator[](const key_type& key) const {
|
|
auto iter = find(key);
|
|
if (iter == end()) {
|
|
//Not found
|
|
throw std::out_of_range("Invalid key");
|
|
}
|
|
|
|
return iter->second;
|
|
}
|
|
|
|
mapped_type& operator[](const key_type& key) {
|
|
auto iter = find(key);
|
|
if (iter == end()) {
|
|
//Not found
|
|
iter = insert(std::make_pair(key, mapped_type())).first;
|
|
}
|
|
|
|
return iter->second;
|
|
}
|
|
|
|
mapped_type& at(const key_type& key) {
|
|
return const_cast<mapped_type&>(const_cast<const flat_map*>(this)->at(key));
|
|
}
|
|
|
|
const mapped_type& at(const key_type& key) const {
|
|
auto iter = find(key);
|
|
if (iter == end()) {
|
|
throw std::out_of_range("Invalid key");
|
|
}
|
|
return iter->second;
|
|
}
|
|
|
|
//Insert value
|
|
std::pair<iterator, bool> insert(const value_type& value) {
|
|
auto iter = lower_bound(value.first);
|
|
if (iter != end() && keys_equivalent(iter->first, value.first)) {
|
|
//Found existing
|
|
return std::make_pair(iter, false);
|
|
} else {
|
|
//Insert
|
|
iter = insert(iter, value);
|
|
|
|
return std::make_pair(iter, true);
|
|
}
|
|
}
|
|
|
|
//Insert value with position hint
|
|
iterator insert(const_iterator position, const value_type& value) {
|
|
//In a legal position
|
|
VTR_ASSERT(position == begin() || value_comp()(*(position - 1), value));
|
|
VTR_ASSERT((size() > 0 && position == --end()) || position == end() || !value_comp()(*(position + 1), value));
|
|
|
|
iterator iter = vec_.insert(position, value);
|
|
|
|
return iter;
|
|
}
|
|
|
|
//Insert range
|
|
template<class InputIterator>
|
|
void insert(InputIterator first, InputIterator last) {
|
|
vec_.insert(vec_.end(), first, last);
|
|
|
|
//TODO: could be more efficient
|
|
sort();
|
|
uniquify();
|
|
}
|
|
|
|
//Erase by key
|
|
void erase(const key_type& key) {
|
|
auto iter = find(key);
|
|
if (iter != end()) {
|
|
vec_.erase(iter);
|
|
}
|
|
}
|
|
|
|
//Erase at iterator
|
|
void erase(const_iterator position) {
|
|
vec_.erase(position);
|
|
}
|
|
|
|
//Erase range
|
|
void erase(const_iterator first, const_iterator last) {
|
|
vec_.erase(first, last);
|
|
}
|
|
|
|
void swap(flat_map& other) { std::swap(*this, other); }
|
|
|
|
void clear() { vec_.clear(); }
|
|
|
|
template<class... Args>
|
|
iterator emplace(const key_type& key, Args&&... args) {
|
|
auto iter = lower_bound(key);
|
|
if (iter != end() && keys_equivalent(iter->first, key)) {
|
|
//Found
|
|
return std::make_pair(iter, false);
|
|
} else {
|
|
//Emplace
|
|
iter = emplace_hint(iter, key, std::forward<Args>(args)...);
|
|
return std::make_pair(iter, true);
|
|
}
|
|
}
|
|
|
|
template<class... Args>
|
|
iterator emplace_hint(const_iterator position, Args&&... args) {
|
|
return vec_.emplace(position, std::forward<Args>(args)...);
|
|
}
|
|
|
|
void reserve(size_type n) { vec_.reserve(n); }
|
|
void shrink_to_fit() { vec_.shrink_to_fit(); }
|
|
|
|
key_compare key_comp() const { return key_compare(); }
|
|
value_compare value_comp() const { return value_compare(key_comp()); }
|
|
|
|
iterator find(const key_type& key) {
|
|
const_iterator const_iter = const_cast<const flat_map*>(this)->find(key);
|
|
return convert_to_iterator(const_iter);
|
|
}
|
|
|
|
const_iterator find(const key_type& key) const {
|
|
auto iter = lower_bound(key);
|
|
if (iter != end() && keys_equivalent(iter->first, key)) {
|
|
//Found
|
|
return iter;
|
|
}
|
|
return end();
|
|
}
|
|
|
|
size_type count(const key_type& key) const {
|
|
return (find(key) == end()) ? 0 : 1;
|
|
}
|
|
|
|
iterator lower_bound(const key_type& key) {
|
|
const_iterator const_iter = const_cast<const flat_map*>(this)->lower_bound(key);
|
|
return convert_to_iterator(const_iter);
|
|
}
|
|
|
|
const_iterator lower_bound(const key_type& key) const {
|
|
return std::lower_bound(begin(), end(), key, value_comp());
|
|
}
|
|
|
|
iterator upper_bound(const key_type& key) {
|
|
const_iterator const_iter = const_cast<const flat_map*>(this)->upper_bound(key);
|
|
return convert_to_iterator(const_iter);
|
|
}
|
|
|
|
const_iterator upper_bound(const key_type& key) const {
|
|
return std::upper_bound(begin(), end(), key, value_comp());
|
|
}
|
|
|
|
std::pair<iterator, iterator> equal_range(const key_type& key) {
|
|
auto const_iter_pair = const_cast<const flat_map*>(this)->equal_range(key);
|
|
return std::pair<iterator, iterator>(iterator(const_iter_pair.first), iterator(const_iter_pair.second));
|
|
}
|
|
|
|
std::pair<const_iterator, const_iterator> equal_range(const key_type& key) const {
|
|
return std::equal_range(begin(), end(), key);
|
|
}
|
|
|
|
public:
|
|
friend void swap(flat_map& lhs, flat_map& rhs) { std::swap(lhs.vec_, rhs.vec_); }
|
|
|
|
private:
|
|
bool keys_equivalent(const key_type& lhs, const key_type& rhs) const {
|
|
return !key_comp()(lhs, rhs) && !key_comp()(rhs, lhs);
|
|
}
|
|
|
|
void sort() {
|
|
std::sort(vec_.begin(), vec_.end(), value_comp());
|
|
}
|
|
|
|
void uniquify() {
|
|
//Uniquify
|
|
auto key_equal_pred = [this](const value_type& lhs, const value_type& rhs) {
|
|
return !value_comp()(lhs, rhs) && !value_comp()(rhs, lhs);
|
|
};
|
|
vec_.erase(std::unique(vec_.begin(), vec_.end(), key_equal_pred), vec_.end());
|
|
}
|
|
|
|
iterator convert_to_iterator(const_iterator const_iter) {
|
|
//This is a work around for the fact that there is no conversion between
|
|
//a const_iterator and iterator.
|
|
//
|
|
//We intiailize i to the start of the container and then advance it by
|
|
//the distance to const_iter. The resulting i points to the same element
|
|
//as const_iter
|
|
//
|
|
//Note that to be able to call std::distance with an iterator and
|
|
//const_iterator we need to specify the type as const_iterator (relying
|
|
//on the implicit conversion from iterator to const_iterator for i)
|
|
//
|
|
//Since the iterators are really vector (i.e. random-access) iterators
|
|
//this takes constant time
|
|
iterator i = begin();
|
|
std::advance(i, std::distance<const_iterator>(i, const_iter));
|
|
return i;
|
|
}
|
|
|
|
private:
|
|
std::vector<value_type> vec_;
|
|
};
|
|
|
|
//Like flat_map, but operator[] never inserts and directly returns the mapped value
|
|
template<class K, class T, class Compare>
|
|
class flat_map2 : public flat_map<K, T, Compare> {
|
|
public:
|
|
flat_map2() {}
|
|
explicit flat_map2(std::vector<typename flat_map2<K, T, Compare>::value_type>&& values)
|
|
: flat_map<K, T, Compare>(std::move(values)) {}
|
|
|
|
const T& operator[](const K& key) const {
|
|
auto itr = this->find(key);
|
|
if (itr == this->end()) {
|
|
throw std::logic_error("Key not found");
|
|
}
|
|
return itr->second;
|
|
}
|
|
|
|
T& operator[](const K& key) {
|
|
return const_cast<T&>(const_cast<const flat_map2*>(this)->operator[](key));
|
|
}
|
|
};
|
|
|
|
template<class K, class T, class Compare>
|
|
class flat_map<K, T, Compare>::value_compare {
|
|
friend class flat_map;
|
|
|
|
public:
|
|
bool operator()(const value_type& x, const value_type& y) const {
|
|
return comp(x.first, y.first);
|
|
}
|
|
|
|
//For std::lower_bound/std::upper_bound
|
|
bool operator()(const value_type& x, const key_type& y) const {
|
|
return comp(x.first, y);
|
|
}
|
|
bool operator()(const key_type& x, const value_type& y) const {
|
|
return comp(x, y.first);
|
|
}
|
|
|
|
private:
|
|
value_compare(Compare c)
|
|
: comp(c) {}
|
|
|
|
Compare comp;
|
|
};
|
|
|
|
} // namespace vtr
|
|
#endif
|