/* * 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 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; } }; // A range which can have inclusive, exclusive or open-ended bounds on each end. template class range { template using optional = std::experimental::optional; public: using bound = range_bound; private: optional _start; optional _end; bool _singular; public: range(optional start, optional 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& 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 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 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& 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 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 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, range> split(const T& split_point, Comparator&& cmp) const { assert(contains(split_point, std::forward(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 range split_after(const T& split_point, Comparator&& cmp) const { assert(contains(split_point, std::forward(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::type> range transform(Transformer&& transformer) && { auto t = [&transformer] (std::experimental::optional&& b) -> std::experimental::optional::bound> { if (!b) { return {}; } return { { transformer(std::move(*b).value()), b->is_inclusive() } }; }; return range(t(std::move(_start)), t(std::move(_end)), _singular); } template 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 static std::vector> deoverlap(std::vector> 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::less_than(r1.start_bound(), r2.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 = 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(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 friend std::ostream& operator<<(std::ostream& out, const range& r); }; template std::ostream& operator<<(std::ostream& out, const 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; } // 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 = 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; } }; }