// Copyright 2010-2021 Google LLC // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // A faster version of flat_hash_map for Variable and LinearConstraint keys. #ifndef OR_TOOLS_MATH_OPT_CPP_ID_MAP_H_ #define OR_TOOLS_MATH_OPT_CPP_ID_MAP_H_ #include #include #include #include #include "absl/container/flat_hash_map.h" #include "absl/container/flat_hash_set.h" #include "absl/types/span.h" #include "ortools/base/logging.h" #include "ortools/base/strong_int.h" #include "ortools/math_opt/core/arrow_operator_proxy.h" // IWYU pragma: export #include "ortools/math_opt/core/model_storage.h" #include "ortools/math_opt/cpp/key_types.h" namespace operations_research { namespace math_opt { // Similar to a absl::flat_hash_map for K as Variable or LinearConstraint. // // Important differences: // * The storage is more efficient, as we store the underlying ids directly. // * The consequence of that is that the keys are usually returned by value in // situations where the flat_hash_map would return references. // * You cannot mix variables/constraints from multiple models in these maps. // Doing so results in a CHECK failure. // // Implementation notes: // * Emptying the map (with clear() or erase()) resets the underlying model to // nullptr, enabling reusing the same instance with a different model. // * Operator= and swap() support operating with different models by // respectively replacing or swapping it. // * For details requirements on K, see key_types.h. // // See also IdSet for the equivalent class for sets. template class IdMap { public: using IdType = typename K::IdType; using StorageType = absl::flat_hash_map; using key_type = K; using mapped_type = V; using value_type = std::pair; using size_type = typename StorageType::size_type; using difference_type = typename StorageType::difference_type; using reference = std::pair; using const_reference = std::pair; using pointer = void; using const_pointer = void; class iterator { public: using value_type = IdMap::value_type; using reference = IdMap::reference; using pointer = IdMap::pointer; using difference_type = IdMap::difference_type; using iterator_category = std::forward_iterator_tag; iterator() = default; inline reference operator*() const; inline internal::ArrowOperatorProxy operator->() const; inline iterator& operator++(); inline iterator operator++(int); friend bool operator==(const iterator& lhs, const iterator& rhs) { return lhs.storage_iterator_ == rhs.storage_iterator_; } friend bool operator!=(const iterator& lhs, const iterator& rhs) { return lhs.storage_iterator_ != rhs.storage_iterator_; } private: friend class IdMap; inline iterator(const IdMap* map, typename StorageType::iterator storage_iterator); const IdMap* map_ = nullptr; typename StorageType::iterator storage_iterator_; }; class const_iterator { public: using value_type = IdMap::value_type; using reference = IdMap::const_reference; using pointer = IdMap::const_pointer; using difference_type = IdMap::difference_type; using iterator_category = std::forward_iterator_tag; const_iterator() = default; inline const_iterator(const iterator& non_const_iterator); // NOLINT inline reference operator*() const; inline internal::ArrowOperatorProxy operator->() const; inline const_iterator& operator++(); inline const_iterator operator++(int); friend bool operator==(const const_iterator& lhs, const const_iterator& rhs) { return lhs.storage_iterator_ == rhs.storage_iterator_; } friend bool operator!=(const const_iterator& lhs, const const_iterator& rhs) { return lhs.storage_iterator_ != rhs.storage_iterator_; } private: friend class IdMap; inline const_iterator( const IdMap* map, typename StorageType::const_iterator storage_iterator); const IdMap* map_ = nullptr; typename StorageType::const_iterator storage_iterator_; }; IdMap() = default; template inline IdMap(InputIt first, InputIt last); inline IdMap(std::initializer_list ilist); // Typically for internal use only. inline IdMap(const ModelStorage* storage, StorageType values); inline const_iterator cbegin() const; inline const_iterator begin() const; inline iterator begin(); inline const_iterator cend() const; inline const_iterator end() const; inline iterator end(); bool empty() const { return map_.empty(); } size_type size() const { return map_.size(); } inline void clear(); void reserve(size_type count) { map_.reserve(count); } inline std::pair insert(std::pair k_v); template inline void insert(InputIt first, InputIt last); inline void insert(std::initializer_list ilist); inline std::pair emplace(const K& k, V v); template inline std::pair try_emplace(const K& k, Args&&... args); // Returns the number of elements erased (zero or one). inline size_type erase(const K& k); // In STL erase(const_iterator) and erase(iterator) both return an // iterator. But flat_hash_map instead has void return types. So here we also // use void. // // In flat_hash_map, both erase(const_iterator) and erase(iterator) are // defined since there is also the erase(const K&) that exists and that // would be used. Since we don't have this overload, we can rely on the // automatic cast of the iterator in const_iterator. inline void erase(const_iterator pos); inline iterator erase(const_iterator first, const_iterator last); inline void swap(IdMap& other); inline const V& at(const K& k) const; inline V& at(const K& k); inline V& operator[](const K& k); inline size_type count(const K& k) const; inline bool contains(const K& k) const; inline iterator find(const K& k); inline const_iterator find(const K& k) const; inline std::pair equal_range(const K& k); inline std::pair equal_range( const K& k) const; // Updates the values in this map by adding the value of the corresponding // keys in the other map. For keys only in the other map, insert their value. // // This function is only available when type V supports operator+=. // // This is equivalent to (but is more efficient than): // for (const auto pair : other) { // (*this)[pair.first] += pair.second; // } // // This function CHECK that all the keys in the two maps have the same model. inline void Add(const IdMap& other); // Updates the values in this map by subtracting the value of the // corresponding keys in the other map. For keys only in the other map, insert // the opposite of their value. // // This function is only available when type V supports operator-=. // // This is equivalent to (but is more efficient than): // for (const auto pair : other) { // (*this)[pair.first] -= pair.second; // } // // This function CHECK that all the keys in the two maps have the same model. inline void Subtract(const IdMap& other); inline std::vector Values(absl::Span keys) const; inline absl::flat_hash_map Values( const absl::flat_hash_set& keys) const; inline std::vector SortedKeys() const; // Returns the values in sorted KEY order. inline std::vector SortedValues() const; const StorageType& raw_map() const { return map_; } const ModelStorage* storage() const { return storage_; } friend bool operator==(const IdMap& lhs, const IdMap& rhs) { return lhs.storage_ == rhs.storage_ && lhs.map_ == rhs.map_; } friend bool operator!=(const IdMap& lhs, const IdMap& rhs) { return !(lhs == rhs); } private: inline std::vector SortedIds() const; // CHECKs that storage_ and k.storage() matches when this map is not empty // (i.e. its storage_ is not null). When it is empty, simply check that // k.storage() is not null. inline void CheckModel(const K& k) const; // Sets storage_ to k.storage() if this map is empty (i.e. its storage_ is // null). Else CHECK that it has the same model. It also CHECK that // k.storage() is not null. inline void CheckOrSetModel(const K& k); // Sets storage_ to other.storage_ if this map is empty (i.e. its storage_ is // null). Else if the other map is not empty, CHECK that it has the same // model. inline void CheckOrSetModel(const IdMap& other); // Invariant: storage == nullptr if and only if map_.empty(). const ModelStorage* storage_ = nullptr; StorageType map_; }; // Calls a.swap(b). // // This function is used for making MapId "swappable". // Ref: https://en.cppreference.com/w/cpp/named_req/Swappable. template void swap(IdMap& a, IdMap& b) { a.swap(b); } //////////////////////////////////////////////////////////////////////////////// // Inline implementations //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // IdMap::iterator //////////////////////////////////////////////////////////////////////////////// template typename IdMap::reference IdMap::iterator::operator*() const { return reference(K(map_->storage_, storage_iterator_->first), storage_iterator_->second); } template internal::ArrowOperatorProxy::iterator::reference> IdMap::iterator::operator->() const { return internal::ArrowOperatorProxy(**this); } template typename IdMap::iterator& IdMap::iterator::operator++() { ++storage_iterator_; return *this; } template typename IdMap::iterator IdMap::iterator::operator++(int) { iterator ret = *this; ++(*this); return ret; } template IdMap::iterator::iterator(const IdMap* map, typename StorageType::iterator storage_iterator) : map_(map), storage_iterator_(std::move(storage_iterator)) {} //////////////////////////////////////////////////////////////////////////////// // IdMap::const_iterator //////////////////////////////////////////////////////////////////////////////// template IdMap::const_iterator::const_iterator(const iterator& non_const_iterator) : map_(non_const_iterator.map_), storage_iterator_(non_const_iterator.storage_iterator_) {} template typename IdMap::const_iterator::reference IdMap::const_iterator::operator*() const { return reference(K(map_->storage_, storage_iterator_->first), storage_iterator_->second); } template internal::ArrowOperatorProxy::const_iterator::reference> IdMap::const_iterator::operator->() const { return internal::ArrowOperatorProxy(**this); } template typename IdMap::const_iterator& IdMap::const_iterator::operator++() { ++storage_iterator_; return *this; } template typename IdMap::const_iterator IdMap::const_iterator::operator++( int) { const_iterator ret = *this; ++(*this); return ret; } template IdMap::const_iterator::const_iterator( const IdMap* map, typename StorageType::const_iterator storage_iterator) : map_(map), storage_iterator_(std::move(storage_iterator)) {} //////////////////////////////////////////////////////////////////////////////// // IdMap //////////////////////////////////////////////////////////////////////////////// template IdMap::IdMap(const ModelStorage* storage, StorageType values) : storage_(storage), map_(std::move(values)) { if (!map_.empty()) { CHECK(storage_ != nullptr); } } template template IdMap::IdMap(InputIt first, InputIt last) { insert(first, last); } template IdMap::IdMap(std::initializer_list ilist) { insert(ilist); } template typename IdMap::const_iterator IdMap::cbegin() const { return const_iterator(this, map_.cbegin()); } template typename IdMap::const_iterator IdMap::begin() const { return cbegin(); } template typename IdMap::iterator IdMap::begin() { return iterator(this, map_.begin()); } template typename IdMap::const_iterator IdMap::cend() const { return const_iterator(this, map_.cend()); } template typename IdMap::const_iterator IdMap::end() const { return cend(); } template typename IdMap::iterator IdMap::end() { return iterator(this, map_.end()); } template void IdMap::clear() { storage_ = nullptr; map_.clear(); } template std::pair::iterator, bool> IdMap::insert( std::pair k_v) { return emplace(k_v.first, std::move(k_v.second)); } template template void IdMap::insert(const InputIt first, const InputIt last) { for (InputIt it = first; it != last; ++it) { insert(*it); } } template void IdMap::insert(std::initializer_list ilist) { insert(ilist.begin(), ilist.end()); } template std::pair::iterator, bool> IdMap::emplace(const K& k, V v) { CheckOrSetModel(k); auto initial_ret = map_.emplace(k.typed_id(), std::move(v)); return std::make_pair(iterator(this, std::move(initial_ret.first)), initial_ret.second); } template template std::pair::iterator, bool> IdMap::try_emplace( const K& k, Args&&... args) { CheckOrSetModel(k); auto initial_ret = map_.try_emplace(k.typed_id(), std::forward(args)...); return std::make_pair(iterator(this, std::move(initial_ret.first)), initial_ret.second); } template typename IdMap::size_type IdMap::erase(const K& k) { CheckModel(k); const size_type ret = map_.erase(k.typed_id()); if (map_.empty()) { storage_ = nullptr; } return ret; } template void IdMap::erase(const const_iterator pos) { map_.erase(pos.storage_iterator_); if (map_.empty()) { storage_ = nullptr; } } template typename IdMap::iterator IdMap::erase(const const_iterator first, const const_iterator last) { auto ret = map_.erase(first.storage_iterator_, last.storage_iterator_); if (map_.empty()) { storage_ = nullptr; } return iterator(this, std::move(ret)); } template void IdMap::swap(IdMap& other) { using std::swap; swap(storage_, other.storage_); swap(map_, other.map_); } template const V& IdMap::at(const K& k) const { CheckModel(k); return map_.at(k.typed_id()); } template V& IdMap::at(const K& k) { CheckModel(k); return map_.at(k.typed_id()); } template V& IdMap::operator[](const K& k) { CheckOrSetModel(k); return map_[k.typed_id()]; } template typename IdMap::size_type IdMap::count(const K& k) const { CheckModel(k); return map_.count(k.typed_id()); } template bool IdMap::contains(const K& k) const { CheckModel(k); return map_.contains(k.typed_id()); } template typename IdMap::iterator IdMap::find(const K& k) { CheckModel(k); return iterator(this, map_.find(k.typed_id())); } template typename IdMap::const_iterator IdMap::find(const K& k) const { CheckModel(k); return const_iterator(this, map_.find(k.typed_id())); } template std::pair::iterator, typename IdMap::iterator> IdMap::equal_range(const K& k) { const auto it = find(k); if (it == end()) { return {it, it}; } return {it, std::next(it)}; } template std::pair::const_iterator, typename IdMap::const_iterator> IdMap::equal_range(const K& k) const { const auto it = find(k); if (it == end()) { return {it, it}; } return {it, std::next(it)}; } template void IdMap::Add(const IdMap& other) { CheckOrSetModel(other); for (const auto& pair : other.map_) { map_[pair.first] += pair.second; } } template void IdMap::Subtract(const IdMap& other) { CheckOrSetModel(other); for (const auto& pair : other.map_) { map_[pair.first] -= pair.second; } } template std::vector IdMap::Values(const absl::Span keys) const { std::vector result; result.reserve(keys.size()); for (const K key : keys) { result.push_back(at(key)); } return result; } template absl::flat_hash_map IdMap::Values( const absl::flat_hash_set& keys) const { absl::flat_hash_map result; for (const K key : keys) { result[key] = at(key); } return result; } template std::vector IdMap::SortedKeys() const { std::vector result; result.reserve(map_.size()); for (const IdType id : SortedIds()) { result.push_back(K(storage_, id)); } return result; } template std::vector IdMap::SortedValues() const { std::vector result; result.reserve(map_.size()); for (const IdType id : SortedIds()) { result.push_back(map_.at(id)); } return result; } template std::vector IdMap::SortedIds() const { std::vector result; result.reserve(map_.size()); for (const auto& [id, _] : map_) { result.push_back(id); } std::sort(result.begin(), result.end()); return result; } template void IdMap::CheckModel(const K& k) const { CHECK(k.storage() != nullptr) << internal::kKeyHasNullModelStorage; CHECK(storage_ == nullptr || storage_ == k.storage()) << internal::kObjectsFromOtherModelStorage; } template void IdMap::CheckOrSetModel(const K& k) { CHECK(k.storage() != nullptr) << internal::kKeyHasNullModelStorage; if (storage_ == nullptr) { storage_ = k.storage(); } else { CHECK_EQ(storage_, k.storage()) << internal::kObjectsFromOtherModelStorage; } } template void IdMap::CheckOrSetModel(const IdMap& other) { if (storage_ == nullptr) { storage_ = other.storage_; } else if (other.storage_ != nullptr) { CHECK_EQ(storage_, other.storage_) << internal::kObjectsFromOtherModelStorage; } else { // By construction when other is not empty, it has a non null `storage_`. DCHECK(other.empty()); } } } // namespace math_opt } // namespace operations_research #endif // OR_TOOLS_MATH_OPT_CPP_ID_MAP_H_