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 
32 namespace operations_research {
33 namespace math_opt {
34 
35 constexpr double kInf = std::numeric_limits<double>::infinity();
36 
37 namespace {
38 absl::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.
67 class 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.
99 absl::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 
139 absl::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 
145 absl::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 
160 absl::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 
238 absl::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 
253 absl::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 CheckSortedIdsSubset(const absl::Span< const int64_t > ids, const absl::Span< const int64_t > universe)
absl::Status CheckIdsIdentical(absl::Span< const int64_t > first_ids, const IdNameBiMap &second_ids, absl::string_view first_description, absl::string_view second_description)
double lower_bound
absl::Status CheckSortedIdsSubsetOfNotDeleted(const absl::Span< const int64_t > ids) const
absl::Status CheckUnsortedIdsSubset(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 CheckSortedIdsSubsetOfFinal(const absl::Span< const int64_t > ids) const
Collection of objects used to extend the Constraint Solver library.
absl::Status CheckIdsNonnegativeAndStrictlyIncreasing(absl::Span< const int64_t > ids)
absl::Status CheckIdsSubsetOfFinal(const absl::Span< const int64_t > ids) const
#define RETURN_IF_ERROR(expr)
Definition: status_macros.h:29
double distance