#ifndef VTR_FLAT_MAP #define VTR_FLAT_MAP #include #include #include #include #include "vtr_assert.h" namespace vtr { //Forward declaration template> class flat_map; template> 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 flat_map make_flat_map(std::vector>&& vec) { return flat_map(std::move(vec)); } template flat_map2 make_flat_map2(std::vector>&& vec) { return flat_map2(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 flat_map { public: typedef K key_type; typedef T mapped_type; typedef std::pair value_type; typedef Compare key_compare; typedef value_type& reference; typedef const value_type& const_reference; typedef typename std::vector::iterator iterator; typedef typename std::vector::const_iterator const_iterator; typedef typename std::vector::reverse_iterator reverse_iterator; typedef typename std::vector::const_reverse_iterator const_reverse_iterator; typedef typename std::vector::difference_type difference_type; typedef typename std::vector::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 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&& 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(const_cast(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 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 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 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)...); return std::make_pair(iter, true); } } template iterator emplace_hint(const_iterator position, Args&&... args) { return vec_.emplace(position, std::forward(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(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(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(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 equal_range(const key_type& key) { auto const_iter_pair = const_cast(this)->equal_range(key); return std::pair(iterator(const_iter_pair.first), iterator(const_iter_pair.second)); } std::pair 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(i, const_iter)); return i; } private: std::vector vec_; }; //Like flat_map, but operator[] never inserts and directly returns the mapped value template class flat_map2 : public flat_map { public: flat_map2() {} explicit flat_map2(std::vector::value_type>&& values) : flat_map(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(const_cast(this)->operator[](key)); } }; template class flat_map::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