This patch makes it so that the template arguments of range<T>::transform are more easily deducible by the compiler. Signed-off-by: Duarte Nunes <duarte@scylladb.com>
460 lines
17 KiB
C++
460 lines
17 KiB
C++
/*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <experimental/optional>
|
|
#include <iostream>
|
|
#include <boost/range/algorithm/copy.hpp>
|
|
#include <boost/range/adaptor/sliced.hpp>
|
|
|
|
template<typename T>
|
|
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<typename Comparator>
|
|
bool equal(const range_bound& other, Comparator&& cmp) const {
|
|
return _inclusive == other._inclusive && cmp(_value, other._value) == 0;
|
|
}
|
|
};
|
|
|
|
// A range which can have inclusive, exclusive or open-ended bounds on each end.
|
|
template<typename T>
|
|
class range {
|
|
template <typename U>
|
|
using optional = std::experimental::optional<U>;
|
|
public:
|
|
using bound = range_bound<T>;
|
|
private:
|
|
optional<bound> _start;
|
|
optional<bound> _end;
|
|
bool _singular;
|
|
public:
|
|
range(optional<bound> start, optional<bound> end, bool singular = false)
|
|
: _start(std::move(start))
|
|
, _end(std::move(end))
|
|
, _singular(singular)
|
|
{ }
|
|
range(T value)
|
|
: _start(bound(std::move(value), true))
|
|
, _end()
|
|
, _singular(true)
|
|
{ }
|
|
range() : range({}, {}) {}
|
|
private:
|
|
// Bound wrappers for compile-time dispatch and safety.
|
|
struct start_bound_ref { const optional<bound>& b; };
|
|
struct end_bound_ref { const optional<bound>& b; };
|
|
|
|
start_bound_ref start_bound() const { return { start() }; }
|
|
end_bound_ref end_bound() const { return { end() }; }
|
|
|
|
template<typename Comparator>
|
|
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<typename Comparator>
|
|
static bool less_than(end_bound_ref end, start_bound_ref start, Comparator&& cmp) {
|
|
return !greater_than_or_equal(end, start, cmp);
|
|
}
|
|
|
|
template<typename Comparator>
|
|
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<typename Comparator>
|
|
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<typename Comparator>
|
|
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<typename Comparator>
|
|
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<typename Comparator>
|
|
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<typename Comparator>
|
|
bool overlaps(const 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 range make(bound start, bound end) {
|
|
return range({std::move(start)}, {std::move(end)});
|
|
}
|
|
static range make_open_ended_both_sides() {
|
|
return {{}, {}};
|
|
}
|
|
static range make_singular(T value) {
|
|
return {std::move(value)};
|
|
}
|
|
static range make_starting_with(bound b) {
|
|
return {{std::move(b)}, {}};
|
|
}
|
|
static 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<bound>& start() const {
|
|
return _start;
|
|
}
|
|
const optional<bound>& 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<typename Comparator>
|
|
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<range, range> unwrap() const {
|
|
return {
|
|
{ {}, end() },
|
|
{ start(), {} }
|
|
};
|
|
}
|
|
// the point is inside the range
|
|
// Comparator must define a total ordering on T.
|
|
template<typename Comparator>
|
|
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<typename Comparator>
|
|
bool contains(const 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<typename Comparator>
|
|
std::vector<range> subtract(const range& other, Comparator&& cmp) const {
|
|
std::vector<range> result;
|
|
std::list<range> left;
|
|
std::list<range> 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<typename Comparator>
|
|
std::pair<range<T>, range<T>> split(const T& split_point, Comparator&& cmp) const {
|
|
assert(contains(split_point, std::forward<Comparator>(cmp)));
|
|
range left(start(), bound(split_point));
|
|
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. split_point has to be inside the range
|
|
// Comparator must define a total ordering on T.
|
|
template<typename Comparator>
|
|
range<T> split_after(const T& split_point, Comparator&& cmp) const {
|
|
assert(contains(split_point, std::forward<Comparator>(cmp)));
|
|
return range(bound(split_point, false), end());
|
|
}
|
|
// 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<typename Transformer, typename U = typename std::result_of<Transformer(T)>::type>
|
|
range<U> transform(Transformer&& transformer) && {
|
|
auto t = [&transformer] (std::experimental::optional<bound>&& b)
|
|
-> std::experimental::optional<typename range<U>::bound>
|
|
{
|
|
if (!b) {
|
|
return {};
|
|
}
|
|
return { { transformer(std::move(*b).value()), b->is_inclusive() } };
|
|
};
|
|
return range<U>(t(std::move(_start)), t(std::move(_end)), _singular);
|
|
}
|
|
template<typename Comparator>
|
|
bool equal(const 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 range& other) const {
|
|
return (_start == other._start) && (_end == other._end) && (_singular == other._singular);
|
|
}
|
|
|
|
// Takes a vector of possibly overlapping ranges and returns a vector containing
|
|
// a set of non-overlapping ranges covering the same values.
|
|
template<typename Comparator>
|
|
static std::vector<range<T>> deoverlap(std::vector<range<T>> ranges, Comparator&& cmp) {
|
|
auto size = ranges.size();
|
|
if (size <= 1) {
|
|
return ranges;
|
|
}
|
|
|
|
for (auto it = ranges.begin(); it != ranges.end(); ++it) {
|
|
if ((*it).is_wrap_around(cmp)) {
|
|
auto&& unwrapped = it->unwrap();
|
|
it = ranges.erase(it);
|
|
it = ranges.emplace(it, std::move(unwrapped.first));
|
|
it = ranges.emplace(it, std::move(unwrapped.second));
|
|
}
|
|
}
|
|
|
|
std::sort(ranges.begin(), ranges.end(), [&](auto&& r1, auto&& r2) {
|
|
return range<T>::less_than(r1.start_bound(), r2.start_bound(), cmp);
|
|
});
|
|
|
|
std::vector<range<T>> deoverlapped_ranges;
|
|
deoverlapped_ranges.reserve(size);
|
|
|
|
auto&& current = ranges[0];
|
|
for (auto&& r : ranges | boost::adaptors::sliced(1, ranges.size())) {
|
|
bool includes_end = greater_than_or_equal(r.end_bound(), current.start_bound(), cmp)
|
|
&& greater_than_or_equal(current.end_bound(), r.end_bound(), cmp);
|
|
if (includes_end) {
|
|
continue; // last.start <= r.start <= r.end <= last.end
|
|
}
|
|
bool includes_start = greater_than_or_equal(current.end_bound(), r.start_bound(), cmp);
|
|
if (includes_start) {
|
|
current = range<T>(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;
|
|
}
|
|
|
|
template<typename U>
|
|
friend std::ostream& operator<<(std::ostream& out, const range<U>& r);
|
|
};
|
|
|
|
template<typename U>
|
|
std::ostream& operator<<(std::ostream& out, const range<U>& 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;
|
|
}
|
|
|
|
// Allow using range<T> in a hash table. The hash function 31 * left +
|
|
// right is the same one used by Cassandra's AbstractBounds.hashCode().
|
|
namespace std {
|
|
|
|
template<typename T>
|
|
struct hash<range<T>> {
|
|
using argument_type = range<T>;
|
|
using result_type = decltype(std::hash<T>()(std::declval<T>()));
|
|
result_type operator()(argument_type const& s) const {
|
|
auto hash = std::hash<T>();
|
|
auto left = s.start() ? hash(s.start()->value()) : 0;
|
|
auto right = s.end() ? hash(s.end()->value()) : 0;
|
|
return 31 * left + right;
|
|
}
|
|
};
|
|
|
|
}
|