diff --git a/interval.hh b/interval.hh new file mode 100644 index 0000000000..4928c6453f --- /dev/null +++ b/interval.hh @@ -0,0 +1,728 @@ +/* + * Copyright (C) 2015 ScyllaDB + */ + +/* + * This file is part of Scylla. + * + * Scylla is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Scylla is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Scylla. If not, see . + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +template +class interval_bound { + T _value; + bool _inclusive; +public: + interval_bound(T value, bool inclusive = true) + : _value(std::move(value)) + , _inclusive(inclusive) + { } + const T& value() const & { return _value; } + T&& value() && { return std::move(_value); } + bool is_inclusive() const { return _inclusive; } + bool operator==(const interval_bound& other) const { + return (_value == other._value) && (_inclusive == other._inclusive); + } + template + bool equal(const interval_bound& other, Comparator&& cmp) const { + return _inclusive == other._inclusive && cmp(_value, other._value) == 0; + } +}; + +template +class nonwrapping_interval; + +template +using interval = nonwrapping_interval; + +// An interval which can have inclusive, exclusive or open-ended bounds on each end. +// The end bound can be smaller than the start bound. +template +class wrapping_interval { + template + using optional = std::optional; +public: + using bound = interval_bound; + + template + using transformed_type = typename std::remove_cv_t>>; +private: + optional _start; + optional _end; + bool _singular; +public: + wrapping_interval(optional start, optional end, bool singular = false) + : _start(std::move(start)) + , _singular(singular) { + if (!_singular) { + _end = std::move(end); + } + } + wrapping_interval(T value) + : _start(bound(std::move(value), true)) + , _end() + , _singular(true) + { } + wrapping_interval() : wrapping_interval({}, {}) { } +private: + // Bound wrappers for compile-time dispatch and safety. + struct start_bound_ref { const optional& b; }; + struct end_bound_ref { const optional& b; }; + + start_bound_ref start_bound() const { return { start() }; } + end_bound_ref end_bound() const { return { end() }; } + + template + static bool greater_than_or_equal(end_bound_ref end, start_bound_ref start, Comparator&& cmp) { + return !end.b || !start.b || cmp(end.b->value(), start.b->value()) + >= (!end.b->is_inclusive() || !start.b->is_inclusive()); + } + + template + static bool less_than(end_bound_ref end, start_bound_ref start, Comparator&& cmp) { + return !greater_than_or_equal(end, start, cmp); + } + + template + static bool less_than_or_equal(start_bound_ref first, start_bound_ref second, Comparator&& cmp) { + return !first.b || (second.b && cmp(first.b->value(), second.b->value()) + <= -(!first.b->is_inclusive() && second.b->is_inclusive())); + } + + template + static bool less_than(start_bound_ref first, start_bound_ref second, Comparator&& cmp) { + return second.b && (!first.b || cmp(first.b->value(), second.b->value()) + < (first.b->is_inclusive() && !second.b->is_inclusive())); + } + + template + static bool greater_than_or_equal(end_bound_ref first, end_bound_ref second, Comparator&& cmp) { + return !first.b || (second.b && cmp(first.b->value(), second.b->value()) + >= (!first.b->is_inclusive() && second.b->is_inclusive())); + } +public: + // the point is before the interval (works only for non wrapped intervals) + // Comparator must define a total ordering on T. + template + bool before(const T& point, Comparator&& cmp) const { + assert(!is_wrap_around(cmp)); + if (!start()) { + return false; //open start, no points before + } + auto r = cmp(point, start()->value()); + if (r < 0) { + return true; + } + if (!start()->is_inclusive() && r == 0) { + return true; + } + return false; + } + // the point is after the interval (works only for non wrapped intervals) + // Comparator must define a total ordering on T. + template + bool after(const T& point, Comparator&& cmp) const { + assert(!is_wrap_around(cmp)); + if (!end()) { + return false; //open end, no points after + } + auto r = cmp(end()->value(), point); + if (r < 0) { + return true; + } + if (!end()->is_inclusive() && r == 0) { + return true; + } + return false; + } + // check if two intervals overlap. + // Comparator must define a total ordering on T. + template + bool overlaps(const wrapping_interval& other, Comparator&& cmp) const { + bool this_wraps = is_wrap_around(cmp); + bool other_wraps = other.is_wrap_around(cmp); + + if (this_wraps && other_wraps) { + return true; + } else if (this_wraps) { + auto unwrapped = unwrap(); + return other.overlaps(unwrapped.first, cmp) || other.overlaps(unwrapped.second, cmp); + } else if (other_wraps) { + auto unwrapped = other.unwrap(); + return overlaps(unwrapped.first, cmp) || overlaps(unwrapped.second, cmp); + } + + // No interval should reach this point as wrap around. + assert(!this_wraps); + assert(!other_wraps); + + // if both this and other have an open start, the two intervals will overlap. + if (!start() && !other.start()) { + return true; + } + + return greater_than_or_equal(end_bound(), other.start_bound(), cmp) + && greater_than_or_equal(other.end_bound(), start_bound(), cmp); + } + static wrapping_interval make(bound start, bound end) { + return wrapping_interval({std::move(start)}, {std::move(end)}); + } + static wrapping_interval make_open_ended_both_sides() { + return {{}, {}}; + } + static wrapping_interval make_singular(T value) { + return {std::move(value)}; + } + static wrapping_interval make_starting_with(bound b) { + return {{std::move(b)}, {}}; + } + static wrapping_interval make_ending_with(bound b) { + return {{}, {std::move(b)}}; + } + bool is_singular() const { + return _singular; + } + bool is_full() const { + return !_start && !_end; + } + void reverse() { + if (!_singular) { + std::swap(_start, _end); + } + } + const optional& start() const { + return _start; + } + const optional& end() const { + return _singular ? _start : _end; + } + // Range is a wrap around if end value is smaller than the start value + // or they're equal and at least one bound is not inclusive. + // Comparator must define a total ordering on T. + template + bool is_wrap_around(Comparator&& cmp) const { + if (_end && _start) { + auto r = cmp(end()->value(), start()->value()); + return r < 0 + || (r == 0 && (!start()->is_inclusive() || !end()->is_inclusive())); + } else { + return false; // open ended interval or singular interval don't wrap around + } + } + // Converts a wrap-around interval to two non-wrap-around intervals. + // The returned intervals are not overlapping and ordered. + // Call only when is_wrap_around(). + std::pair unwrap() const { + return { + { {}, end() }, + { start(), {} } + }; + } + // the point is inside the interval + // Comparator must define a total ordering on T. + template + bool contains(const T& point, Comparator&& cmp) const { + if (is_wrap_around(cmp)) { + auto unwrapped = unwrap(); + return unwrapped.first.contains(point, cmp) + || unwrapped.second.contains(point, cmp); + } else { + return !before(point, cmp) && !after(point, cmp); + } + } + // Returns true iff all values contained by other are also contained by this. + // Comparator must define a total ordering on T. + template + bool contains(const wrapping_interval& other, Comparator&& cmp) const { + bool this_wraps = is_wrap_around(cmp); + bool other_wraps = other.is_wrap_around(cmp); + + if (this_wraps && other_wraps) { + return cmp(start()->value(), other.start()->value()) + <= -(!start()->is_inclusive() && other.start()->is_inclusive()) + && cmp(end()->value(), other.end()->value()) + >= (!end()->is_inclusive() && other.end()->is_inclusive()); + } + + if (!this_wraps && !other_wraps) { + return less_than_or_equal(start_bound(), other.start_bound(), cmp) + && greater_than_or_equal(end_bound(), other.end_bound(), cmp); + } + + if (other_wraps) { // && !this_wraps + return !start() && !end(); + } + + // !other_wraps && this_wraps + return (other.start() && cmp(start()->value(), other.start()->value()) + <= -(!start()->is_inclusive() && other.start()->is_inclusive())) + || (other.end() && cmp(end()->value(), other.end()->value()) + >= (!end()->is_inclusive() && other.end()->is_inclusive())); + } + // Returns intervals which cover all values covered by this interval but not covered by the other interval. + // Ranges are not overlapping and ordered. + // Comparator must define a total ordering on T. + template + std::vector subtract(const wrapping_interval& other, Comparator&& cmp) const { + std::vector result; + std::list left; + std::list right; + + if (is_wrap_around(cmp)) { + auto u = unwrap(); + left.emplace_back(std::move(u.first)); + left.emplace_back(std::move(u.second)); + } else { + left.push_back(*this); + } + + if (other.is_wrap_around(cmp)) { + auto u = other.unwrap(); + right.emplace_back(std::move(u.first)); + right.emplace_back(std::move(u.second)); + } else { + right.push_back(other); + } + + // left and right contain now non-overlapping, ordered intervals + + while (!left.empty() && !right.empty()) { + auto& r1 = left.front(); + auto& r2 = right.front(); + if (less_than(r2.end_bound(), r1.start_bound(), cmp)) { + right.pop_front(); + } else if (less_than(r1.end_bound(), r2.start_bound(), cmp)) { + result.emplace_back(std::move(r1)); + left.pop_front(); + } else { // Overlap + auto tmp = std::move(r1); + left.pop_front(); + if (!greater_than_or_equal(r2.end_bound(), tmp.end_bound(), cmp)) { + left.push_front({bound(r2.end()->value(), !r2.end()->is_inclusive()), tmp.end()}); + } + if (!less_than_or_equal(r2.start_bound(), tmp.start_bound(), cmp)) { + left.push_front({tmp.start(), bound(r2.start()->value(), !r2.start()->is_inclusive())}); + } + } + } + + boost::copy(left, std::back_inserter(result)); + + // TODO: Merge adjacent intervals (optimization) + return result; + } + // split interval in two around a split_point. split_point has to be inside the interval + // split_point will belong to first interval + // Comparator must define a total ordering on T. + template + std::pair, wrapping_interval> split(const T& split_point, Comparator&& cmp) const { + assert(contains(split_point, std::forward(cmp))); + wrapping_interval left(start(), bound(split_point)); + wrapping_interval right(bound(split_point, false), end()); + return std::make_pair(std::move(left), std::move(right)); + } + // Create a sub-interval including values greater than the split_point. Returns std::nullopt if + // split_point is after the end (but not included in the interval, in case of wraparound intervals) + // Comparator must define a total ordering on T. + template + std::optional> split_after(const T& split_point, Comparator&& cmp) const { + if (contains(split_point, std::forward(cmp)) + && (!end() || cmp(split_point, end()->value()) != 0)) { + return wrapping_interval(bound(split_point, false), end()); + } else if (end() && cmp(split_point, end()->value()) >= 0) { + // whether to return std::nullopt or the full interval is not + // well-defined for wraparound intervals; we return nullopt + // if split_point is after the end. + return std::nullopt; + } else { + return *this; + } + } + template> + static std::optional::bound> transform_bound(Bound&& b, Transformer&& transformer) { + if (b) { + return { { transformer(std::forward(b).value().value()), b->is_inclusive() } }; + }; + return {}; + } + // Transforms this interval into a new interval of a different value type + // Supplied transformer should transform value of type T (the old type) into value of type U (the new type). + template> + wrapping_interval transform(Transformer&& transformer) && { + return wrapping_interval(transform_bound(std::move(_start), transformer), transform_bound(std::move(_end), transformer), _singular); + } + template> + wrapping_interval transform(Transformer&& transformer) const & { + return wrapping_interval(transform_bound(_start, transformer), transform_bound(_end, transformer), _singular); + } + template + bool equal(const wrapping_interval& other, Comparator&& cmp) const { + return bool(_start) == bool(other._start) + && bool(_end) == bool(other._end) + && (!_start || _start->equal(*other._start, cmp)) + && (!_end || _end->equal(*other._end, cmp)) + && _singular == other._singular; + } + bool operator==(const wrapping_interval& other) const { + return (_start == other._start) && (_end == other._end) && (_singular == other._singular); + } + + template + friend std::ostream& operator<<(std::ostream& out, const wrapping_interval& r); +private: + friend class nonwrapping_interval; +}; + +template +std::ostream& operator<<(std::ostream& out, const wrapping_interval& r) { + if (r.is_singular()) { + return out << "{" << r.start()->value() << "}"; + } + + if (!r.start()) { + out << "(-inf, "; + } else { + if (r.start()->is_inclusive()) { + out << "["; + } else { + out << "("; + } + out << r.start()->value() << ", "; + } + + if (!r.end()) { + out << "+inf)"; + } else { + out << r.end()->value(); + if (r.end()->is_inclusive()) { + out << "]"; + } else { + out << ")"; + } + } + + return out; +} + +// An interval which can have inclusive, exclusive or open-ended bounds on each end. +// The end bound can never be smaller than the start bound. +template +class nonwrapping_interval { + template + using optional = std::optional; +public: + using bound = interval_bound; + + template + using transformed_type = typename wrapping_interval::template transformed_type; +private: + wrapping_interval _interval; +public: + nonwrapping_interval(T value) + : _interval(std::move(value)) + { } + nonwrapping_interval() : nonwrapping_interval({}, {}) { } + // Can only be called if start <= end. IDL ctor. + nonwrapping_interval(optional start, optional end, bool singular = false) + : _interval(std::move(start), std::move(end), singular) + { } + // Can only be called if !r.is_wrap_around(). + explicit nonwrapping_interval(wrapping_interval&& r) + : _interval(std::move(r)) + { } + // Can only be called if !r.is_wrap_around(). + explicit nonwrapping_interval(const wrapping_interval& r) + : _interval(r) + { } + operator wrapping_interval() const & { + return _interval; + } + operator wrapping_interval() && { + return std::move(_interval); + } + + // the point is before the interval. + // Comparator must define a total ordering on T. + template + bool before(const T& point, Comparator&& cmp) const { + return _interval.before(point, std::forward(cmp)); + } + // the point is after the interval. + // Comparator must define a total ordering on T. + template + bool after(const T& point, Comparator&& cmp) const { + return _interval.after(point, std::forward(cmp)); + } + // check if two intervals overlap. + // Comparator must define a total ordering on T. + template + bool overlaps(const nonwrapping_interval& other, Comparator&& cmp) const { + // if both this and other have an open start, the two intervals will overlap. + if (!start() && !other.start()) { + return true; + } + + return wrapping_interval::greater_than_or_equal(_interval.end_bound(), other._interval.start_bound(), cmp) + && wrapping_interval::greater_than_or_equal(other._interval.end_bound(), _interval.start_bound(), cmp); + } + static nonwrapping_interval make(bound start, bound end) { + return nonwrapping_interval({std::move(start)}, {std::move(end)}); + } + static nonwrapping_interval make_open_ended_both_sides() { + return {{}, {}}; + } + static nonwrapping_interval make_singular(T value) { + return {std::move(value)}; + } + static nonwrapping_interval make_starting_with(bound b) { + return {{std::move(b)}, {}}; + } + static nonwrapping_interval make_ending_with(bound b) { + return {{}, {std::move(b)}}; + } + bool is_singular() const { + return _interval.is_singular(); + } + bool is_full() const { + return _interval.is_full(); + } + const optional& start() const { + return _interval.start(); + } + const optional& end() const { + return _interval.end(); + } + // the point is inside the interval + // Comparator must define a total ordering on T. + template + bool contains(const T& point, Comparator&& cmp) const { + return !before(point, cmp) && !after(point, cmp); + } + // Returns true iff all values contained by other are also contained by this. + // Comparator must define a total ordering on T. + template + bool contains(const nonwrapping_interval& other, Comparator&& cmp) const { + return wrapping_interval::less_than_or_equal(_interval.start_bound(), other._interval.start_bound(), cmp) + && wrapping_interval::greater_than_or_equal(_interval.end_bound(), other._interval.end_bound(), cmp); + } + // Returns intervals which cover all values covered by this interval but not covered by the other interval. + // Ranges are not overlapping and ordered. + // Comparator must define a total ordering on T. + template + std::vector subtract(const nonwrapping_interval& other, Comparator&& cmp) const { + auto subtracted = _interval.subtract(other._interval, std::forward(cmp)); + return boost::copy_range>(subtracted | boost::adaptors::transformed([](auto&& r) { + return nonwrapping_interval(std::move(r)); + })); + } + // split interval in two around a split_point. split_point has to be inside the interval + // split_point will belong to first interval + // Comparator must define a total ordering on T. + template + std::pair, nonwrapping_interval> split(const T& split_point, Comparator&& cmp) const { + assert(contains(split_point, std::forward(cmp))); + nonwrapping_interval left(start(), bound(split_point)); + nonwrapping_interval right(bound(split_point, false), end()); + return std::make_pair(std::move(left), std::move(right)); + } + // Create a sub-interval including values greater than the split_point. If split_point is after + // the end, returns std::nullopt. + template + std::optional split_after(const T& split_point, Comparator&& cmp) const { + if (end() && cmp(split_point, end()->value()) >= 0) { + return std::nullopt; + } else if (start() && cmp(split_point, start()->value()) < 0) { + return *this; + } else { + return nonwrapping_interval(interval_bound(split_point, false), end()); + } + } + // Creates a new sub-interval which is the intersection of this interval and an interval starting with "start". + // If there is no overlap, returns std::nullopt. + template + std::optional trim_front(std::optional&& start, Comparator&& cmp) const { + return intersection(nonwrapping_interval(std::move(start), {}), cmp); + } + // Transforms this interval into a new interval of a different value type + // Supplied transformer should transform value of type T (the old type) into value of type U (the new type). + template> + nonwrapping_interval transform(Transformer&& transformer) && { + return nonwrapping_interval(std::move(_interval).transform(std::forward(transformer))); + } + template> + nonwrapping_interval transform(Transformer&& transformer) const & { + return nonwrapping_interval(_interval.transform(std::forward(transformer))); + } + template + bool equal(const nonwrapping_interval& other, Comparator&& cmp) const { + return _interval.equal(other._interval, std::forward(cmp)); + } + bool operator==(const nonwrapping_interval& other) const { + return _interval == other._interval; + } + // Takes a vector of possibly overlapping intervals and returns a vector containing + // a set of non-overlapping intervals covering the same values. + template + static std::vector deoverlap(std::vector intervals, Comparator&& cmp) { + auto size = intervals.size(); + if (size <= 1) { + return intervals; + } + + std::sort(intervals.begin(), intervals.end(), [&](auto&& r1, auto&& r2) { + return wrapping_interval::less_than(r1._interval.start_bound(), r2._interval.start_bound(), cmp); + }); + + std::vector deoverlapped_intervals; + deoverlapped_intervals.reserve(size); + + auto&& current = intervals[0]; + for (auto&& r : intervals | boost::adaptors::sliced(1, intervals.size())) { + bool includes_end = wrapping_interval::greater_than_or_equal(r._interval.end_bound(), current._interval.start_bound(), cmp) + && wrapping_interval::greater_than_or_equal(current._interval.end_bound(), r._interval.end_bound(), cmp); + if (includes_end) { + continue; // last.start <= r.start <= r.end <= last.end + } + bool includes_start = wrapping_interval::greater_than_or_equal(current._interval.end_bound(), r._interval.start_bound(), cmp); + if (includes_start) { + current = nonwrapping_interval(std::move(current.start()), std::move(r.end())); + } else { + deoverlapped_intervals.emplace_back(std::move(current)); + current = std::move(r); + } + } + + deoverlapped_intervals.emplace_back(std::move(current)); + return deoverlapped_intervals; + } + +private: + // These private functions optimize the case where a sequence supports the + // lower and upper bound operations more efficiently, as is the case with + // some boost containers. + struct std_ {}; + struct built_in_ : std_ {}; + + template().lower_bound(std::declval(), std::declval()))> + typename std::remove_reference::type::const_iterator do_lower_bound(const T& value, Range&& r, LessComparator&& cmp, built_in_) const { + return r.lower_bound(value, std::forward(cmp)); + } + + template().upper_bound(std::declval(), std::declval()))> + typename std::remove_reference::type::const_iterator do_upper_bound(const T& value, Range&& r, LessComparator&& cmp, built_in_) const { + return r.upper_bound(value, std::forward(cmp)); + } + + template + typename std::remove_reference::type::const_iterator do_lower_bound(const T& value, Range&& r, LessComparator&& cmp, std_) const { + return std::lower_bound(r.begin(), r.end(), value, std::forward(cmp)); + } + + template + typename std::remove_reference::type::const_iterator do_upper_bound(const T& value, Range&& r, LessComparator&& cmp, std_) const { + return std::upper_bound(r.begin(), r.end(), value, std::forward(cmp)); + } +public: + // Return the lower bound of the specified sequence according to these bounds. + template + typename std::remove_reference::type::const_iterator lower_bound(Range&& r, LessComparator&& cmp) const { + return start() + ? (start()->is_inclusive() + ? do_lower_bound(start()->value(), std::forward(r), std::forward(cmp), built_in_()) + : do_upper_bound(start()->value(), std::forward(r), std::forward(cmp), built_in_())) + : std::cbegin(r); + } + // Return the upper bound of the specified sequence according to these bounds. + template + typename std::remove_reference::type::const_iterator upper_bound(Range&& r, LessComparator&& cmp) const { + return end() + ? (end()->is_inclusive() + ? do_upper_bound(end()->value(), std::forward(r), std::forward(cmp), built_in_()) + : do_lower_bound(end()->value(), std::forward(r), std::forward(cmp), built_in_())) + : (is_singular() + ? do_upper_bound(start()->value(), std::forward(r), std::forward(cmp), built_in_()) + : std::cend(r)); + } + // Returns a subset of the range that is within these bounds. + template + boost::iterator_range::type::const_iterator> + slice(Range&& range, LessComparator&& cmp) const { + return boost::make_iterator_range(lower_bound(range, cmp), upper_bound(range, cmp)); + } + + // Returns the intersection between this interval and other. + template + std::optional intersection(const nonwrapping_interval& other, Comparator&& cmp) const { + auto p = std::minmax(_interval, other._interval, [&cmp] (auto&& a, auto&& b) { + return wrapping_interval::less_than(a.start_bound(), b.start_bound(), cmp); + }); + if (wrapping_interval::greater_than_or_equal(p.first.end_bound(), p.second.start_bound(), cmp)) { + auto end = std::min(p.first.end_bound(), p.second.end_bound(), [&cmp] (auto&& a, auto&& b) { + return !wrapping_interval::greater_than_or_equal(a, b, cmp); + }); + return nonwrapping_interval(p.second.start(), end.b); + } + return {}; + } + + template + friend std::ostream& operator<<(std::ostream& out, const nonwrapping_interval& r); +}; + +template +std::ostream& operator<<(std::ostream& out, const nonwrapping_interval& r) { + return out << r._interval; +} + +template typename T, typename U> +concept Interval = std::is_same, wrapping_interval>::value || std::is_same, nonwrapping_interval>::value; + +// Allow using interval in a hash table. The hash function 31 * left + +// right is the same one used by Cassandra's AbstractBounds.hashCode(). +namespace std { + +template +struct hash> { + using argument_type = wrapping_interval; + using result_type = decltype(std::hash()(std::declval())); + result_type operator()(argument_type const& s) const { + auto hash = std::hash(); + auto left = s.start() ? hash(s.start()->value()) : 0; + auto right = s.end() ? hash(s.end()->value()) : 0; + return 31 * left + right; + } +}; + +template +struct hash> { + using argument_type = nonwrapping_interval; + using result_type = decltype(std::hash()(std::declval())); + result_type operator()(argument_type const& s) const { + return hash>()(s); + } +}; + +} diff --git a/range.hh b/range.hh index 7e94527735..1f8e2a7167 100644 --- a/range.hh +++ b/range.hh @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 ScyllaDB + * Copyright (C) 2020 ScyllaDB */ /* @@ -21,708 +21,22 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include +#include "interval.hh" -template -class range_bound { - T _value; - bool _inclusive; -public: - range_bound(T value, bool inclusive = true) - : _value(std::move(value)) - , _inclusive(inclusive) - { } - const T& value() const & { return _value; } - T&& value() && { return std::move(_value); } - bool is_inclusive() const { return _inclusive; } - bool operator==(const range_bound& other) const { - return (_value == other._value) && (_inclusive == other._inclusive); - } - template - bool equal(const range_bound& other, Comparator&& cmp) const { - return _inclusive == other._inclusive && cmp(_value, other._value) == 0; - } -}; +// range.hh is deprecated and should be replaced with interval.hh -template -class nonwrapping_range; -// A range which can have inclusive, exclusive or open-ended bounds on each end. -// The end bound can be smaller than the start bound. -template -class wrapping_range { - template - using optional = std::optional; -public: - using bound = range_bound; +template +using range_bound = interval_bound; - template - using transformed_type = typename std::remove_cv_t>>; -private: - optional _start; - optional _end; - bool _singular; -public: - wrapping_range(optional start, optional end, bool singular = false) - : _start(std::move(start)) - , _singular(singular) { - if (!_singular) { - _end = std::move(end); - } - } - wrapping_range(T value) - : _start(bound(std::move(value), true)) - , _end() - , _singular(true) - { } - wrapping_range() : wrapping_range({}, {}) { } -private: - // Bound wrappers for compile-time dispatch and safety. - struct start_bound_ref { const optional& b; }; - struct end_bound_ref { const optional& b; }; +template +using nonwrapping_range = interval; - start_bound_ref start_bound() const { return { start() }; } - end_bound_ref end_bound() const { return { end() }; } +template +using wrapping_range = wrapping_interval; - template - static bool greater_than_or_equal(end_bound_ref end, start_bound_ref start, Comparator&& cmp) { - return !end.b || !start.b || cmp(end.b->value(), start.b->value()) - >= (!end.b->is_inclusive() || !start.b->is_inclusive()); - } +template +using range = wrapping_interval; - template - static bool less_than(end_bound_ref end, start_bound_ref start, Comparator&& cmp) { - return !greater_than_or_equal(end, start, cmp); - } - - template - static bool less_than_or_equal(start_bound_ref first, start_bound_ref second, Comparator&& cmp) { - return !first.b || (second.b && cmp(first.b->value(), second.b->value()) - <= -(!first.b->is_inclusive() && second.b->is_inclusive())); - } - - template - static bool less_than(start_bound_ref first, start_bound_ref second, Comparator&& cmp) { - return second.b && (!first.b || cmp(first.b->value(), second.b->value()) - < (first.b->is_inclusive() && !second.b->is_inclusive())); - } - - template - static bool greater_than_or_equal(end_bound_ref first, end_bound_ref second, Comparator&& cmp) { - return !first.b || (second.b && cmp(first.b->value(), second.b->value()) - >= (!first.b->is_inclusive() && second.b->is_inclusive())); - } -public: - // the point is before the range (works only for non wrapped ranges) - // Comparator must define a total ordering on T. - template - bool before(const T& point, Comparator&& cmp) const { - assert(!is_wrap_around(cmp)); - if (!start()) { - return false; //open start, no points before - } - auto r = cmp(point, start()->value()); - if (r < 0) { - return true; - } - if (!start()->is_inclusive() && r == 0) { - return true; - } - return false; - } - // the point is after the range (works only for non wrapped ranges) - // Comparator must define a total ordering on T. - template - bool after(const T& point, Comparator&& cmp) const { - assert(!is_wrap_around(cmp)); - if (!end()) { - return false; //open end, no points after - } - auto r = cmp(end()->value(), point); - if (r < 0) { - return true; - } - if (!end()->is_inclusive() && r == 0) { - return true; - } - return false; - } - // check if two ranges overlap. - // Comparator must define a total ordering on T. - template - bool overlaps(const wrapping_range& other, Comparator&& cmp) const { - bool this_wraps = is_wrap_around(cmp); - bool other_wraps = other.is_wrap_around(cmp); - - if (this_wraps && other_wraps) { - return true; - } else if (this_wraps) { - auto unwrapped = unwrap(); - return other.overlaps(unwrapped.first, cmp) || other.overlaps(unwrapped.second, cmp); - } else if (other_wraps) { - auto unwrapped = other.unwrap(); - return overlaps(unwrapped.first, cmp) || overlaps(unwrapped.second, cmp); - } - - // No range should reach this point as wrap around. - assert(!this_wraps); - assert(!other_wraps); - - // if both this and other have an open start, the two ranges will overlap. - if (!start() && !other.start()) { - return true; - } - - return greater_than_or_equal(end_bound(), other.start_bound(), cmp) - && greater_than_or_equal(other.end_bound(), start_bound(), cmp); - } - static wrapping_range make(bound start, bound end) { - return wrapping_range({std::move(start)}, {std::move(end)}); - } - static wrapping_range make_open_ended_both_sides() { - return {{}, {}}; - } - static wrapping_range make_singular(T value) { - return {std::move(value)}; - } - static wrapping_range make_starting_with(bound b) { - return {{std::move(b)}, {}}; - } - static wrapping_range make_ending_with(bound b) { - return {{}, {std::move(b)}}; - } - bool is_singular() const { - return _singular; - } - bool is_full() const { - return !_start && !_end; - } - void reverse() { - if (!_singular) { - std::swap(_start, _end); - } - } - const optional& start() const { - return _start; - } - const optional& end() const { - return _singular ? _start : _end; - } - // Range is a wrap around if end value is smaller than the start value - // or they're equal and at least one bound is not inclusive. - // Comparator must define a total ordering on T. - template - bool is_wrap_around(Comparator&& cmp) const { - if (_end && _start) { - auto r = cmp(end()->value(), start()->value()); - return r < 0 - || (r == 0 && (!start()->is_inclusive() || !end()->is_inclusive())); - } else { - return false; // open ended range or singular range don't wrap around - } - } - // Converts a wrap-around range to two non-wrap-around ranges. - // The returned ranges are not overlapping and ordered. - // Call only when is_wrap_around(). - std::pair unwrap() const { - return { - { {}, end() }, - { start(), {} } - }; - } - // the point is inside the range - // Comparator must define a total ordering on T. - template - bool contains(const T& point, Comparator&& cmp) const { - if (is_wrap_around(cmp)) { - auto unwrapped = unwrap(); - return unwrapped.first.contains(point, cmp) - || unwrapped.second.contains(point, cmp); - } else { - return !before(point, cmp) && !after(point, cmp); - } - } - // Returns true iff all values contained by other are also contained by this. - // Comparator must define a total ordering on T. - template - bool contains(const wrapping_range& other, Comparator&& cmp) const { - bool this_wraps = is_wrap_around(cmp); - bool other_wraps = other.is_wrap_around(cmp); - - if (this_wraps && other_wraps) { - return cmp(start()->value(), other.start()->value()) - <= -(!start()->is_inclusive() && other.start()->is_inclusive()) - && cmp(end()->value(), other.end()->value()) - >= (!end()->is_inclusive() && other.end()->is_inclusive()); - } - - if (!this_wraps && !other_wraps) { - return less_than_or_equal(start_bound(), other.start_bound(), cmp) - && greater_than_or_equal(end_bound(), other.end_bound(), cmp); - } - - if (other_wraps) { // && !this_wraps - return !start() && !end(); - } - - // !other_wraps && this_wraps - return (other.start() && cmp(start()->value(), other.start()->value()) - <= -(!start()->is_inclusive() && other.start()->is_inclusive())) - || (other.end() && cmp(end()->value(), other.end()->value()) - >= (!end()->is_inclusive() && other.end()->is_inclusive())); - } - // Returns ranges which cover all values covered by this range but not covered by the other range. - // Ranges are not overlapping and ordered. - // Comparator must define a total ordering on T. - template - std::vector subtract(const wrapping_range& other, Comparator&& cmp) const { - std::vector result; - std::list left; - std::list right; - - if (is_wrap_around(cmp)) { - auto u = unwrap(); - left.emplace_back(std::move(u.first)); - left.emplace_back(std::move(u.second)); - } else { - left.push_back(*this); - } - - if (other.is_wrap_around(cmp)) { - auto u = other.unwrap(); - right.emplace_back(std::move(u.first)); - right.emplace_back(std::move(u.second)); - } else { - right.push_back(other); - } - - // left and right contain now non-overlapping, ordered ranges - - while (!left.empty() && !right.empty()) { - auto& r1 = left.front(); - auto& r2 = right.front(); - if (less_than(r2.end_bound(), r1.start_bound(), cmp)) { - right.pop_front(); - } else if (less_than(r1.end_bound(), r2.start_bound(), cmp)) { - result.emplace_back(std::move(r1)); - left.pop_front(); - } else { // Overlap - auto tmp = std::move(r1); - left.pop_front(); - if (!greater_than_or_equal(r2.end_bound(), tmp.end_bound(), cmp)) { - left.push_front({bound(r2.end()->value(), !r2.end()->is_inclusive()), tmp.end()}); - } - if (!less_than_or_equal(r2.start_bound(), tmp.start_bound(), cmp)) { - left.push_front({tmp.start(), bound(r2.start()->value(), !r2.start()->is_inclusive())}); - } - } - } - - boost::copy(left, std::back_inserter(result)); - - // TODO: Merge adjacent ranges (optimization) - return result; - } - // split range in two around a split_point. split_point has to be inside the range - // split_point will belong to first range - // Comparator must define a total ordering on T. - template - std::pair, wrapping_range> split(const T& split_point, Comparator&& cmp) const { - assert(contains(split_point, std::forward(cmp))); - wrapping_range left(start(), bound(split_point)); - wrapping_range right(bound(split_point, false), end()); - return std::make_pair(std::move(left), std::move(right)); - } - // Create a sub-range including values greater than the split_point. Returns std::nullopt if - // split_point is after the end (but not included in the range, in case of wraparound ranges) - // Comparator must define a total ordering on T. - template - std::optional> split_after(const T& split_point, Comparator&& cmp) const { - if (contains(split_point, std::forward(cmp)) - && (!end() || cmp(split_point, end()->value()) != 0)) { - return wrapping_range(bound(split_point, false), end()); - } else if (end() && cmp(split_point, end()->value()) >= 0) { - // whether to return std::nullopt or the full range is not - // well-defined for wraparound ranges; we return nullopt - // if split_point is after the end. - return std::nullopt; - } else { - return *this; - } - } - template> - static std::optional::bound> transform_bound(Bound&& b, Transformer&& transformer) { - if (b) { - return { { transformer(std::forward(b).value().value()), b->is_inclusive() } }; - }; - return {}; - } - // Transforms this range into a new range of a different value type - // Supplied transformer should transform value of type T (the old type) into value of type U (the new type). - template> - wrapping_range transform(Transformer&& transformer) && { - return wrapping_range(transform_bound(std::move(_start), transformer), transform_bound(std::move(_end), transformer), _singular); - } - template> - wrapping_range transform(Transformer&& transformer) const & { - return wrapping_range(transform_bound(_start, transformer), transform_bound(_end, transformer), _singular); - } - template - bool equal(const wrapping_range& other, Comparator&& cmp) const { - return bool(_start) == bool(other._start) - && bool(_end) == bool(other._end) - && (!_start || _start->equal(*other._start, cmp)) - && (!_end || _end->equal(*other._end, cmp)) - && _singular == other._singular; - } - bool operator==(const wrapping_range& other) const { - return (_start == other._start) && (_end == other._end) && (_singular == other._singular); - } - - template - friend std::ostream& operator<<(std::ostream& out, const wrapping_range& r); -private: - friend class nonwrapping_range; -}; - -template -std::ostream& operator<<(std::ostream& out, const wrapping_range& r) { - if (r.is_singular()) { - return out << "{" << r.start()->value() << "}"; - } - - if (!r.start()) { - out << "(-inf, "; - } else { - if (r.start()->is_inclusive()) { - out << "["; - } else { - out << "("; - } - out << r.start()->value() << ", "; - } - - if (!r.end()) { - out << "+inf)"; - } else { - out << r.end()->value(); - if (r.end()->is_inclusive()) { - out << "]"; - } else { - out << ")"; - } - } - - return out; -} - -// A range which can have inclusive, exclusive or open-ended bounds on each end. -// The end bound can never be smaller than the start bound. -template -class nonwrapping_range { - template - using optional = std::optional; -public: - using bound = range_bound; - - template - using transformed_type = typename wrapping_range::template transformed_type; -private: - wrapping_range _range; -public: - nonwrapping_range(T value) - : _range(std::move(value)) - { } - nonwrapping_range() : nonwrapping_range({}, {}) { } - // Can only be called if start <= end. IDL ctor. - nonwrapping_range(optional start, optional end, bool singular = false) - : _range(std::move(start), std::move(end), singular) - { } - // Can only be called if !r.is_wrap_around(). - explicit nonwrapping_range(wrapping_range&& r) - : _range(std::move(r)) - { } - // Can only be called if !r.is_wrap_around(). - explicit nonwrapping_range(const wrapping_range& r) - : _range(r) - { } - operator wrapping_range() const & { - return _range; - } - operator wrapping_range() && { - return std::move(_range); - } - - // the point is before the range. - // Comparator must define a total ordering on T. - template - bool before(const T& point, Comparator&& cmp) const { - return _range.before(point, std::forward(cmp)); - } - // the point is after the range. - // Comparator must define a total ordering on T. - template - bool after(const T& point, Comparator&& cmp) const { - return _range.after(point, std::forward(cmp)); - } - // check if two ranges overlap. - // Comparator must define a total ordering on T. - template - bool overlaps(const nonwrapping_range& other, Comparator&& cmp) const { - // if both this and other have an open start, the two ranges will overlap. - if (!start() && !other.start()) { - return true; - } - - return wrapping_range::greater_than_or_equal(_range.end_bound(), other._range.start_bound(), cmp) - && wrapping_range::greater_than_or_equal(other._range.end_bound(), _range.start_bound(), cmp); - } - static nonwrapping_range make(bound start, bound end) { - return nonwrapping_range({std::move(start)}, {std::move(end)}); - } - static nonwrapping_range make_open_ended_both_sides() { - return {{}, {}}; - } - static nonwrapping_range make_singular(T value) { - return {std::move(value)}; - } - static nonwrapping_range make_starting_with(bound b) { - return {{std::move(b)}, {}}; - } - static nonwrapping_range make_ending_with(bound b) { - return {{}, {std::move(b)}}; - } - bool is_singular() const { - return _range.is_singular(); - } - bool is_full() const { - return _range.is_full(); - } - const optional& start() const { - return _range.start(); - } - const optional& end() const { - return _range.end(); - } - // the point is inside the range - // Comparator must define a total ordering on T. - template - bool contains(const T& point, Comparator&& cmp) const { - return !before(point, cmp) && !after(point, cmp); - } - // Returns true iff all values contained by other are also contained by this. - // Comparator must define a total ordering on T. - template - bool contains(const nonwrapping_range& other, Comparator&& cmp) const { - return wrapping_range::less_than_or_equal(_range.start_bound(), other._range.start_bound(), cmp) - && wrapping_range::greater_than_or_equal(_range.end_bound(), other._range.end_bound(), cmp); - } - // Returns ranges which cover all values covered by this range but not covered by the other range. - // Ranges are not overlapping and ordered. - // Comparator must define a total ordering on T. - template - std::vector subtract(const nonwrapping_range& other, Comparator&& cmp) const { - auto subtracted = _range.subtract(other._range, std::forward(cmp)); - return boost::copy_range>(subtracted | boost::adaptors::transformed([](auto&& r) { - return nonwrapping_range(std::move(r)); - })); - } - // split range in two around a split_point. split_point has to be inside the range - // split_point will belong to first range - // Comparator must define a total ordering on T. - template - std::pair, nonwrapping_range> split(const T& split_point, Comparator&& cmp) const { - assert(contains(split_point, std::forward(cmp))); - nonwrapping_range left(start(), bound(split_point)); - nonwrapping_range right(bound(split_point, false), end()); - return std::make_pair(std::move(left), std::move(right)); - } - // Create a sub-range including values greater than the split_point. If split_point is after - // the end, returns std::nullopt. - template - std::optional split_after(const T& split_point, Comparator&& cmp) const { - if (end() && cmp(split_point, end()->value()) >= 0) { - return std::nullopt; - } else if (start() && cmp(split_point, start()->value()) < 0) { - return *this; - } else { - return nonwrapping_range(range_bound(split_point, false), end()); - } - } - // Creates a new sub-range which is the intersection of this range and a range starting with "start". - // If there is no overlap, returns std::nullopt. - template - std::optional trim_front(std::optional&& start, Comparator&& cmp) const { - return intersection(nonwrapping_range(std::move(start), {}), cmp); - } - // Transforms this range into a new range of a different value type - // Supplied transformer should transform value of type T (the old type) into value of type U (the new type). - template> - nonwrapping_range transform(Transformer&& transformer) && { - return nonwrapping_range(std::move(_range).transform(std::forward(transformer))); - } - template> - nonwrapping_range transform(Transformer&& transformer) const & { - return nonwrapping_range(_range.transform(std::forward(transformer))); - } - template - bool equal(const nonwrapping_range& other, Comparator&& cmp) const { - return _range.equal(other._range, std::forward(cmp)); - } - bool operator==(const nonwrapping_range& other) const { - return _range == other._range; - } - // Takes a vector of possibly overlapping ranges and returns a vector containing - // a set of non-overlapping ranges covering the same values. - template - static std::vector deoverlap(std::vector ranges, Comparator&& cmp) { - auto size = ranges.size(); - if (size <= 1) { - return ranges; - } - - std::sort(ranges.begin(), ranges.end(), [&](auto&& r1, auto&& r2) { - return wrapping_range::less_than(r1._range.start_bound(), r2._range.start_bound(), cmp); - }); - - std::vector deoverlapped_ranges; - deoverlapped_ranges.reserve(size); - - auto&& current = ranges[0]; - for (auto&& r : ranges | boost::adaptors::sliced(1, ranges.size())) { - bool includes_end = wrapping_range::greater_than_or_equal(r._range.end_bound(), current._range.start_bound(), cmp) - && wrapping_range::greater_than_or_equal(current._range.end_bound(), r._range.end_bound(), cmp); - if (includes_end) { - continue; // last.start <= r.start <= r.end <= last.end - } - bool includes_start = wrapping_range::greater_than_or_equal(current._range.end_bound(), r._range.start_bound(), cmp); - if (includes_start) { - current = nonwrapping_range(std::move(current.start()), std::move(r.end())); - } else { - deoverlapped_ranges.emplace_back(std::move(current)); - current = std::move(r); - } - } - - deoverlapped_ranges.emplace_back(std::move(current)); - return deoverlapped_ranges; - } - -private: - // These private functions optimize the case where a sequence supports the - // lower and upper bound operations more efficiently, as is the case with - // some boost containers. - struct std_ {}; - struct built_in_ : std_ {}; - - template().lower_bound(std::declval(), std::declval()))> - typename std::remove_reference::type::const_iterator do_lower_bound(const T& value, Range&& r, LessComparator&& cmp, built_in_) const { - return r.lower_bound(value, std::forward(cmp)); - } - - template().upper_bound(std::declval(), std::declval()))> - typename std::remove_reference::type::const_iterator do_upper_bound(const T& value, Range&& r, LessComparator&& cmp, built_in_) const { - return r.upper_bound(value, std::forward(cmp)); - } - - template - typename std::remove_reference::type::const_iterator do_lower_bound(const T& value, Range&& r, LessComparator&& cmp, std_) const { - return std::lower_bound(r.begin(), r.end(), value, std::forward(cmp)); - } - - template - typename std::remove_reference::type::const_iterator do_upper_bound(const T& value, Range&& r, LessComparator&& cmp, std_) const { - return std::upper_bound(r.begin(), r.end(), value, std::forward(cmp)); - } -public: - // Return the lower bound of the specified sequence according to these bounds. - template - typename std::remove_reference::type::const_iterator lower_bound(Range&& r, LessComparator&& cmp) const { - return start() - ? (start()->is_inclusive() - ? do_lower_bound(start()->value(), std::forward(r), std::forward(cmp), built_in_()) - : do_upper_bound(start()->value(), std::forward(r), std::forward(cmp), built_in_())) - : std::cbegin(r); - } - // Return the upper bound of the specified sequence according to these bounds. - template - typename std::remove_reference::type::const_iterator upper_bound(Range&& r, LessComparator&& cmp) const { - return end() - ? (end()->is_inclusive() - ? do_upper_bound(end()->value(), std::forward(r), std::forward(cmp), built_in_()) - : do_lower_bound(end()->value(), std::forward(r), std::forward(cmp), built_in_())) - : (is_singular() - ? do_upper_bound(start()->value(), std::forward(r), std::forward(cmp), built_in_()) - : std::cend(r)); - } - // Returns a subset of the range that is within these bounds. - template - boost::iterator_range::type::const_iterator> - slice(Range&& range, LessComparator&& cmp) const { - return boost::make_iterator_range(lower_bound(range, cmp), upper_bound(range, cmp)); - } - - // Returns the intersection between this range and other. - template - std::optional intersection(const nonwrapping_range& other, Comparator&& cmp) const { - auto p = std::minmax(_range, other._range, [&cmp] (auto&& a, auto&& b) { - return wrapping_range::less_than(a.start_bound(), b.start_bound(), cmp); - }); - if (wrapping_range::greater_than_or_equal(p.first.end_bound(), p.second.start_bound(), cmp)) { - auto end = std::min(p.first.end_bound(), p.second.end_bound(), [&cmp] (auto&& a, auto&& b) { - return !wrapping_range::greater_than_or_equal(a, b, cmp); - }); - return nonwrapping_range(p.second.start(), end.b); - } - return {}; - } - - template - friend std::ostream& operator<<(std::ostream& out, const nonwrapping_range& r); -}; - -template -std::ostream& operator<<(std::ostream& out, const nonwrapping_range& r) { - return out << r._range; -} - -template -using range = wrapping_range; - -template typename T, typename U> -concept Range = std::is_same, wrapping_range>::value || std::is_same, nonwrapping_range>::value; - -// Allow using range in a hash table. The hash function 31 * left + -// right is the same one used by Cassandra's AbstractBounds.hashCode(). -namespace std { - -template -struct hash> { - using argument_type = wrapping_range; - using result_type = decltype(std::hash()(std::declval())); - result_type operator()(argument_type const& s) const { - auto hash = std::hash(); - auto left = s.start() ? hash(s.start()->value()) : 0; - auto right = s.end() ? hash(s.end()->value()) : 0; - return 31 * left + right; - } -}; - -template -struct hash> { - using argument_type = nonwrapping_range; - using result_type = decltype(std::hash()(std::declval())); - result_type operator()(argument_type const& s) const { - return hash>()(s); - } -}; - -} +template typename T, typename U> +concept Range = Interval;