OR-Tools  9.6
sorted_interval_list.cc
Go to the documentation of this file.
1 // Copyright 2010-2022 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 <algorithm>
17 #include <cmath>
18 #include <limits>
19 #include <map>
20 #include <ostream>
21 #include <string>
22 #include <utility>
23 #include <vector>
24 
25 #include "absl/container/inlined_vector.h"
26 #include "absl/strings/str_format.h"
27 #include "absl/types/span.h"
29 #include "ortools/base/logging.h"
31 
32 namespace operations_research {
33 
34 std::string ClosedInterval::DebugString() const {
35  if (start == end) return absl::StrFormat("[%d]", start);
36  return absl::StrFormat("[%d,%d]", start, end);
37 }
38 
40  absl::Span<const ClosedInterval> intervals) {
41  for (int i = 1; i < intervals.size(); ++i) {
42  if (intervals[i - 1].start > intervals[i - 1].end) return false;
43  // First test make sure that intervals[i - 1].end + 1 will not overflow.
44  if (intervals[i - 1].end >= intervals[i].start ||
45  intervals[i - 1].end + 1 >= intervals[i].start) {
46  return false;
47  }
48  }
49  return intervals.empty() ? true
50  : intervals.back().start <= intervals.back().end;
51 }
52 
53 namespace {
54 
55 template <class Intervals>
56 std::string IntervalsAsString(const Intervals& intervals) {
57  std::string result;
58  for (ClosedInterval interval : intervals) {
59  result += interval.DebugString();
60  }
61  if (result.empty()) result = "[]";
62  return result;
63 }
64 
65 // Transforms a sorted list of intervals in a sorted DISJOINT list for which
66 // IntervalsAreSortedAndNonAdjacent() would return true.
67 void UnionOfSortedIntervals(absl::InlinedVector<ClosedInterval, 1>* intervals) {
68  DCHECK(std::is_sorted(intervals->begin(), intervals->end()));
69  const int size = intervals->size();
70  if (size == 0) return;
71 
72  int new_size = 1;
73  for (int i = 1; i < size; ++i) {
74  const ClosedInterval& current = (*intervals)[i];
75  const int64_t end = (*intervals)[new_size - 1].end;
77  current.start <= end + 1) {
78  (*intervals)[new_size - 1].end = std::max(current.end, end);
79  continue;
80  }
81  (*intervals)[new_size++] = current;
82  }
83  intervals->resize(new_size);
84 
85  // This is important for InlinedVector in the case the result is a single
86  // intervals.
87  intervals->shrink_to_fit();
88  DCHECK(IntervalsAreSortedAndNonAdjacent(*intervals));
89 }
90 
91 } // namespace
92 
93 // TODO(user): Use MathUtil::CeilOfRatio / FloorOfRatio instead.
94 int64_t CeilRatio(int64_t value, int64_t positive_coeff) {
95  DCHECK_GT(positive_coeff, 0);
96  const int64_t result = value / positive_coeff;
97  const int64_t adjust = static_cast<int64_t>(result * positive_coeff < value);
98  return result + adjust;
99 }
100 
101 int64_t FloorRatio(int64_t value, int64_t positive_coeff) {
102  DCHECK_GT(positive_coeff, 0);
103  const int64_t result = value / positive_coeff;
104  const int64_t adjust = static_cast<int64_t>(result * positive_coeff > value);
105  return result - adjust;
106 }
107 
108 std::ostream& operator<<(std::ostream& out, const ClosedInterval& interval) {
109  return out << interval.DebugString();
110 }
111 
112 std::ostream& operator<<(std::ostream& out,
113  const std::vector<ClosedInterval>& intervals) {
114  return out << IntervalsAsString(intervals);
115 }
116 
117 std::ostream& operator<<(std::ostream& out, const Domain& domain) {
118  return out << IntervalsAsString(domain);
119 }
120 
121 Domain::Domain(int64_t value) : intervals_({{value, value}}) {}
122 
123 // HACK(user): We spare significant time if we use an initializer here, because
124 // InlineVector<1> is able to recognize the fast path or "exactly one element".
125 // I was unable to obtain the same performance with any other recipe, I always
126 // had at least 1 more cycle. See BM_SingleIntervalDomainConstructor.
127 // Since the constructor takes very few cycles (most likely less than 10),
128 // that's quite significant.
129 namespace {
130 inline ClosedInterval UncheckedClosedInterval(int64_t s, int64_t e) {
131  ClosedInterval i;
132  i.start = s;
133  i.end = e;
134  return i;
135 }
136 } // namespace
137 
138 Domain::Domain(int64_t left, int64_t right)
139  : intervals_({UncheckedClosedInterval(left, right)}) {
140  if (left > right) intervals_.clear();
141 }
142 
144 
145 Domain Domain::FromValues(std::vector<int64_t> values) {
146  std::sort(values.begin(), values.end());
147  Domain result;
148  for (const int64_t v : values) {
149  if (result.intervals_.empty() || v > result.intervals_.back().end + 1) {
150  result.intervals_.push_back({v, v});
151  } else {
152  result.intervals_.back().end = v;
153  }
154  }
155  return result;
156 }
157 
158 Domain Domain::FromIntervals(absl::Span<const ClosedInterval> intervals) {
159  Domain result;
160  result.intervals_.assign(intervals.begin(), intervals.end());
161  std::sort(result.intervals_.begin(), result.intervals_.end());
162  UnionOfSortedIntervals(&result.intervals_);
163  return result;
164 }
165 
167  absl::Span<const int64_t> flat_intervals) {
168  DCHECK(flat_intervals.size() % 2 == 0) << flat_intervals.size();
169  Domain result;
170  result.intervals_.reserve(flat_intervals.size() / 2);
171  for (int i = 0; i < flat_intervals.size(); i += 2) {
172  result.intervals_.push_back({flat_intervals[i], flat_intervals[i + 1]});
173  }
174  std::sort(result.intervals_.begin(), result.intervals_.end());
175  UnionOfSortedIntervals(&result.intervals_);
176  return result;
177 }
178 
179 Domain Domain::FromFlatIntervals(const std::vector<int64_t>& flat_intervals) {
180  return FromFlatSpanOfIntervals(absl::MakeSpan(flat_intervals));
181 }
182 
184  const std::vector<std::vector<int64_t>>& intervals) {
185  Domain result;
186  for (const std::vector<int64_t>& interval : intervals) {
187  if (interval.size() == 1) {
188  result.intervals_.push_back({interval[0], interval[0]});
189  } else {
190  DCHECK_EQ(interval.size(), 2);
191  result.intervals_.push_back({interval[0], interval[1]});
192  }
193  }
194  std::sort(result.intervals_.begin(), result.intervals_.end());
195  UnionOfSortedIntervals(&result.intervals_);
196  return result;
197 }
198 
199 bool Domain::IsEmpty() const { return intervals_.empty(); }
200 
201 bool Domain::IsFixed() const { return Min() == Max(); }
202 
203 int64_t Domain::Size() const {
204  int64_t size = 0;
205  for (const ClosedInterval interval : intervals_) {
207  size, operations_research::CapSub(interval.end, interval.start));
208  }
209  // Because the intervals are closed on both side above, with miss 1 per
210  // interval.
211  size = operations_research::CapAdd(size, intervals_.size());
212  return size;
213 }
214 
215 int64_t Domain::Min() const {
216  DCHECK(!IsEmpty());
217  return intervals_.front().start;
218 }
219 
220 int64_t Domain::Max() const {
221  DCHECK(!IsEmpty());
222  return intervals_.back().end;
223 }
224 
225 int64_t Domain::SmallestValue() const {
226  DCHECK(!IsEmpty());
227  int64_t result = Min();
228  for (const ClosedInterval interval : intervals_) {
229  if (interval.start <= 0 && interval.end >= 0) return 0;
230  for (const int64_t b : {interval.start, interval.end}) {
231  if (b > 0 && b <= std::abs(result)) result = b;
232  if (b < 0 && -b < std::abs(result)) result = b;
233  }
234  }
235  return result;
236 }
237 
238 int64_t Domain::ValueAtOrBefore(int64_t input) const {
239  // Because we only compare by start and there is no duplicate starts, this
240  // should be the next interval after the one that has a chance to contains
241  // value.
242  auto it = std::upper_bound(intervals_.begin(), intervals_.end(),
244  if (it == intervals_.begin()) return input;
245  --it;
246  return input <= it->end ? input : it->end;
247 }
248 
249 int64_t Domain::ValueAtOrAfter(int64_t input) const {
250  // Because we only compare by start and there is no duplicate starts, this
251  // should be the next interval after the one that has a chance to contains
252  // value.
253  auto it = std::upper_bound(intervals_.begin(), intervals_.end(),
255  if (it == intervals_.end()) return input;
256  const int64_t candidate = it->start;
257  if (it == intervals_.begin()) return candidate;
258  --it;
259  return input <= it->end ? input : candidate;
260 }
261 
262 int64_t Domain::FixedValue() const {
263  DCHECK(IsFixed());
264  return intervals_.front().start;
265 }
266 
267 bool Domain::Contains(int64_t value) const {
268  // Because we only compare by start and there is no duplicate starts, this
269  // should be the next interval after the one that has a chance to contains
270  // value.
271  auto it = std::upper_bound(intervals_.begin(), intervals_.end(),
273  if (it == intervals_.begin()) return false;
274  --it;
275  return value <= it->end;
276 }
277 
278 bool Domain::IsIncludedIn(const Domain& domain) const {
279  int i = 0;
280  const auto& others = domain.intervals_;
281  for (const ClosedInterval interval : intervals_) {
282  // Find the unique interval in others that contains interval if any.
283  for (; i < others.size() && interval.end > others[i].end; ++i) {
284  }
285  if (i == others.size()) return false;
286  if (interval.start < others[i].start) return false;
287  }
288  return true;
289 }
290 
292  Domain result;
293  int64_t next_start = kint64min;
294  result.intervals_.reserve(intervals_.size() + 1);
295  for (const ClosedInterval& interval : intervals_) {
296  if (interval.start != kint64min) {
297  result.intervals_.push_back({next_start, interval.start - 1});
298  }
299  if (interval.end == kint64max) return result;
300  next_start = interval.end + 1;
301  }
302  result.intervals_.push_back({next_start, kint64max});
303  DCHECK(IntervalsAreSortedAndNonAdjacent(result.intervals_));
304  return result;
305 }
306 
308  Domain result = *this;
309  result.NegateInPlace();
310  return result;
311 }
312 
313 void Domain::NegateInPlace() {
314  if (intervals_.empty()) return;
315  std::reverse(intervals_.begin(), intervals_.end());
316  if (intervals_.back().end == kint64min) {
317  // corner-case
318  intervals_.pop_back();
319  }
320  for (ClosedInterval& ref : intervals_) {
321  std::swap(ref.start, ref.end);
322  ref.start = ref.start == kint64min ? kint64max : -ref.start;
323  ref.end = ref.end == kint64min ? kint64max : -ref.end;
324  }
325  DCHECK(IntervalsAreSortedAndNonAdjacent(intervals_));
326 }
327 
328 Domain Domain::IntersectionWith(const Domain& domain) const {
329  Domain result;
330  const auto& a = intervals_;
331  const auto& b = domain.intervals_;
332  for (int i = 0, j = 0; i < a.size() && j < b.size();) {
333  if (a[i].start <= b[j].start) {
334  if (a[i].end < b[j].start) {
335  // Empty intersection. We advance past the first interval.
336  ++i;
337  } else { // a[i].end >= b[j].start
338  // Non-empty intersection: push back the intersection of these two, and
339  // advance past the first interval to finish.
340  if (a[i].end <= b[j].end) {
341  result.intervals_.push_back({b[j].start, a[i].end});
342  ++i;
343  } else { // a[i].end > b[j].end.
344  result.intervals_.push_back({b[j].start, b[j].end});
345  ++j;
346  }
347  }
348  } else { // a[i].start > b[i].start.
349  // We do the exact same thing as above, but swapping a and b.
350  if (b[j].end < a[i].start) {
351  ++j;
352  } else { // b[j].end >= a[i].start
353  if (b[j].end <= a[i].end) {
354  result.intervals_.push_back({a[i].start, b[j].end});
355  ++j;
356  } else { // a[i].end > b[j].end.
357  result.intervals_.push_back({a[i].start, a[i].end});
358  ++i;
359  }
360  }
361  }
362  }
363  DCHECK(IntervalsAreSortedAndNonAdjacent(result.intervals_));
364  return result;
365 }
366 
367 Domain Domain::UnionWith(const Domain& domain) const {
368  Domain result;
369  const auto& a = intervals_;
370  const auto& b = domain.intervals_;
371  result.intervals_.resize(a.size() + b.size());
372  std::merge(a.begin(), a.end(), b.begin(), b.end(), result.intervals_.begin());
373  UnionOfSortedIntervals(&result.intervals_);
374  return result;
375 }
376 
377 // TODO(user): Use a better algorithm.
378 Domain Domain::AdditionWith(const Domain& domain) const {
379  Domain result;
380 
381  const auto& a = intervals_;
382  const auto& b = domain.intervals_;
383  result.intervals_.reserve(a.size() * b.size());
384  for (const ClosedInterval& i : a) {
385  for (const ClosedInterval& j : b) {
386  result.intervals_.push_back(
387  {CapAdd(i.start, j.start), CapAdd(i.end, j.end)});
388  }
389  }
390 
391  // The sort is not needed if one of the list is of size 1.
392  if (a.size() > 1 && b.size() > 1) {
393  std::sort(result.intervals_.begin(), result.intervals_.end());
394  }
395  UnionOfSortedIntervals(&result.intervals_);
396  return result;
397 }
398 
400  if (NumIntervals() > kDomainComplexityLimit) {
401  return Domain(Min(), Max());
402  } else {
403  return *this;
404  }
405 }
406 
407 Domain Domain::MultiplicationBy(int64_t coeff, bool* exact) const {
408  if (exact != nullptr) *exact = true;
409  if (intervals_.empty()) return {};
410  if (coeff == 0) return Domain(0);
411 
412  const int64_t abs_coeff = std::abs(coeff);
413  const int64_t size_if_non_trivial = abs_coeff > 1 ? Size() : 0;
414  if (size_if_non_trivial > kDomainComplexityLimit) {
415  if (exact != nullptr) *exact = false;
416  return ContinuousMultiplicationBy(coeff);
417  }
418 
419  Domain result;
420  if (abs_coeff > 1) {
421  const int64_t max_value = kint64max / abs_coeff;
422  const int64_t min_value = kint64min / abs_coeff;
423  result.intervals_.reserve(size_if_non_trivial);
424  for (const ClosedInterval& i : intervals_) {
425  for (int64_t v = i.start;; ++v) {
426  // We ignore anything that overflow.
427  if (v >= min_value && v <= max_value) {
428  // Because abs_coeff > 1, all new values are disjoint.
429  const int64_t new_value = v * abs_coeff;
430  result.intervals_.push_back({new_value, new_value});
431  }
432 
433  // This is to avoid doing ++v when v is kint64max!
434  if (v == i.end) break;
435  }
436  }
437  } else {
438  result = *this;
439  }
440  if (coeff < 0) result.NegateInPlace();
441  return result;
442 }
443 
445  Domain result = *this;
446  const int64_t abs_coeff = std::abs(coeff);
447  for (ClosedInterval& i : result.intervals_) {
448  i.start = CapProd(i.start, abs_coeff);
449  i.end = CapProd(i.end, abs_coeff);
450  }
451  UnionOfSortedIntervals(&result.intervals_);
452  if (coeff < 0) result.NegateInPlace();
453  return result;
454 }
455 
457  Domain result;
458  for (const ClosedInterval& i : this->intervals_) {
459  for (const ClosedInterval& j : domain.intervals_) {
460  ClosedInterval new_interval;
461  const int64_t a = CapProd(i.start, j.start);
462  const int64_t b = CapProd(i.end, j.end);
463  const int64_t c = CapProd(i.start, j.end);
464  const int64_t d = CapProd(i.end, j.start);
465  new_interval.start = std::min({a, b, c, d});
466  new_interval.end = std::max({a, b, c, d});
467  result.intervals_.push_back(new_interval);
468  }
469  }
470  std::sort(result.intervals_.begin(), result.intervals_.end());
471  UnionOfSortedIntervals(&result.intervals_);
472  return result;
473 }
474 
475 Domain Domain::DivisionBy(int64_t coeff) const {
476  CHECK_NE(coeff, 0);
477  Domain result = *this;
478  const int64_t abs_coeff = std::abs(coeff);
479  for (ClosedInterval& i : result.intervals_) {
480  i.start = i.start / abs_coeff;
481  i.end = i.end / abs_coeff;
482  }
483  UnionOfSortedIntervals(&result.intervals_);
484  if (coeff < 0) result.NegateInPlace();
485  return result;
486 }
487 
488 Domain Domain::InverseMultiplicationBy(const int64_t coeff) const {
489  if (coeff == 0) {
490  return Contains(0) ? Domain::AllValues() : Domain();
491  }
492  Domain result = *this;
493  int new_size = 0;
494  const int64_t abs_coeff = std::abs(coeff);
495  for (const ClosedInterval& i : result.intervals_) {
496  const int64_t start = CeilRatio(i.start, abs_coeff);
497  const int64_t end = FloorRatio(i.end, abs_coeff);
498  if (start > end) continue;
499  if (new_size > 0 && start == result.intervals_[new_size - 1].end + 1) {
500  result.intervals_[new_size - 1].end = end;
501  } else {
502  result.intervals_[new_size++] = {start, end};
503  }
504  }
505  result.intervals_.resize(new_size);
506  result.intervals_.shrink_to_fit();
507  DCHECK(IntervalsAreSortedAndNonAdjacent(result.intervals_));
508  if (coeff < 0) result.NegateInPlace();
509  return result;
510 }
511 
512 namespace {
513 Domain ModuloHelper(int64_t min, int64_t max, const Domain& modulo) {
514  DCHECK_GT(min, 0);
515  DCHECK_GT(modulo.Min(), 0);
516  const int64_t max_mod = modulo.Max() - 1;
517 
518  // The min/max are exact if the modulo is fixed. Note that we could return the
519  // exact domain with a potential hole but we currently don't.
520  if (modulo.Min() == modulo.Max()) {
521  const int64_t size = max - min;
522  const int64_t v1 = min % modulo.Max();
523  if (v1 + size > max_mod) return Domain(0, max_mod);
524  return Domain(v1, v1 + size);
525  }
526 
527  // TODO(user): This is a superset.
528  return Domain(0, std::min(max, max_mod));
529 }
530 } // namespace
531 
533  if (IsEmpty()) return Domain();
534  CHECK_GT(modulo.Min(), 0);
535  const int64_t max_mod = modulo.Max() - 1;
536  if (Max() >= 0 && Min() <= 0) {
537  return Domain(std::max(Min(), -max_mod), std::min(Max(), max_mod));
538  }
539  if (Min() > 0) {
540  return ModuloHelper(Min(), Max(), modulo);
541  }
542  DCHECK_LT(Max(), 0);
543  return ModuloHelper(-Max(), -Min(), modulo).Negation();
544 }
545 
547  if (IsEmpty()) return Domain();
548  CHECK_GT(divisor.Min(), 0);
549  return Domain(std::min(Min() / divisor.Max(), Min() / divisor.Min()),
550  std::max(Max() / divisor.Min(), Max() / divisor.Max()));
551 }
552 
554  if (IsEmpty()) return Domain();
555  const Domain abs_domain =
559  if (abs_domain.Size() >= kDomainComplexityLimit) {
560  Domain result;
561  result.intervals_.reserve(abs_domain.NumIntervals());
562  for (const auto& interval : abs_domain.intervals()) {
563  result.intervals_.push_back(
564  ClosedInterval(CapProd(interval.start, interval.start),
565  CapProd(interval.end, interval.end)));
566  }
567  UnionOfSortedIntervals(&result.intervals_);
568  return result;
569  } else {
570  std::vector<int64_t> values;
571  values.reserve(abs_domain.Size());
572  for (const int64_t value : abs_domain.Values()) {
573  values.push_back(CapProd(value, value));
574  }
575  return Domain::FromValues(values);
576  }
577 }
578 
579 // It is a bit difficult to see, but this code is doing the same thing as
580 // for all interval in this.UnionWith(implied_domain.Complement())):
581 // - Take the two extreme points (min and max) in interval \inter implied.
582 // - Append to result [min, max] if these points exists.
583 Domain Domain::SimplifyUsingImpliedDomain(const Domain& implied_domain) const {
584  Domain result;
585  if (implied_domain.IsEmpty()) return result;
586 
587  int i = 0;
588  int64_t min_point;
589  int64_t max_point;
590  bool started = false;
591  for (const ClosedInterval interval : intervals_) {
592  // We only "close" the new result interval if it cannot be extended by
593  // implied_domain.Complement(). The only extension possible look like:
594  // interval_: ...] [....
595  // implied : ...] [... i ...]
596  if (started && implied_domain.intervals_[i].start < interval.start) {
597  result.intervals_.push_back({min_point, max_point});
598  started = false;
599  }
600 
601  // Find the two extreme points in interval \inter implied_domain.
602  // Always stop the loop at the first interval with and end strictly greater
603  // that interval.end.
604  for (; i < implied_domain.intervals_.size(); ++i) {
605  const ClosedInterval current = implied_domain.intervals_[i];
606  if (current.end >= interval.start && current.start <= interval.end) {
607  // Current and interval have a non-empty intersection.
608  const int64_t inter_max = std::min(interval.end, current.end);
609  if (!started) {
610  started = true;
611  min_point = std::max(interval.start, current.start);
612  max_point = inter_max;
613  } else {
614  // No need to update the min_point here, and the new inter_max must
615  // necessarily be > old one.
616  DCHECK_GE(inter_max, max_point);
617  max_point = inter_max;
618  }
619  }
620  if (current.end > interval.end) break;
621  }
622  if (i == implied_domain.intervals_.size()) break;
623  }
624  if (started) {
625  result.intervals_.push_back({min_point, max_point});
626  }
627  DCHECK(IntervalsAreSortedAndNonAdjacent(result.intervals_));
628  return result;
629 }
630 
631 std::vector<int64_t> Domain::FlattenedIntervals() const {
632  std::vector<int64_t> result;
633  for (const ClosedInterval& interval : intervals_) {
634  result.push_back(interval.start);
635  result.push_back(interval.end);
636  }
637  return result;
638 }
639 
640 bool Domain::operator<(const Domain& other) const {
641  const auto& d1 = intervals_;
642  const auto& d2 = other.intervals_;
643  const int common_size = std::min(d1.size(), d2.size());
644  for (int i = 0; i < common_size; ++i) {
645  const ClosedInterval& i1 = d1[i];
646  const ClosedInterval& i2 = d2[i];
647  if (i1.start < i2.start) return true;
648  if (i1.start > i2.start) return false;
649  if (i1.end < i2.end) return true;
650  if (i1.end > i2.end) return false;
651  }
652  return d1.size() < d2.size();
653 }
654 
655 std::string Domain::ToString() const { return IntervalsAsString(intervals_); }
656 
657 int64_t SumOfKMinValueInDomain(const Domain& domain, int k) {
658  int64_t current_sum = 0.0;
659  int current_index = 0;
660  for (const ClosedInterval interval : domain) {
661  if (current_index >= k) break;
662  for (int v(interval.start); v <= interval.end; ++v) {
663  if (current_index >= k) break;
664  current_index++;
665  current_sum += v;
666  }
667  }
668  return current_sum;
669 }
670 
671 int64_t SumOfKMaxValueInDomain(const Domain& domain, int k) {
672  return -SumOfKMinValueInDomain(domain.Negation(), k);
673 }
674 
676 
678  const std::vector<int64_t>& starts, const std::vector<int64_t>& ends) {
679  InsertIntervals(starts, ends);
680 }
681 
683  const std::vector<int>& starts, const std::vector<int>& ends) {
684  InsertIntervals(starts, ends);
685 }
686 
688  const std::vector<ClosedInterval>& intervals) {
689  for (ClosedInterval interval : intervals) {
690  InsertInterval(interval.start, interval.end);
691  }
692 }
693 
696  int64_t end) {
697  SortedDisjointIntervalList interval_list;
698  int64_t next_start = start;
699  for (auto it = FirstIntervalGreaterOrEqual(start); it != this->end(); ++it) {
700  const ClosedInterval& interval = *it;
701  const int64_t next_end = CapSub(interval.start, 1);
702  if (next_end > end) break;
703  if (next_start <= next_end) {
704  interval_list.InsertInterval(next_start, next_end);
705  }
706  next_start = CapAdd(interval.end, 1);
707  }
708  if (next_start <= end) {
709  interval_list.InsertInterval(next_start, end);
710  }
711  return interval_list;
712 }
713 
715  int64_t start, int64_t end) {
716  // start > end could mean an empty interval, but we prefer to LOG(DFATAL)
717  // anyway. Really, the user should never give us that.
718  if (start > end) {
719  LOG(DFATAL) << "Invalid interval: " << ClosedInterval({start, end});
720  return intervals_.end();
721  }
722 
723  auto result = intervals_.insert({start, end});
724  if (!result.second) return result.first; // Duplicate: exit immediately.
725 
726  // TODO(user): tune the algorithm below if it proves to be a bottleneck.
727  // For example, one could try to avoid an insertion if it's not needed
728  // (when the interval merges with a single existing interval or is fully
729  // contained by one).
730 
731  // Iterate over the previous iterators whose end is after (or almost at) our
732  // start. After this, "it1" will point to the first interval that needs to be
733  // merged with the current interval (possibly pointing to the current interval
734  // itself, if no "earlier" interval should be merged).
735  auto it1 = result.first;
736  if (start == kint64min) { // Catch underflows
737  it1 = intervals_.begin();
738  } else {
739  const int64_t before_start = start - 1;
740  while (it1 != intervals_.begin()) {
741  auto prev_it = it1;
742  --prev_it;
743  if (prev_it->end < before_start) break;
744  it1 = prev_it;
745  }
746  }
747 
748  // Ditto, on the other side: "it2" will point to the interval *after* the last
749  // one that should be merged with the current interval.
750  auto it2 = result.first;
751  if (end == kint64max) {
752  it2 = intervals_.end();
753  } else {
754  const int64_t after_end = end + 1;
755  do {
756  ++it2;
757  } while (it2 != intervals_.end() && it2->start <= after_end);
758  }
759 
760  // [it1..it2) is the range (inclusive on it1, exclusive on it2) of intervals
761  // that should be merged together. We'll set it3 = it2-1 and erase [it1..it3)
762  // and set *it3 to the merged interval.
763  auto it3 = it2;
764  it3--;
765  if (it1 == it3) return it3; // Nothing was merged.
766  const int64_t new_start = std::min(it1->start, start);
767  const int64_t new_end = std::max(it3->end, end);
768  auto it = intervals_.erase(it1, it3);
769  // HACK(user): set iterators point to *const* values. Which is expected,
770  // because if one alters a set element's value, then it collapses the set
771  // property! But in this very special case, we know that we can just overwrite
772  // it->start, so we do it.
773  const_cast<ClosedInterval*>(&(*it))->start = new_start;
774  const_cast<ClosedInterval*>(&(*it))->end = new_end;
775  return it;
776 }
777 
779  int64_t value, int64_t* newly_covered) {
780  auto it = intervals_.upper_bound({value, kint64max});
781  auto it_prev = it;
782 
783  // No interval containing or adjacent to "value" on the left (i.e. below).
784  if (it != begin()) {
785  --it_prev;
786  }
787  if (it == begin() || ((value != kint64min) && it_prev->end < value - 1)) {
788  *newly_covered = value;
789  if (it == end() || it->start != value + 1) {
790  // No interval adjacent to "value" on the right: insert a singleton.
791  return intervals_.insert(it, {value, value});
792  } else {
793  // There is an interval adjacent to "value" on the right. Extend it by
794  // one. Note that we already know that there won't be a merge with another
795  // interval on the left, since there were no interval adjacent to "value"
796  // on the left.
797  DCHECK_EQ(it->start, value + 1);
798  const_cast<ClosedInterval*>(&(*it))->start = value;
799  return it;
800  }
801  }
802 
803  // At this point, "it_prev" points to an interval containing or adjacent to
804  // "value" on the left: grow it by one, and if it now touches the next
805  // interval, merge with it.
806  CHECK_NE(kint64max, it_prev->end) << "Cannot grow right by one: the interval "
807  "that would grow already ends at "
808  "kint64max";
809  *newly_covered = it_prev->end + 1;
810  if (it != end() && it_prev->end + 2 == it->start) {
811  // We need to merge it_prev with 'it'.
812  const_cast<ClosedInterval*>(&(*it_prev))->end = it->end;
813  intervals_.erase(it);
814  } else {
815  const_cast<ClosedInterval*>(&(*it_prev))->end = it_prev->end + 1;
816  }
817  return it_prev;
818 }
819 
820 template <class T>
821 void SortedDisjointIntervalList::InsertAll(const std::vector<T>& starts,
822  const std::vector<T>& ends) {
823  CHECK_EQ(starts.size(), ends.size());
824  for (int i = 0; i < starts.size(); ++i) InsertInterval(starts[i], ends[i]);
825 }
826 
828  const std::vector<int64_t>& starts, const std::vector<int64_t>& ends) {
829  InsertAll(starts, ends);
830 }
831 
832 void SortedDisjointIntervalList::InsertIntervals(const std::vector<int>& starts,
833  const std::vector<int>& ends) {
834  // TODO(user): treat kint32min and kint32max as their kint64 variants.
835  InsertAll(starts, ends);
836 }
837 
840  const auto it = intervals_.upper_bound({value, kint64max});
841  if (it == begin()) return it;
842  auto it_prev = it;
843  it_prev--;
844  DCHECK_LE(it_prev->start, value);
845  return it_prev->end >= value ? it_prev : it;
846 }
847 
850  const auto it = intervals_.upper_bound({value, kint64max});
851  if (it == begin()) return end();
852  auto it_prev = it;
853  it_prev--;
854  return it_prev;
855 }
856 
858  std::string str;
859  for (const ClosedInterval& interval : intervals_) {
860  str += interval.DebugString();
861  }
862  return str;
863 }
864 
865 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
We call domain any subset of Int64 = [kint64min, kint64max].
static Domain AllValues()
Returns the full domain Int64.
Domain InverseMultiplicationBy(const int64_t coeff) const
Returns {x ∈ Int64, ∃ e ∈ D, x * coeff = e}.
std::string ToString() const
Returns a compact string of a vector of intervals like "[1,4][6][10,20]".
Domain Negation() const
Returns {x ∈ Int64, ∃ e ∈ D, x = -e}.
Domain Complement() const
Returns the set Int64 ∖ D.
bool IsIncludedIn(const Domain &domain) const
Returns true iff D is included in the given domain.
bool Contains(int64_t value) const
Returns true iff value is in Domain.
Domain ContinuousMultiplicationBy(int64_t coeff) const
Returns a superset of MultiplicationBy() to avoid the explosion in the representation size.
static Domain FromFlatSpanOfIntervals(absl::Span< const int64_t > flat_intervals)
Same as FromIntervals() for a flattened representation (start, end, start, end, .....
absl::InlinedVector< ClosedInterval, 1 >::const_iterator end() const
int NumIntervals() const
Basic read-only std::vector<> wrapping to view a Domain as a sorted list of non-adjacent intervals.
int64_t FixedValue() const
Returns the value of a fixed domain.
bool operator<(const Domain &other) const
Lexicographic order on the intervals() representation.
Domain AdditionWith(const Domain &domain) const
Returns {x ∈ Int64, ∃ a ∈ D, ∃ b ∈ domain, x = a + b}.
int64_t Size() const
Returns the number of elements in the domain.
Domain UnionWith(const Domain &domain) const
Returns the union of D and domain.
Domain MultiplicationBy(int64_t coeff, bool *exact=nullptr) const
Returns {x ∈ Int64, ∃ e ∈ D, x = e * coeff}.
static Domain FromFlatIntervals(const std::vector< int64_t > &flat_intervals)
This method is available in Python, Java and .NET.
std::vector< int64_t > FlattenedIntervals() const
This method returns the flattened list of interval bounds of the domain.
bool IsFixed() const
Returns true iff the domain is reduced to a single value.
Domain IntersectionWith(const Domain &domain) const
Returns the intersection of D and domain.
static Domain FromVectorIntervals(const std::vector< std::vector< int64_t > > &intervals)
This method is available in Python, Java and .NET.
int64_t Min() const
Returns the min value of the domain.
bool IsEmpty() const
Returns true if this is the empty set.
Domain PositiveDivisionBySuperset(const Domain &divisor) const
Returns a superset of {x ∈ Int64, ∃ e ∈ D, ∃ d ∈ divisor, x = e / d }.
std::vector< ClosedInterval > intervals() const
static Domain FromIntervals(absl::Span< const ClosedInterval > intervals)
Creates a domain from the union of an unsorted list of intervals.
Domain()
By default, Domain will be empty.
int64_t SmallestValue() const
Returns the value closest to zero.
int64_t Max() const
Returns the max value of the domain.
Domain RelaxIfTooComplex() const
If NumIntervals() is too large, this return a superset of the domain.
static Domain FromValues(std::vector< int64_t > values)
Creates a domain from the union of an unsorted list of integer values.
Domain SquareSuperset() const
Returns a superset of {x ∈ Int64, ∃ y ∈ D, x = y * y }.
Domain DivisionBy(int64_t coeff) const
Returns {x ∈ Int64, ∃ e ∈ D, x = e / coeff}.
DomainIteratorBeginEnd Values() const &
int64_t ValueAtOrAfter(int64_t input) const
Domain PositiveModuloBySuperset(const Domain &modulo) const
Returns a superset of {x ∈ Int64, ∃ e ∈ D, ∃ m ∈ modulo, x = e % m }.
Domain SimplifyUsingImpliedDomain(const Domain &implied_domain) const
Advanced usage.
int64_t ValueAtOrBefore(int64_t input) const
Returns the closest value in the domain that is <= (resp.
This class represents a sorted list of disjoint, closed intervals.
Iterator GrowRightByOne(int64_t value, int64_t *newly_covered)
If value is in an interval, increase its end by one, otherwise insert the interval [value,...
void InsertIntervals(const std::vector< int64_t > &starts, const std::vector< int64_t > &ends)
Adds all intervals [starts[i]..ends[i]].
Iterator InsertInterval(int64_t start, int64_t end)
Adds the interval [start..end] to the list, and merges overlapping or immediately adjacent intervals ...
Iterator FirstIntervalGreaterOrEqual(int64_t value) const
Returns an iterator to either:
ConstIterator begin() const
Const iterators for SortedDisjoinIntervalList.
SortedDisjointIntervalList BuildComplementOnInterval(int64_t start, int64_t end)
Builds the complement of the interval list on the interval [start, end].
int64_t b
int64_t a
int64_t value
static const int64_t kint64max
static const int64_t kint64min
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
Definition: id_map.h:269
Collection of objects used to extend the Constraint Solver library.
int64_t SumOfKMinValueInDomain(const Domain &domain, int k)
int64_t CapAdd(int64_t x, int64_t y)
int64_t FloorRatio(int64_t value, int64_t positive_coeff)
int64_t CapSub(int64_t x, int64_t y)
std::ostream & operator<<(std::ostream &out, const Assignment &assignment)
int64_t CeilRatio(int64_t value, int64_t positive_coeff)
int64_t SumOfKMaxValueInDomain(const Domain &domain, int k)
int64_t CapProd(int64_t x, int64_t y)
bool IntervalsAreSortedAndNonAdjacent(absl::Span< const ClosedInterval > intervals)
Returns true iff we have:
static int input(yyscan_t yyscanner)
IntervalVar * interval
Definition: resource.cc:101
IntVar * upper_bound
Definition: routing.cc:1087
std::optional< int64_t > end
int64_t start
Represents a closed interval [start, end].