OR-Tools  9.1
ids_validator.cc
Go to the documentation of this file.
1// Copyright 2010-2021 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <stddef.h>
17
18#include <algorithm>
19#include <cstdint>
20#include <iterator>
21#include <limits>
22#include <string>
23
24#include "absl/container/flat_hash_set.h"
25#include "absl/status/status.h"
26#include "absl/strings/str_cat.h"
27#include "absl/strings/string_view.h"
28#include "absl/types/span.h"
31
32namespace operations_research {
33namespace math_opt {
34
35constexpr double kInf = std::numeric_limits<double>::infinity();
36
37namespace {
38absl::Status CheckSortedIdsSubsetWithIndexOffset(
39 const absl::Span<const int64_t> ids,
40 const absl::Span<const int64_t> universe, const int64_t offset) {
41 int id_index = 0;
42 int universe_index = 0;
43 // NOTE(user): in the common case where ids and/or universe is consecutive,
44 // we can avoid iterating though the list and do interval based checks.
45 while (id_index < ids.size() && universe_index < universe.size()) {
46 if (universe[universe_index] < ids[id_index]) {
47 ++universe_index;
48 } else if (universe[universe_index] == ids[id_index]) {
49 ++id_index;
50 } else {
51 break;
52 }
53 }
54 if (id_index < ids.size()) {
55 return absl::InvalidArgumentError(
56 absl::StrCat("Bad id: ", ids[id_index],
57 " (at index: ", id_index + offset, ") found"));
58 }
59 return absl::OkStatus();
60}
61
62// Given a sorted, strictly increasing set of ids, provides `contains()` to
63// check if another id is in the set in O(1) time.
64//
65// Implementation note: when ids are consecutive, they are stored as a single
66// interval [lb, ub), otherwise they are stored as a hash table of integers.
67class FastIdCheck {
68 public:
69 // ids must be sorted with unique strictly increasing entries.
70 explicit FastIdCheck(const absl::Span<const int64_t> ids) {
71 if (ids.empty()) {
72 interval_mode_ = true;
73 } else if (ids.size() == ids.back() + 1 - ids.front()) {
74 interval_mode_ = true;
75 interval_lb_ = ids.front();
76 interval_ub_ = ids.back() + 1;
77 } else {
78 ids_ = absl::flat_hash_set<int64_t>(ids.begin(), ids.end());
79 }
80 }
81 bool contains(int64_t id) const {
82 if (interval_mode_) {
83 return id >= interval_lb_ && id < interval_ub_;
84 } else {
85 return ids_.contains(id);
86 }
87 }
88
89 private:
90 bool interval_mode_ = false;
91 int64_t interval_lb_ = 0;
92 int64_t interval_ub_ = 0;
93 absl::flat_hash_set<int64_t> ids_;
94};
95
96// Checks that the elements of ids and bad_list have no overlap.
97//
98// Assumed: ids and bad_list are sorted in increasing order, repeats allowed.
99absl::Status CheckSortedIdsNotBad(const absl::Span<const int64_t> ids,
100 const absl::Span<const int64_t> bad_list) {
101 int id_index = 0;
102 int bad_index = 0;
103 while (id_index < ids.size() && bad_index < bad_list.size()) {
104 if (bad_list[bad_index] < ids[id_index]) {
105 ++bad_index;
106 } else if (bad_list[bad_index] > ids[id_index]) {
107 ++id_index;
108 } else {
109 return absl::InvalidArgumentError(absl::StrCat(
110 "Bad id: ", ids[id_index], " (at index: ", id_index, ") found."));
111 }
112 }
113 return absl::OkStatus();
114}
115} // namespace
116
118 absl::Span<const int64_t> ids) {
119 int64_t previous{-1};
120 for (int i = 0; i < ids.size(); ++i) {
121 if (ids[i] <= previous) {
122 std::string error_base = absl::StrCat(
123 "Expected ids to be nonnegative and strictly increasing, but at "
124 "index ",
125 i, ", found id: ", ids[i]);
126 if (i == 0) {
127 return absl::InvalidArgumentError(
128 absl::StrCat(error_base, " (a negative id)"));
129 } else {
130 return absl::InvalidArgumentError(absl::StrCat(
131 error_base, " and at index ", i - 1, " found id: ", ids[i - 1]));
132 }
133 }
134 previous = ids[i];
135 }
136 return absl::OkStatus();
137}
138
139absl::Status CheckSortedIdsSubset(const absl::Span<const int64_t> ids,
140 const absl::Span<const int64_t> universe) {
141 RETURN_IF_ERROR(CheckSortedIdsSubsetWithIndexOffset(ids, universe, 0));
142 return absl::OkStatus();
143}
144
145absl::Status CheckUnsortedIdsSubset(const absl::Span<const int64_t> ids,
146 const absl::Span<const int64_t> universe) {
147 if (ids.empty()) {
148 return absl::OkStatus();
149 }
150 const FastIdCheck id_check(universe);
151 for (int i = 0; i < ids.size(); ++i) {
152 if (!id_check.contains(ids[i])) {
153 return absl::InvalidArgumentError(
154 absl::StrCat("Bad id: ", ids[i], " (at index: ", i, ") not found."));
155 }
156 }
157 return absl::OkStatus();
158}
159
160absl::Status IdUpdateValidator::IsValid() const {
161 for (int i = 0; i < deleted_ids_.size(); ++i) {
162 const int64_t deleted_id = deleted_ids_[i];
163 if (!old_ids_.HasId(deleted_id)) {
164 return absl::InvalidArgumentError(
165 absl::StrCat("Tried to delete id: ", deleted_id, " (at index: ", i,
166 ") but it was not present."));
167 }
168 }
169 if (old_ids_.Empty() || new_ids_.empty()) {
170 return absl::OkStatus();
171 }
172 if (old_ids_.LargestId() >= new_ids_.front()) {
173 return absl::InvalidArgumentError(absl::StrCat(
174 "All old ids should be less than all new ids, but final old id was: ",
175 old_ids_.LargestId(), " and first new id was: ", new_ids_.front()));
176 }
177 return absl::OkStatus();
178}
179
181 const absl::Span<const int64_t> ids) const {
182 RETURN_IF_ERROR(CheckSortedIdsNotBad(ids, deleted_ids_)) << " was deleted";
183 for (int i = 0; i < ids.size(); ++i) {
184 if (!old_ids_.HasId(ids[i])) {
185 return absl::InvalidArgumentError(
186 absl::StrCat("Bad id: ", ids[i], " (at index: ", i, ") not found."));
187 }
188 }
189 return absl::OkStatus();
190}
191
193 const absl::Span<const int64_t> ids) const {
194 // Implementation:
195 // * Partition ids into "old" and "new"
196 // * Check that the old ids are in old_ids_ but not deleted_ids_.
197 // * Check that the new ids are in new_ids_.
198 size_t split_point = ids.size();
199 if (!new_ids_.empty()) {
200 split_point = std::distance(
201 ids.begin(), std::lower_bound(ids.begin(), ids.end(), new_ids_[0]));
202 }
204 CheckSortedIdsSubsetOfNotDeleted(ids.subspan(0, split_point)));
205 RETURN_IF_ERROR(CheckSortedIdsSubsetWithIndexOffset(ids.subspan(split_point),
206 new_ids_, split_point));
207 return absl::OkStatus();
208}
209
210// Checks that ids is a subset of FINAL = old_ids_ - deleted_ids_ + new_ids_.
211//
212// If ids is sorted, prefer CheckSortedIdsSubsetOfFinal.
214 const absl::Span<const int64_t> ids) const {
215 if (ids.empty()) {
216 return absl::OkStatus();
217 }
218 const FastIdCheck deleted_fast(deleted_ids_);
219 const FastIdCheck new_fast(new_ids_);
220 for (int i = 0; i < ids.size(); ++i) {
221 const int64_t id = ids[i];
222 if (id <= old_ids_.LargestId()) {
223 if (!old_ids_.HasId(id)) {
224 return absl::InvalidArgumentError(
225 absl::StrCat("Bad id: ", id, " (at index: ", i, ") not found."));
226 } else if (deleted_fast.contains(id)) {
227 return absl::InvalidArgumentError(
228 absl::StrCat("Bad id: ", id, " (at index: ", i, ") was deleted."));
229 }
230 } else if (!new_fast.contains(id)) {
231 return absl::InvalidArgumentError(
232 absl::StrCat("Bad id: ", id, " (at index: ", i, ") not found."));
233 }
234 }
235 return absl::OkStatus();
236}
237
238absl::Status CheckIdsSubset(absl::Span<const int64_t> ids,
239 const IdNameBiMap& universe,
240 absl::string_view ids_description,
241 absl::string_view universe_description) {
242 for (int i = 0; i < ids.size(); ++i) {
243 const int64_t id = ids[i];
244 if (!universe.HasId(id)) {
245 return absl::InvalidArgumentError(
246 absl::StrCat("Id: ", id, " (at index: ", i, ") in ", ids_description,
247 " is missing from ", universe_description, "."));
248 }
249 }
250 return absl::OkStatus();
251}
252
253absl::Status CheckIdsIdentical(absl::Span<const int64_t> first_ids,
254 const IdNameBiMap& second_ids,
255 absl::string_view first_description,
256 absl::string_view second_description) {
257 if (first_ids.size() != second_ids.Size()) {
258 return absl::InvalidArgumentError(absl::StrCat(
259 first_description, " has size ", first_ids.size(), ", but ",
260 second_description, " has size ", second_ids.Size(), "."));
261 }
262 RETURN_IF_ERROR(CheckIdsSubset(first_ids, second_ids, first_description,
263 second_description));
264 return absl::OkStatus();
265}
266
267} // namespace math_opt
268} // namespace operations_research
absl::Status CheckSortedIdsSubsetOfFinal(const absl::Span< const int64_t > ids) const
absl::Status CheckIdsSubsetOfFinal(const absl::Span< const int64_t > ids) const
absl::Status CheckSortedIdsSubsetOfNotDeleted(const absl::Span< const int64_t > ids) const
double lower_bound
absl::Status CheckUnsortedIdsSubset(const absl::Span< const int64_t > ids, const absl::Span< const int64_t > universe)
absl::Status CheckIdsNonnegativeAndStrictlyIncreasing(absl::Span< const int64_t > ids)
absl::Status CheckSortedIdsSubset(const absl::Span< const int64_t > ids, const absl::Span< const int64_t > universe)
absl::Status CheckIdsSubset(absl::Span< const int64_t > ids, const IdNameBiMap &universe, absl::string_view ids_description, absl::string_view universe_description)
absl::Status CheckIdsIdentical(absl::Span< const int64_t > first_ids, const IdNameBiMap &second_ids, absl::string_view first_description, absl::string_view second_description)
Collection of objects used to extend the Constraint Solver library.
double distance
#define RETURN_IF_ERROR(expr)
Definition: status_macros.h:29