Files
ortools-clone/ortools/math_opt/cpp/id_set.h
Corentin Le Molgat 156190eea8 Update math_opt
2022-03-02 22:10:54 +01:00

375 lines
11 KiB
C++

// 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.
#ifndef OR_TOOLS_MATH_OPT_CPP_ID_SET_H_
#define OR_TOOLS_MATH_OPT_CPP_ID_SET_H_
#include <initializer_list>
#include <iterator>
#include <utility>
#include "absl/container/flat_hash_set.h"
#include "ortools/base/logging.h"
#include "ortools/math_opt/core/arrow_operator_proxy.h"
#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_set<K> 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_set 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 set (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 IdMap for the equivalent class for maps.
template <typename K>
class IdSet {
public:
using IdType = typename K::IdType;
using StorageType = absl::flat_hash_set<IdType>;
using key_type = K;
using value_type = key_type;
using size_type = typename StorageType::size_type;
using difference_type = typename StorageType::difference_type;
using reference = K;
using const_reference = const K;
using pointer = void;
using const_pointer = void;
class const_iterator {
public:
using value_type = IdSet::value_type;
using reference = IdSet::const_reference;
using pointer = IdSet::const_pointer;
using difference_type = IdSet::difference_type;
using iterator_category = std::forward_iterator_tag;
const_iterator() = default;
inline const_reference operator*() const;
inline internal::ArrowOperatorProxy<reference> 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 IdSet;
inline const_iterator(
const IdSet* set,
typename StorageType::const_iterator storage_iterator);
const IdSet* set_ = nullptr;
typename StorageType::const_iterator storage_iterator_;
};
// All iterators on sets are const; but STL still defines the `iterator`
// type. The `flat_hash_set` defines two classes the but the policy makes both
// constant. Here to simplify the code we use the same type.
using iterator = const_iterator;
IdSet() = default;
template <typename InputIt>
inline IdSet(InputIt first, InputIt last);
inline IdSet(std::initializer_list<value_type> ilist);
// Typically for internal use only.
inline IdSet(const ModelStorage* storage, StorageType values);
inline const_iterator cbegin() const;
inline const_iterator begin() const;
inline const_iterator cend() const;
inline const_iterator end() const;
bool empty() const { return set_.empty(); }
size_type size() const { return set_.size(); }
inline void clear();
void reserve(size_type count) { set_.reserve(count); }
inline std::pair<const_iterator, bool> insert(const K& k);
template <typename InputIt>
inline void insert(InputIt first, InputIt last);
inline void insert(std::initializer_list<value_type> ilist);
inline std::pair<const_iterator, bool> emplace(const K& k);
// Returns the number of elements erased (zero or one).
inline size_type erase(const K& k);
// In STL erase(const_iterator) returns an iterator. But flat_hash_set instead
// has void return types. So here we also use void.
inline void erase(const_iterator pos);
inline const_iterator erase(const_iterator first, const_iterator last);
inline void swap(IdSet& other);
inline size_type count(const K& k) const;
inline bool contains(const K& k) const;
inline const_iterator find(const K& k) const;
inline std::pair<const_iterator, const_iterator> equal_range(
const K& k) const;
const StorageType& raw_set() const { return set_; }
const ModelStorage* storage() const { return storage_; }
friend bool operator==(const IdSet& lhs, const IdSet& rhs) {
return lhs.storage_ == rhs.storage_ && lhs.set_ == rhs.set_;
}
friend bool operator!=(const IdSet& lhs, const IdSet& rhs) {
return !(lhs == rhs);
}
private:
// CHECKs that storage_ and k.storage() matches when this set 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 set is empty (i.e. its storage_ is
// null). Else CHECK that it has the same storage. It also CHECK that
// k.storage() is not null.
inline void CheckOrSetModel(const K& k);
// Invariant: storage == nullptr if and only if set_.empty().
const ModelStorage* storage_ = nullptr;
StorageType set_;
};
// Calls a.swap(b).
//
// This function is used for making IdSet "swappable".
// Ref: https://en.cppreference.com/w/cpp/named_req/Swappable.
template <typename K>
void swap(IdSet<K>& a, IdSet<K>& b) {
a.swap(b);
}
////////////////////////////////////////////////////////////////////////////////
// Inline implementations
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// IdSet::const_iterator
////////////////////////////////////////////////////////////////////////////////
template <typename K>
typename IdSet<K>::const_iterator::reference
IdSet<K>::const_iterator::operator*() const {
return K(set_->storage_, *storage_iterator_);
}
template <typename K>
internal::ArrowOperatorProxy<typename IdSet<K>::const_iterator::reference>
IdSet<K>::const_iterator::operator->() const {
return internal::ArrowOperatorProxy<reference>(**this);
}
template <typename K>
typename IdSet<K>::const_iterator& IdSet<K>::const_iterator::operator++() {
++storage_iterator_;
return *this;
}
template <typename K>
typename IdSet<K>::const_iterator IdSet<K>::const_iterator::operator++(int) {
const_iterator ret = *this;
++(*this);
return ret;
}
template <typename K>
IdSet<K>::const_iterator::const_iterator(
const IdSet* set, typename StorageType::const_iterator storage_iterator)
: set_(set), storage_iterator_(std::move(storage_iterator)) {}
////////////////////////////////////////////////////////////////////////////////
// IdSet
////////////////////////////////////////////////////////////////////////////////
template <typename K>
IdSet<K>::IdSet(const ModelStorage* storage, StorageType values)
: storage_(storage), set_(std::move(values)) {
if (!set_.empty()) {
CHECK(storage_ != nullptr);
}
}
template <typename K>
template <typename InputIt>
IdSet<K>::IdSet(InputIt first, InputIt last) {
insert(first, last);
}
template <typename K>
IdSet<K>::IdSet(std::initializer_list<value_type> ilist) {
insert(ilist);
}
template <typename K>
typename IdSet<K>::const_iterator IdSet<K>::cbegin() const {
return const_iterator(this, set_.cbegin());
}
template <typename K>
typename IdSet<K>::const_iterator IdSet<K>::begin() const {
return cbegin();
}
template <typename K>
typename IdSet<K>::const_iterator IdSet<K>::cend() const {
return const_iterator(this, set_.cend());
}
template <typename K>
typename IdSet<K>::const_iterator IdSet<K>::end() const {
return cend();
}
template <typename K>
void IdSet<K>::clear() {
storage_ = nullptr;
set_.clear();
}
template <typename K>
std::pair<typename IdSet<K>::const_iterator, bool> IdSet<K>::insert(
const K& k) {
return emplace(k);
}
template <typename K>
template <typename InputIt>
void IdSet<K>::insert(const InputIt first, const InputIt last) {
for (InputIt it = first; it != last; ++it) {
insert(*it);
}
}
template <typename K>
void IdSet<K>::insert(std::initializer_list<value_type> ilist) {
insert(ilist.begin(), ilist.end());
}
template <typename K>
std::pair<typename IdSet<K>::const_iterator, bool> IdSet<K>::emplace(
const K& k) {
CheckOrSetModel(k);
auto initial_ret = set_.emplace(k.typed_id());
return std::make_pair(const_iterator(this, std::move(initial_ret.first)),
initial_ret.second);
}
template <typename K>
typename IdSet<K>::size_type IdSet<K>::erase(const K& k) {
CheckModel(k);
const size_type ret = set_.erase(k.typed_id());
if (set_.empty()) {
storage_ = nullptr;
}
return ret;
}
template <typename K>
void IdSet<K>::erase(const const_iterator pos) {
set_.erase(pos.storage_iterator_);
if (set_.empty()) {
storage_ = nullptr;
}
}
template <typename K>
typename IdSet<K>::const_iterator IdSet<K>::erase(const const_iterator first,
const const_iterator last) {
auto ret = set_.erase(first.storage_iterator_, last.storage_iterator_);
if (set_.empty()) {
storage_ = nullptr;
}
return const_iterator(this, std::move(ret));
}
template <typename K>
void IdSet<K>::swap(IdSet& other) {
using std::swap;
swap(storage_, other.storage_);
swap(set_, other.set_);
}
template <typename K>
typename IdSet<K>::size_type IdSet<K>::count(const K& k) const {
CheckModel(k);
return set_.count(k.typed_id());
}
template <typename K>
bool IdSet<K>::contains(const K& k) const {
CheckModel(k);
return set_.contains(k.typed_id());
}
template <typename K>
typename IdSet<K>::const_iterator IdSet<K>::find(const K& k) const {
CheckModel(k);
return const_iterator(this, set_.find(k.typed_id()));
}
template <typename K>
std::pair<typename IdSet<K>::const_iterator, typename IdSet<K>::const_iterator>
IdSet<K>::equal_range(const K& k) const {
const auto it = find(k);
if (it == end()) {
return {it, it};
}
return {it, std::next(it)};
}
template <typename K>
void IdSet<K>::CheckModel(const K& k) const {
CHECK(k.storage() != nullptr) << internal::kKeyHasNullModelStorage;
CHECK(storage_ == nullptr || storage_ == k.storage())
<< internal::kObjectsFromOtherModelStorage;
}
template <typename K>
void IdSet<K>::CheckOrSetModel(const K& k) {
CHECK(k.storage() != nullptr) << internal::kKeyHasNullModelStorage;
if (storage_ == nullptr) {
storage_ = k.storage();
} else {
CHECK_EQ(storage_, k.storage()) << internal::kObjectsFromOtherModelStorage;
}
}
} // namespace math_opt
} // namespace operations_research
#endif // OR_TOOLS_MATH_OPT_CPP_ID_SET_H_