| #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 |