blob: c223c2d9fc76186de75037391b8e9396fdb8b917 [file] [log] [blame]
// SPDX-License-Identifier: Apache-2.0
// Copyright 2014 Network Geographics
/** @file
Support classes for creating intervals of numeric values.
The template class can be used directly via a @c typedef or
used as a base class if additional functionality is required.
*/
#pragma once
#include <algorithm>
#include <limits>
#include <functional>
#include "swoc/swoc_version.h"
#include "swoc/swoc_meta.h"
#include "swoc/RBTree.h"
#include "swoc/MemArena.h"
namespace swoc { inline namespace SWOC_VERSION_NS {
namespace detail {
/// A set of metafunctions to get extrema from a metric type.
/// These probe for a static member and falls back to @c std::numeric_limits.
/// @{
/** Maximum value.
*
* @tparam M Metric type.
* @return Maximum value for @a M.
*
* Use @c std::numeric_limits.
*/
template <typename M>
constexpr auto
maximum(meta::CaseTag<0>) -> M {
return std::numeric_limits<M>::max();
}
/** Maximum value.
*
* @tparam M Metric type.
* @return Maximum value for @a M.
*
* Use @c M::MAX
*/
template <typename M>
constexpr auto
maximum(meta::CaseTag<1>) -> decltype(M::MAX) {
return M::MAX;
}
/** Maximum value.
*
* @tparam M Metric type.
* @return Maximum value for @a M.
*/
template <typename M>
constexpr M
maximum() {
return maximum<M>(meta::CaseArg);
}
/** Minimum value.
*
* @tparam M Metric type.
* @return Minimum value for @a M
*
* Use @c std::numeric_limits
*/
template <typename M>
constexpr auto
minimum(meta::CaseTag<0>) -> M {
return std::numeric_limits<M>::min();
}
/** Minimum value.
*
* @tparam M Metric type.
* @return Minimum value for @a M
*
* Use @c M::MIN
*/
template <typename M>
constexpr auto
minimum(meta::CaseTag<1>) -> decltype(M::MIN) {
return M::MIN;
}
/** Minimum value.
*
* @tparam M Metric type.
* @return Minimum value for @a M
*/
template <typename M>
constexpr M
minimum() {
return minimum<M>(meta::CaseArg);
}
/// @}
} // namespace detail
/// Relationship between two intervals.
enum class DiscreteRangeRelation : uint8_t {
NONE, ///< No common elements.
EQUAL, ///< Identical ranges.
SUBSET, ///< All elements in LHS are also in RHS.
SUPERSET, ///< Every element in RHS is in LHS.
OVERLAP, ///< There exists at least one element in both LHS and RHS.
ADJACENT ///< The two intervals are adjacent and disjoint.
};
/// Relationship between one edge of an interval and the "opposite" edge of another.
enum class DiscreteRangeEdgeRelation : uint8_t {
NONE, ///< Edge is on the opposite side of the relating edge.
GAP, ///< There is a gap between the edges.
ADJ, ///< The edges are adjacent.
OVLP, ///< Edge is inside interval.
};
/** A range over a discrete finite value metric.
@tparam T The type for the range values.
The template argument @a T is presumed to
- be completely ordered.
- have prefix increment and decrement operators
- equality operator
- have value semantics
- have minimum and maximum values either
- members @c MIN and @c MAX that define static instances
- @c std::numeric_limits<T> support.
The interval is always an inclusive (closed) contiguous interval,
defined by the minimum and maximum values contained in the interval.
An interval can be @em empty and contain no values. This is the state
of a default constructed interval.
*/
template <typename T> class DiscreteRange {
using self_type = DiscreteRange;
protected:
T _min; ///< The minimum value in the interval
T _max; ///< the maximum value in the interval
public:
using metric_type = T; ///< Export metric type.
using Relation = DiscreteRangeRelation; ///< Import type for convenience.
using EdgeRelation = DiscreteRangeEdgeRelation; ///< Import type for convenience.
/** Default constructor.
* An invalid (empty) range is constructed.
*/
constexpr DiscreteRange() : _min(detail::maximum<T>()), _max(detail::minimum<T>()) {}
/** Construct a singleton range.
* @param value Single value to be contained by the interval.
*
* @note Not marked @c explicit and so serves as a conversion from scalar values to an interval.
*/
constexpr DiscreteRange(T const &value) : _min(value), _max(value){};
/** Constructor.
*
* @param min Minimum value in the interval.
* @param max Maximum value in the interval.
*/
constexpr DiscreteRange(T const &min, T const &max) : _min(min), _max(max) {}
~DiscreteRange() = default;
/** Check if there are no values in the range.
*
* @return @c true if the range is empty (contains no values), @c false if it contains at least
* one value.
*/
bool empty() const;
/** Update the range.
*
* @param min New minimum value.
* @param max New maximum value.
* @return @a this.
*/
self_type &assign(metric_type const &min, metric_type const &max);
/** Update the range.
*
* @param value The new minimum and maximum value.
* @return @a this.
*
* The range will contain the single value @a value.
*/
self_type &assign(metric_type const &value);
/** Update the minimum value.
*
* @param min The new minimum value.
* @return @a this.
*
* @note No checks are done - this can result in an empty range.
*/
self_type &assign_min(metric_type const &min);
/** Update the maximum value.
*
* @param max The new maximum value.
* @return @a this.
*
* @note No checks are done - this can result in an empty range.
*/
self_type &assign_max(metric_type const &max);
/** Decrement the maximum value.
*
* @return @a this.
*
* @note No checks are done, the caller must ensure it is valid to decremented the current maximum.
*/
self_type &clip_max();
/** Minimum value.
* @return the minimum value in the range.
* @note The return value is unspecified if the interval is empty.
*/
metric_type const &min() const;
/** Maximum value.
* @return The maximum value in the range.
* @note The return value is unspecified if the interval is empty.
*/
metric_type const &max() const;
/// Equality.
bool operator==(self_type const &that) const;
/// Inequality.
bool operator!=(self_type const &that) const;
/** Check if a value is in @a this range.
*
* @param value Metric value to check.
* @return @c true if @a value is in the range, @c false if not.
*/
bool contains(metric_type const &value) const;
/** Logical intersection.
* @return @c true if there is at least one common value in the two intervals, @c false otherwise.
*/
bool has_intersection_with(self_type const &that) const;
/** Compute the intersection of two intervals
*
* @return The interval consisting of values that are contained by both intervals. The return range
* is empty if the intervals are disjoint.
*
* @internal Co-variant
*/
self_type intersection(self_type const &that) const;
/** Test for adjacency.
* @return @c true if the intervals are adjacent.
* @note Only disjoint intervals can be adjacent.
*/
bool is_adjacent_to(self_type const &that) const;
/** Lower adjacency.
*
* @param that Range to check for adjacency.
* @return @c true if @a this and @ta that are adjacent and @a this is less than @a that.
*/
bool is_left_adjacent_to(self_type const &that) const;
/** Valid union.
*
* @param that Range to compare.
*
* @return @a true if the hull of @a this and @a that contains only elements that are also in
* @a this or @a that.
*/
bool has_union(self_type const &that) const;
/** Superset test.
*
* @return @c true if every value in @c that is also in @c this.
*/
bool is_superset_of(self_type const &that) const;
/** Subset test.
*
* @return @c true if every value in @c this is also in @c that.
*/
bool is_subset_of(self_type const &that) const;
/** Strict superset test.
*
* @return @c true if @a this contains every value in @a this and @a this has at least one value not
* in @a that.
*/
bool is_strict_superset_of(self_type const &that) const;
/** Strict subset test.
*
* @return @c true if @a that contains every value in @a this and @a that has at least one value not
* in @a this.
*/
bool is_strict_subset_of(self_type const &that) const;
/** Generic relationship.
*
* @return The relationship between @a this and @a that.
*/
Relation relationship(self_type const &that) const;
/** Determine the relationship of the left edge of @a that with @a this.
*
* @param that The other interval.
* @return The edge relationship.
*
* This checks the right edge of @a this against the left edge of @a that.
*
* - GAP: @a that left edge is right of @a this.
* - ADJ: @a that left edge is right adjacent to @a this.
* - OVLP: @a that left edge is inside @a this.
* - NONE: @a that left edge is left of @a this.
*/
EdgeRelation left_edge_relationship(self_type const &that) const;
/** Convex hull.
* @return The smallest interval that is a superset of @c this and @a that.
* @internal Co-variant
*/
self_type hull(self_type const &that) const;
//! Check if the interval is exactly one element.
bool is_singleton() const;
/** Test for empty, operator form.
@return @c true if the interval is empty, @c false otherwise.
*/
bool operator!() const;
/** Test for non-empty.
*
* @return @c true if there values in the range, @c false if no values in the range.
*/
explicit operator bool() const;
/** Maximality.
* @return @c true if this range contains every value.
*/
bool is_maximal() const;
/** Clip interval.
* Remove all element in @c this interval not in @a that interval.
*/
self_type &operator&=(self_type const &that);
/** Convex hull.
* Minimally extend @a this to cover all elements in @c this and @a that.
* @return @a this.
*/
self_type &operator|=(self_type const &that);
/// Make the range empty.
self_type &clear();
/** Functor for lexicographic ordering.
If, for some reason, an interval needs to be put in a container
that requires a strict weak ordering, the default @c operator @c < will
not work. Instead, this functor should be used as the comparison
functor. E.g.
@code
typedef std::set<Interval<T>, Interval<T>::lexicographic_order> container;
@endcode
@note Lexicographic ordering is a standard tuple ordering where the
order is determined by pairwise comparing the elements of both tuples.
The first pair of elements that are not equal determine the ordering
of the overall tuples.
*/
struct lexicographic_order {
using first_argument_type = self_type;
using second_argument_type = self_type;
using result_type = bool;
//! Functor operator.
bool operator()(self_type const &lhs, self_type const &rhs) const;
};
};
template <typename T>
bool
DiscreteRange<T>::operator==(DiscreteRange::self_type const &that) const {
return _min == that._min && _max == that._max;
}
template <typename T>
bool
DiscreteRange<T>::operator!=(DiscreteRange::self_type const &that) const {
return _min != that._min | _max != that._max;
}
template <typename T>
auto
DiscreteRange<T>::clip_max() -> self_type & {
--_max;
return *this;
}
template <typename T>
bool
DiscreteRange<T>::operator!() const {
return _min > _max;
}
template <typename T> DiscreteRange<T>::operator bool() const {
return _min <= _max;
}
template <typename T>
bool
DiscreteRange<T>::contains(metric_type const &value) const {
return _min <= value && value <= _max;
}
template <typename T>
auto
DiscreteRange<T>::clear() -> self_type & {
_min = detail::maximum<T>();
_max = detail::minimum<T>();
return *this;
}
template <typename T>
bool
DiscreteRange<T>::lexicographic_order::operator()(DiscreteRange::self_type const &lhs, DiscreteRange::self_type const &rhs) const {
return lhs._min == rhs._min ? lhs._max < rhs._max : lhs._min < rhs._min;
}
template <typename T>
DiscreteRange<T> &
DiscreteRange<T>::assign(metric_type const &min, metric_type const &max) {
_min = min;
_max = max;
return *this;
}
template <typename T>
DiscreteRange<T>
DiscreteRange<T>::hull(DiscreteRange::self_type const &that) const {
// need to account for invalid ranges.
return !*this ? that : !that ? *this : self_type(std::min(_min, that._min), std::max(_max, that._max));
}
template <typename T>
auto
DiscreteRange<T>::left_edge_relationship(self_type const &that) const -> EdgeRelation {
if (_max < that._max) {
return ++metric_type(_max) < that._max ? EdgeRelation::GAP : EdgeRelation::ADJ;
}
return _min >= that._min ? EdgeRelation::NONE : EdgeRelation::OVLP;
}
template <typename T>
auto
DiscreteRange<T>::relationship(self_type const &that) const -> Relation {
Relation retval = Relation::NONE;
if (this->has_intersection_with(that)) {
if (*this == that)
retval = Relation::EQUAL;
else if (this->is_subset_of(that))
retval = Relation::SUBSET;
else if (this->is_superset_of(that))
retval = Relation::SUPERSET;
else
retval = Relation::OVERLAP;
} else if (this->is_adjacent_to(that)) {
retval = Relation::ADJACENT;
}
return retval;
}
template <typename T>
DiscreteRange<T> &
DiscreteRange<T>::assign(metric_type const &singleton) {
_min = singleton;
_max = singleton;
return *this;
}
template <typename T>
DiscreteRange<T> &
DiscreteRange<T>::assign_min(metric_type const &min) {
_min = min;
return *this;
}
template <typename T>
bool
DiscreteRange<T>::is_singleton() const {
return _min == _max;
}
template <typename T>
bool
DiscreteRange<T>::empty() const {
return _min > _max;
}
template <typename T>
bool
DiscreteRange<T>::is_maximal() const {
return _min == detail::minimum<T>() && _max == detail::maximum<T>();
}
template <typename T>
bool
DiscreteRange<T>::is_strict_superset_of(DiscreteRange::self_type const &that) const {
return (_min < that._min && that._max <= _max) || (_min <= that._min && that._max < _max);
}
template <typename T>
DiscreteRange<T> &
DiscreteRange<T>::operator|=(DiscreteRange::self_type const &that) {
if (!*this) {
*this = that;
} else if (that) {
if (that._min < _min) {
_min = that._min;
}
if (that._max > _max) {
_max = that._max;
}
}
return *this;
}
template <typename T>
DiscreteRange<T> &
DiscreteRange<T>::assign_max(metric_type const &max) {
_max = max;
return *this;
}
template <typename T>
bool
DiscreteRange<T>::is_strict_subset_of(DiscreteRange::self_type const &that) const {
return that.is_strict_superset_of(*this);
}
template <typename T>
bool
DiscreteRange<T>::is_subset_of(DiscreteRange::self_type const &that) const {
return that.is_superset_of(*this);
}
template <typename T>
T const &
DiscreteRange<T>::min() const {
return _min;
}
template <typename T>
T const &
DiscreteRange<T>::max() const {
return _max;
}
template <typename T>
bool
DiscreteRange<T>::has_union(DiscreteRange::self_type const &that) const {
return this->has_intersection_with(that) || this->is_adjacent_to(that);
}
template <typename T>
DiscreteRange<T> &
DiscreteRange<T>::operator&=(DiscreteRange::self_type const &that) {
*this = this->intersection(that);
return *this;
}
template <typename T>
bool
DiscreteRange<T>::has_intersection_with(DiscreteRange::self_type const &that) const {
return (that._min <= _min && _min <= that._max) || (_min <= that._min && that._min <= _max);
}
template <typename T>
bool
DiscreteRange<T>::is_superset_of(DiscreteRange::self_type const &that) const {
return _min <= that._min && that._max <= _max;
}
template <typename T>
bool
DiscreteRange<T>::is_adjacent_to(DiscreteRange::self_type const &that) const {
return this->is_left_adjacent_to(that) || that.is_left_adjacent_to(*this);
}
template <typename T>
bool
DiscreteRange<T>::is_left_adjacent_to(DiscreteRange::self_type const &that) const {
/* Need to be careful here. We don't know much about T and we certainly don't know if "t+1"
* even compiles for T. We do require the increment operator, however, so we can use that on a
* copy to get the equivalent of t+1 for adjacency testing. We must also handle the possibility
* T has a modulus and not depend on ++t > t always being true. However, we know that if t1 >
* t0 then ++t0 > t0.
*/
metric_type max(_max); // some built in integer types don't support increment on rvalues.
return _max < that._min && ++max == that._min;
}
template <typename T>
DiscreteRange<T>
DiscreteRange<T>::intersection(DiscreteRange::self_type const &that) const {
return {std::max(_min, that._min), std::min(_max, that._max)};
}
template <typename T>
bool
operator==(DiscreteRange<T> const &lhs, DiscreteRange<T> const &rhs) {
return lhs.min() == rhs.min() && lhs.max() == rhs.max();
}
/** Inequality.
Two intervals are equal if their min and max values are equal.
@relates DiscreteRange
*/
template <typename T>
bool
operator!=(DiscreteRange<T> const &lhs, DiscreteRange<T> const &rhs) {
return !(lhs == rhs);
}
/** Operator form of logical intersection test for two intervals.
@return @c true if there is at least one common value in the
two intervals, @c false otherwise.
@note Yeah, a bit ugly, using an operator that is not standardly
boolean but
- There don't seem to be better choices (&&,|| not good)
- The assymmetry between intersection and union makes for only three natural operators
- ^ at least looks like "intersects"
@relates DiscreteRange
*/
template <typename T>
bool
operator^(DiscreteRange<T> const &lhs, DiscreteRange<T> const &rhs) {
return lhs.has_intersection_with(rhs);
}
/** Containment ordering.
@return @c true if @c this is a strict subset of @a rhs.
@note Equivalent to @c is_strict_subset.
@relates DiscreteRange
*/
template <typename T>
inline bool
operator<(DiscreteRange<T> const &lhs, DiscreteRange<T> const &rhs) {
return rhs.is_strict_superset_of(lhs);
}
/** Containment ordering.
@return @c true if @c this is a subset of @a rhs.
@note Equivalent to @c is_subset.
@relates DiscreteRange
*/
template <typename T>
inline bool
operator<=(DiscreteRange<T> const &lhs, DiscreteRange<T> const &rhs) {
return rhs.is_superset_of(lhs);
}
/** Containment ordering.
@return @c true if @c this is a strict superset of @a rhs.
@note Equivalent to @c is_strict_superset.
@relates DiscreteRange
*/
template <typename T>
inline bool
operator>(DiscreteRange<T> const &lhs, DiscreteRange<T> const &rhs) {
return lhs.is_strict_superset_of(rhs);
}
/** Containment ordering.
@return @c true if @c this is a superset of @a rhs.
@note Equivalent to @c is_superset.
@relates DiscreteRange
*/
template <typename T>
inline bool
operator>=(DiscreteRange<T> const &lhs, DiscreteRange<T> const &rhs) {
return lhs.is_superset_of(rhs);
}
/** A space for a discrete @c METRIC.
*
* @tparam METRIC Value type for the space.
* @tparam PAYLOAD Data stored with values in the space.
*
* This is a range based mapping of all values in @c METRIC (the "space") to @c PAYLOAD.
*
* @c PAYLOAD is presumed to be relatively cheap to construct and copy.
*
* @c METRIC must be
* - discrete and finite valued type with increment and decrement operations.
*/
template <typename METRIC, typename PAYLOAD> class DiscreteSpace {
using self_type = DiscreteSpace;
protected:
using metric_type = METRIC; ///< Export.
using payload_type = PAYLOAD; ///< Export.
using range_type = DiscreteRange<METRIC>;
/// A node in the range tree.
class Node : public detail::RBNode {
using self_type = Node; ///< Self reference type.
using super_type = detail::RBNode; ///< Parent class.
friend class DiscreteSpace;
range_type _range; ///< Range covered by this node (in the IntrusiveDList).
range_type _hull; ///< Range covered by subtree rooted at this node.
PAYLOAD _payload{}; ///< Default constructor, should zero init if @c PAYLOAD is a pointer.
public:
/// Linkage for @c IntrusiveDList.
using Linkage = swoc::IntrusiveLinkageRebind<self_type, super_type::Linkage>;
Node() = default; ///< Construct empty node.
/// Construct from @a range and @a payload.
Node(range_type const &range, PAYLOAD const &payload) : _range(range), _payload(payload) {}
/// Construct from two metrics and a payload
Node(METRIC const &min, METRIC const &max, PAYLOAD const &payload) : _range(min, max), _payload(payload) {}
/// @return The payload in the node.
PAYLOAD &payload();
/** Set the @a range of a node.
*
* @param range Range to use.
* @return @a this
*/
self_type &assign(range_type const &range);
/** Set the @a payload for @a this node.
*
* @param payload Payload to use.
* @return @a this
*/
self_type &assign(PAYLOAD const &payload);
range_type const &
range() const {
return _range;
}
self_type &
assign_min(METRIC const &m, bool update_tree = true) {
_range.assign_min(m);
if (update_tree)
{
this->ripple_structure_fixup();
}
return *this;
}
self_type &
assign_max(METRIC const &m, bool update_tree = true) {
_range.assign_max(m);
if (update_tree)
{
this->ripple_structure_fixup();
}
return *this;
}
METRIC const &
min() const {
return _range.min();
}
METRIC const &
max() const {
return _range.max();
}
void structure_fixup() override;
self_type *
left() {
return static_cast<self_type *>(_left);
}
self_type *
right() {
return static_cast<self_type *>(_right);
}
};
using Direction = typename Node::Direction;
Node *_root = nullptr; ///< Root node.
IntrusiveDList<typename Node::Linkage> _list; ///< In order list of nodes.
swoc::MemArena _arena{4000}; ///< Memory Storage.
swoc::FixedArena<Node> _fa{_arena}; ///< Node allocator and free list.
// Utility methods to avoid having casts scattered all over.
Node *
prev(Node *n) {
return Node::Linkage::prev_ptr(n);
}
Node *
next(Node *n) {
return Node::Linkage::next_ptr(n);
}
Node *
left(Node *n) {
return static_cast<Node *>(n->_left);
}
Node *
right(Node *n) {
return static_cast<Node *>(n->_right);
}
public:
using iterator = typename decltype(_list)::iterator;
using const_iterator = typename decltype(_list)::const_iterator;
DiscreteSpace() = default;
~DiscreteSpace();
/** Mark ranges in one operation.
*
* @param marks Vector of ranges and payloads to mark.
* @param is_sorted @c true if input is sorted, @c false if not. Assumes not sorted.
* @return @a this
*/
self_type &mark_bulk(std::vector<std::pair<range_type, PAYLOAD>> &marks, bool is_sorted = false);
/** Mark ranges in one operation.
*
* @param start Pointer to the first range/payload pair.
* @param n Number of pairs.
* @param is_sorted @c true if input is sorted, @c false if not. Assumes not sorted.
* @return @a this
*/
self_type &mark_bulk(std::pair<range_type, PAYLOAD>* start, size_t n, bool is_sorted = false);
/** Set the @a payload for a @a range
*
* @param range Range to mark.
* @param payload Payload to set.
* @param update_tree @c true to update the RBTree structure.
* @return @a this
*
* Values in @a range are set to @a payload regardless of the current state.
*/
self_type &mark(range_type const &range, PAYLOAD const &payload, bool update_tree = true);
/** Erase a @a range.
*
* @param range Range to erase.
* @return @a this
*
* All values in @a range are removed from the space.
*/
self_type &erase(range_type const &range);
/** Blend a @a color to a @a range.
*
* @tparam F Functor to blend payloads.
* @tparam U type to blend in to payloads.
* @param range Range for blending.
* @param color Payload to blend.
* @param blender Functor to compute blended color.
* @return @a this
*
* @a color is blended to values in @a range. If an address in @a range does not have a payload,
* its payload is set a default constructed @c PAYLOAD blended with @a color. If such an address
* does have a payload, @a color is blended in to that payload using @a blender. The existing color
* is passed as the first argument and @a color as the second argument. The functor is expected to
* update the first argument to be the blended color. The function must return a @c bool to
* indicate whether the blend resulted in a valid color. If @c false is returned, the blended
* region is removed from the space.
*/
template <typename F, typename U = PAYLOAD> self_type &blend(range_type const &range, U const &color, F &&blender);
/** Fill @a range with @a payload.
*
* @param range Range to fill.
* @param payload Payload to use.
* @return @a this
*
* Values in @a range that do not have a payload are set to @a payload. Values in the space are
* not changed.
*/
self_type &fill(range_type const &range, PAYLOAD const &payload);
/** Find the payload at @a metric.
*
* @param metric The metric for which to search.
* @return An iterator for the item or the @c end iterator if not.
*/
iterator find(METRIC const &metric);
/** Find the payload at @a metric.
*
* @param metric The metric for which to search.
* @return An iterator for the item or the @c end iterator if not.
*/
const_iterator find(METRIC const &metric) const;
/** Lower bound.
*
* @param m Search value.
* @return Rightmost range that starts at or before @a m.
*/
iterator lower_bound(METRIC const &m);
/** Upper bound.
*
* @param m search value
* @return Leftmost range that starts after @a m.
*/
iterator upper_bound(METRIC const &m);
/** Intersection of @a range with container.
*
* @param range Search range.
* @return Iterator pair that contains all ranges that intersect with @a range.
*
* @see lower_bound
* @see upper_bound
*/
std::pair<iterator, iterator> intersection(range_type const &range);
/// @return The number of distinct ranges.
size_t count() const;
/// @return @c true if there are no ranges in the container, @c false otherwise.
bool empty() const;
/// @return Iterator for the first range.
iterator
begin() {
return _list.begin();
}
/// @return Iterator past the last node.
iterator
end() {
return _list.end();
}
/// @return Iterator for the first range.
const_iterator
begin() const {
return _list.begin();
}
/// @return Iterator past the last node.
const_iterator
end() const {
return _list.end();
}
/// Remove all ranges.
void clear();
protected:
/** Find the lower bounding node.
*
* @param target Search value.
* @return The rightmost range that starts at or before @a target, or @c nullptr if all ranges start
* after @a target.
*/
Node *lower_node(METRIC const &target);
/** Find the upper bound node.
*
* @param target Search value.
* @return The leftmoswt range that starts after @a target, or @c nullptr if all ranges start
* before @a target.
*/
Node *upper_node(METRIC const &target);
/// @return First node in the tree.
Node *head();
/// @return Last node in the tree.
Node *tail();
/** Insert @a node before @a spot.
*
* @param spot Target node.
* @param node Node to insert.
* @param update_tree @c true to update the RBTree structure.
*/
void insert_before(Node *spot, Node *node, bool update_tree = true);
/** Insert @a node after @a spot.
*
* @param spot Target node.
* @param node Node to insert.
* @param update_tree @c true to update the RBTree structure.
*/
void insert_after(Node *spot, Node *node, bool update_tree = true);
/** Add @a node to tree as the first element.
*
* @param node Node to prepend.
* @param update_tree @c true to update the RBTree structure.
*
* Invariant - @a node is first in order.
*/
void prepend(Node *node, bool update_tree = true);
/** Add @a node to tree as the last node.
*
* @param node Node to append.
* @param update_tree @c true to update the RBTree structure.
*
* Invariant - @a node is last in order.
*/
void append(Node *node, bool update_tree = true);
/** Remove node from container and update container.
*
* @param node Node to remove.
* @param update_tree @c true to update the RBTree structure.
*/
void remove(Node *node, bool update_tree = true);
};
// ---
template <typename METRIC, typename PAYLOAD>
PAYLOAD &
DiscreteSpace<METRIC, PAYLOAD>::Node::payload() {
return _payload;
}
template <typename METRIC, typename PAYLOAD>
auto
DiscreteSpace<METRIC, PAYLOAD>::Node::assign(DiscreteSpace::range_type const &range) -> self_type & {
_range = range;
return *this;
}
template <typename METRIC, typename PAYLOAD>
auto
DiscreteSpace<METRIC, PAYLOAD>::Node::assign(PAYLOAD const &payload) -> self_type & {
_payload = payload;
return *this;
}
template <typename METRIC, typename PAYLOAD>
void
DiscreteSpace<METRIC, PAYLOAD>::Node::structure_fixup() {
// Invariant: The hulls of all children are correct.
if (_left && _right) {
// If both children, local range must be inside the hull of the children and irrelevant.
_hull.assign(this->left()->_hull.min(), this->right()->_hull.max());
} else if (_left) {
_hull.assign(this->left()->_hull.min(), _range.max());
} else if (_right) {
_hull.assign(_range.min(), this->right()->_hull.max());
} else {
_hull = _range;
}
}
// ---
// Discrete Space
// ---
template <typename METRIC, typename PAYLOAD> DiscreteSpace<METRIC, PAYLOAD>::~DiscreteSpace() {
// Destruct all the payloads - the nodes themselves are in the arena and disappear with it.
for (auto &node : _list) {
std::destroy_at(&node.payload());
}
}
template <typename METRIC, typename PAYLOAD>
size_t
DiscreteSpace<METRIC, PAYLOAD>::count() const {
return _list.count();
}
template <typename METRIC, typename PAYLOAD>
bool
DiscreteSpace<METRIC, PAYLOAD>::empty() const {
return _list.empty();
}
template <typename METRIC, typename PAYLOAD>
auto
DiscreteSpace<METRIC, PAYLOAD>::head() -> Node * {
return static_cast<Node *>(_list.head());
}
template <typename METRIC, typename PAYLOAD>
auto
DiscreteSpace<METRIC, PAYLOAD>::tail() -> Node * {
return static_cast<Node *>(_list.tail());
}
template <typename METRIC, typename PAYLOAD>
auto
DiscreteSpace<METRIC, PAYLOAD>::find(METRIC const &metric) -> iterator {
auto n = _root; // current node to test.
while (n) {
if (metric < n->min()) {
if (n->_hull.contains(metric)) {
n = n->left();
} else {
return this->end();
}
} else if (n->max() < metric) {
if (n->_hull.contains(metric)) {
n = n->right();
} else {
return this->end();
}
} else {
return _list.iterator_for(n);
}
}
return this->end();
}
template <typename METRIC, typename PAYLOAD>
auto
DiscreteSpace<METRIC, PAYLOAD>::find(METRIC const &metric) const -> const_iterator {
return const_cast<self_type *>(this)->find(metric);
}
template <typename METRIC, typename PAYLOAD>
auto
DiscreteSpace<METRIC, PAYLOAD>::lower_node(METRIC const &target) -> Node * {
Node *n = _root; // current node to test.
Node *zret = nullptr; // best node so far.
// Fast check for sequential insertion
if (auto ln = _list.tail(); ln != nullptr && ln->max() < target) {
return ln;
}
while (n) {
if (target < n->min()) {
n = left(n);
} else {
zret = n; // this is a better candidate.
if (n->max() < target) {
n = right(n);
} else {
break;
}
}
}
return zret;
}
template <typename METRIC, typename PAYLOAD>
auto
DiscreteSpace<METRIC, PAYLOAD>::upper_node(METRIC const &target) -> Node * {
Node *n = _root; // current node to test.
Node *zret = nullptr; // best node so far.
// Fast check if there is no range past @a target.
if (auto ln = _list.tail(); ln == nullptr || ln->min() <= target) {
return nullptr;
}
while (n) {
if (target > n->min()) {
n = right(n);
} else {
zret = n; // this is a better candidate.
if (n->min() > target) {
n = left(n);
} else {
break;
}
}
}
// Must be past any intersecting range due to STL iteration being half open.
if (zret && (zret->min() <= target)) {
zret = next(zret);
}
return zret;
}
template <typename METRIC, typename PAYLOAD>
auto
DiscreteSpace<METRIC, PAYLOAD>::lower_bound(METRIC const &m) -> iterator {
auto n = this->lower_node(m);
return n ? iterator(n) : this->end();
}
template <typename METRIC, typename PAYLOAD>
auto
DiscreteSpace<METRIC, PAYLOAD>::upper_bound(METRIC const &m) -> iterator {
auto n = this->upper_node(m);
return n ? iterator(n) : this->end();
}
template <typename METRIC, typename PAYLOAD>
auto
DiscreteSpace<METRIC, PAYLOAD>::intersection(DiscreteSpace::range_type const &range) -> std::pair<iterator, iterator> {
// Quick checks for null intersection
if (this->head() == nullptr || // empty
this->head()->_range.min() > range.max() || // before first range
this->tail()->_range.max() < range.min() // after last range
) {
return {this->end(), this->end()};
}
// Invariant - the search range intersects the convex hull of the ranges. This doesn't require the search
// range to intersect any of the actual ranges.
auto *lower = this->lower_node(range.min());
auto *upper = this->upper_node(range.max());
if (lower == nullptr) {
lower = this->head();
} else if (lower->_range.max() < range.min()) {
lower = next(lower); // lower is before @a range so @c next must be at or past.
if (lower->_range.min() > range.max()) { // @a range is in an uncolored gap.
return {this->end(), this->end()};
}
}
return {_list.iterator_for(lower), upper ? _list.iterator_for(upper) : this->end()};
}
template <typename METRIC, typename PAYLOAD>
void
DiscreteSpace<METRIC, PAYLOAD>::prepend(DiscreteSpace::Node *node, bool update_tree) {
if (update_tree) {
if (!_root) {
_root = node;
} else {
_root = static_cast<Node *>(_list.head()->set_child(node, Direction::LEFT)->rebalance_after_insert());
}
}
_list.prepend(node);
}
template <typename METRIC, typename PAYLOAD>
void
DiscreteSpace<METRIC, PAYLOAD>::append(DiscreteSpace::Node *node, bool update_tree) {
if (update_tree) {
if (!_root) {
_root = node;
} else {
// The last node has no right child, or it wouldn't be the last.
_root = static_cast<Node *>(_list.tail()->set_child(node, Direction::RIGHT)->rebalance_after_insert());
}
}
_list.append(node);
}
template <typename METRIC, typename PAYLOAD>
void
DiscreteSpace<METRIC, PAYLOAD>::remove(DiscreteSpace::Node *node, bool update_tree) {
_list.erase(node);
_fa.destroy(node);
if (!update_tree) {
return;
}
_root = static_cast<Node *>(node->remove());
}
template <typename METRIC, typename PAYLOAD>
void
DiscreteSpace<METRIC, PAYLOAD>::insert_before(DiscreteSpace::Node *spot, DiscreteSpace::Node *node, bool update_tree) {
if (update_tree) {
if (left(spot) == nullptr) {
spot->set_child(node, Direction::LEFT);
} else {
// If there's a left child, there's a previous node, therefore spot->_prev is valid.
// Further, the previous node must be the rightmost descendant node of the left subtree
// and therefore has no current right child.
spot->_prev->set_child(node, Direction::RIGHT);
}
}
_list.insert_before(spot, node);
if (update_tree) {
_root = static_cast<Node *>(node->rebalance_after_insert());
}
}
template <typename METRIC, typename PAYLOAD>
void
DiscreteSpace<METRIC, PAYLOAD>::insert_after(DiscreteSpace::Node *spot, DiscreteSpace::Node *node, bool update_tree) {
if (update_tree) {
if (right(spot) == nullptr) {
spot->set_child(node, Direction::RIGHT);
} else {
// If there's a right child, there's a successor node, and therefore @a _next is valid.
// Further, the successor node must be the left most descendant of the right subtree
// therefore it doesn't have a left child.
spot->_next->set_child(node, Direction::LEFT);
}
}
_list.insert_after(spot, node);
if (update_tree) {
_root = static_cast<Node *>(node->rebalance_after_insert());
}
}
template <typename METRIC, typename PAYLOAD>
DiscreteSpace<METRIC, PAYLOAD> &
DiscreteSpace<METRIC, PAYLOAD>::erase(DiscreteSpace::range_type const &range) {
Node *n = this->lower_node(range.min()); // current node.
while (n) {
auto nn = next(n); // cache in case @a n disappears.
if (n->min() > range.max()) { // cleared the target range, done.
break;
}
if (n->max() >= range.min()) { // some overlap
if (n->max() <= range.max()) { // pure left overlap, clip.
if (n->min() >= range.min()) { // covered, remove.
this->remove(n);
} else { // stub on the left, clip to that.
n->assign_max(--metric_type{range.min()});
}
} else if (n->min() >= range.min()) { // pure left overlap, clip.
n->assign_min(++metric_type{range.max()});
} else { // @a n covers @a range, must split.
auto y = _fa.make(range_type{n->min(), --metric_type{range.min()}}, n->payload());
n->assign_min(++metric_type{range.max()});
this->insert_before(n, y);
break;
}
}
n = nn;
}
return *this;
}
template <typename METRIC, typename PAYLOAD>
DiscreteSpace<METRIC, PAYLOAD> &
DiscreteSpace<METRIC, PAYLOAD>::mark_bulk(std::vector<std::pair<DiscreteSpace::range_type, PAYLOAD>> &ranges, bool is_sorted) {
return this->mark_bulk(ranges.data(), ranges.size(), is_sorted);
}
template <typename METRIC, typename PAYLOAD>
DiscreteSpace<METRIC, PAYLOAD> &
DiscreteSpace<METRIC, PAYLOAD>::mark_bulk(std::pair<range_type, PAYLOAD>* start, size_t n, bool is_sorted)
{
// Sort the input data in-place before processing, if applicable.
if (!is_sorted)
{
// Stable sort allows for duplicate elements.
std::stable_sort(start, start + n, [](const auto &lhs, const auto &rhs) {
if (lhs.first.min() != rhs.first.min()) {
return lhs.first.min() < rhs.first.min();
}
return lhs.first.max() < rhs.first.max();
});
}
// Verify that this is purely an append operation.
// If it's not, then we need to rebuild the entire tree each iteration (suboptimal case).
bool isAppend = !_list.empty() && _list.tail()->max() < start[0].first.min();
// Mark the ranges in the input data.
for (size_t i = 0; i < n; ++i)
{
auto const& [range, payload] = start[i];
this->mark(range, payload, !isAppend);
}
// Rebuild the entire red-black tree.
detail::RBNode* temp_head = _list.head();
_root = static_cast<Node *>(detail::RBNode::buildTree(temp_head, _list.count()));
return *this;
}
template <typename METRIC, typename PAYLOAD>
DiscreteSpace<METRIC, PAYLOAD> &
DiscreteSpace<METRIC, PAYLOAD>::mark(DiscreteSpace::range_type const &range, PAYLOAD const &payload, bool update_tree) {
Node *n = this->lower_node(range.min()); // current node.
Node *x = nullptr; // New node, gets set if we re-use an existing one.
Node *y = nullptr; // Temporary for removing and advancing.
// Use carefully, only in situations where it is known there is no overflow.
auto max_plus_1 = ++metric_type{range.max()};
/* We have lots of special cases here primarily to minimize memory
allocation by re-using an existing node as often as possible.
*/
if (n) {
// Watch for wrap.
auto min_minus_1 = --metric_type{range.min()};
if (n->min() == range.min()) {
// Could be another span further left which is adjacent.
// Coalesce if the data is the same. min_minus_1 is OK because
// if there is a previous range, min is not zero.
Node *p = prev(n);
if (p && p->payload() == payload && p->max() == min_minus_1) {
x = p;
n = x; // need to back up n because frame of reference moved.
x->assign_max(range.max(), update_tree);
} else if (n->max() <= range.max()) {
// Span will be subsumed by request span so it's available for use.
x = n;
x->assign_max(range.max()).assign(payload);
} else if (n->payload() == payload) {
return *this; // request is covered by existing span with the same data
} else {
// request span is covered by existing span.
x = _fa.make(range, payload); //
n->assign_min(max_plus_1, update_tree); // clip existing.
this->insert_before(n, x, update_tree);
return *this;
}
} else if (n->payload() == payload && n->max() >= min_minus_1) {
// min_minus_1 is safe here because n->min() < min so min is not zero.
x = n;
// If the existing span covers the requested span, we're done.
if (x->max() >= range.max()) {
return *this;
}
x->assign_max(range.max(), update_tree);
} else if (n->max() <= range.max()) {
// Can only have left skew overlap, otherwise disjoint.
// Clip if overlap.
if (n->max() >= range.min()) {
n->assign_max(min_minus_1, update_tree);
} else if (nullptr != (y = next(n)) && y->max() <= range.max()) {
// because @a n was selected as the minimum it must be the case that
// y->min >= min (or y would have been selected). Therefore in this
// case the request covers the next node therefore it can be reused.
x = y;
x->assign(range).assign(payload);
if (update_tree)
{
x->ripple_structure_fixup();
}
n = x; // this gets bumped again, which is correct.
}
} else {
// Existing span covers new span but with a different payload.
// We split it, put the new span in between and we're done.
// max_plus_1 is valid because n->max() > max.
Node *r;
x = _fa.make(range, payload);
r = _fa.make(range_type{max_plus_1, n->max()}, n->payload());
n->assign_max(min_minus_1, update_tree);
this->insert_after(n, x, update_tree);
this->insert_after(x, r, update_tree);
return *this; // done.
}
n = next(n); // lower bound span handled, move on.
if (!x) {
x = _fa.make(range, payload);
if (n) {
this->insert_before(n, x, update_tree);
} else {
this->append(x, update_tree); // note that since n == 0 we'll just return.
}
}
} else if (nullptr != (n = this->head()) && // at least one node in tree.
n->payload() == payload && // payload matches
(n->max() <= range.max() || n->min() <= max_plus_1) // overlap or adj.
) {
// Same payload with overlap, re-use.
x = n;
n = next(n);
x->assign_min(range.min(), update_tree);
if (x->max() < range.max()) {
x->assign_max(range.max(), update_tree);
}
} else {
x = _fa.make(range, payload);
this->prepend(x, update_tree);
}
// At this point, @a x has the node for this span and all existing spans of
// interest start at or past this span.
while (n) {
if (n->max() <= range.max()) { // completely covered, drop span, continue
y = n;
n = next(n);
this->remove(y, update_tree);
} else if (max_plus_1 < n->min()) { // no overlap, done.
break;
} else if (n->payload() == payload) { // skew overlap or adj., same payload
x->assign_max(n->max(), update_tree);
y = n;
n = next(n);
this->remove(y, update_tree);
} else if (n->min() <= range.max()) { // skew overlap different payload
n->assign_min(max_plus_1, update_tree);
break;
} else { // n->min() > range.max(), different payloads - done.
break;
}
}
return *this;
}
template <typename METRIC, typename PAYLOAD>
DiscreteSpace<METRIC, PAYLOAD> &
DiscreteSpace<METRIC, PAYLOAD>::fill(DiscreteSpace::range_type const &range, PAYLOAD const &payload) {
// Rightmost node of interest with n->min() <= min.
Node *n = this->lower_node(range.min());
Node *x = nullptr; // New node (if any).
// Need copies because we will modify these.
auto min = range.min();
auto max = range.max();
// Handle cases involving a node of interest to the left of the
// range.
if (n) {
if (n->min() < min) {
auto min_1 = min;
--min_1; // dec is OK because min isn't zero.
if (n->max() < min_1) { // no overlap, not adjacent.
n = next(n);
} else if (n->max() >= max) { // incoming range is covered, just discard.
return *this;
} else if (n->payload() != payload) { // different payload, clip range on left.
min = n->max();
++min;
n = next(n);
} else { // skew overlap or adjacent to predecessor with same payload, use node and continue.
x = n;
n = next(n);
}
}
} else {
n = this->head();
}
// Work through the rest of the nodes of interest.
// Invariant: n->min() >= min
// Careful here -- because max_plus1 might wrap we need to use it only if we can be certain it
// didn't. This is done by ordering the range tests so that when max_plus1 is used when we know
// there exists a larger value than max.
auto max_plus1 = max;
++max_plus1;
/* Notes:
- max (and thence also max_plus1) never change during the loop.
- we must have either x != 0 or adjust min but not both for each loop iteration.
*/
while (n) {
if (n->payload() == payload) {
if (x) {
if (n->max() <= max) { // next range is covered, so we can remove and continue.
this->remove(n);
n = next(x);
} else if (n->min() <= max_plus1) {
// Overlap or adjacent with larger max - absorb and finish.
x->assign_max(n->max());
this->remove(n);
return *this;
} else {
// have the space to finish off the range.
x->assign_max(max);
return *this;
}
} else { // not carrying a span.
if (n->max() <= max) { // next range is covered - use it.
x = n;
x->assign_min(min);
n = next(n);
} else if (n->min() <= max_plus1) {
n->assign_min(min);
return *this;
} else { // no overlap, space to complete range.
this->insert_before(n, _fa.make(min, max, payload));
return *this;
}
}
} else { // different payload
if (x) {
if (max < n->min()) { // range ends before n starts, done.
x->assign_max(max);
return *this;
} else if (max <= n->max()) { // range ends before n, done.
x->assign_max(--metric_type(n->min()));
return *this;
} else { // n is contained in range, skip over it.
x->assign_max(--metric_type(n->min()));
x = nullptr;
min = n->max();
++min; // OK because n->max() maximal => next is null.
n = next(n);
}
} else { // no carry node.
if (max < n->min()) { // entirely before next span.
this->insert_before(n, _fa.make(min, max, payload));
return *this;
} else {
if (min < n->min()) { // leading section, need node.
auto y = _fa.make(min, --metric_type(n->min()), payload);
this->insert_before(n, y);
}
if (max <= n->max()) { // nothing past node
return *this;
}
min = n->max();
++min;
n = next(n);
}
}
}
}
// Invariant: min is larger than any existing range maximum.
if (x) {
x->assign_max(max);
} else {
this->append(_fa.make(min, max, payload));
}
return *this;
}
template <typename METRIC, typename PAYLOAD>
template <typename F, typename U>
auto
DiscreteSpace<METRIC, PAYLOAD>::blend(DiscreteSpace::range_type const &range, U const &color, F &&blender) -> self_type & {
// Do a base check for the color to use on unmapped values. If self blending on @a color
// is @c false, then do not color currently unmapped values.
PAYLOAD plain_color{}; // color to paint uncolored metrics.
bool plain_color_p = blender(plain_color, color); // start with default and blend in @a color.
auto node_cleaner = [&](Node *ptr) -> void { _fa.destroy(ptr); };
// Used to hold a temporary blended node - @c release if put in space, otherwise cleaned up.
using unique_node = std::unique_ptr<Node, decltype(node_cleaner)>;
// Rightmost node of interest with n->min() <= range.min().
Node *n = this->lower_node(range.min());
// This doesn't change, compute outside loop.
auto range_max_plus_1 = range.max();
++range_max_plus_1; // only use in contexts where @a max < METRIC max value.
// Update every loop to track what remains to be filled.
auto remaining = range;
if (nullptr == n) {
n = this->head();
}
// Process @a n, covering the values from the previous range to @a n.max
while (n) {
// If there's no overlap, skip because this will be checked next loop, or it's the last node
// and will be checked post loop. Loop logic is simpler if n->max() >= remaining.min()
if (n->max() < remaining.min()) {
n = next(n);
continue;
}
// Invariant - n->max() >= remaining.min();
// Check for left extension. If found, clip that node to be adjacent and put in a
// temporary that covers the overlap with the original payload.
if (n->min() < remaining.min()) {
// @a fill is inserted iff n->max() < remaining.max(), in which case the max is correct.
// This is needed in other cases only for the color blending result.
unique_node fill{_fa.make(remaining.min(), n->max(), n->payload()), node_cleaner};
bool fill_p = blender(fill->payload(), color); // fill or clear?
if (fill_p) {
bool same_color_p = fill->payload() == n->payload();
if (same_color_p && n->max() >= remaining.max()) {
return *this; // incoming range is completely covered by @a n in the same color, done.
}
if (!same_color_p) {
auto fn = fill.release();
auto n_max = n->max(); // save this so @a n can be clipped.
n->assign_max(--metric_type(remaining.min())); // clip @a n down.
this->insert_after(n, fn); // add intersection node in different color.
if (n_max > remaining.max()) { // right extent too - split and done.
fn->assign_max(remaining.max()); // fill node stops at end of target range.
this->insert_after(fn, _fa.make(++metric_type(remaining.max()), n_max, n->payload()));
return *this;
}
n = fn; // skip to use new node as current node.
}
remaining.assign_min(++metric_type(n->max())); // going to fill up n->max(), clip target.
} else { // clear, don't fill.
auto n_r = n->range(); // cache to avoid ordering requirements.
if (n_r.max() > remaining.max()) { // overhang on the right, must split.
fill.release(); // not going to use it,
n->assign_min(++metric_type(remaining.max()));
this->insert_before(n, _fa.make(n_r.min(), --metric_type(remaining.min()), n->payload()));
return *this;
}
n->assign_max(--metric_type(remaining.min())); // clip @a n down.
if (n_r.max() == remaining.max()) {
return *this;
}
remaining.assign_min(++metric_type(n_r.max()));
}
continue;
}
Node *pred = prev(n);
// invariant
// - remaining.min() <= n->max()
// - !pred || pred->max() < remaining.min()
// Calculate and cache key relationships between @a n and @a remaining.
// @a n extends past @a remaining, so the trailing segment must be dealt with.
bool right_ext_p = n->max() > remaining.max();
// @a n strictly right overlaps with @a remaining.
bool right_overlap_p = remaining.contains(n->min());
// @a n is adjacent on the right to @a remaining.
bool right_adj_p = !right_overlap_p && remaining.is_left_adjacent_to(n->range());
// @a n has the same color as would be used for unmapped values.
bool n_plain_colored_p = plain_color_p && (n->payload() == plain_color);
// Check for no right overlap - that means @a n is past the target range.
// It may be possible to extend @a n or the previous range to cover
// the target range. Regardless, all of @a range can be filled at this point.
if (!right_overlap_p) {
// @a pred has the same color as would be used for unmapped values
// and is adjacent to @a remaining.
bool pred_plain_colored_p = pred && ++metric_type(pred->max()) == remaining.min() && pred->payload() == plain_color;
if (right_adj_p && n_plain_colored_p) { // can pull @a n left to cover
n->assign_min(remaining.min());
if (pred_plain_colored_p) { // if that touches @a pred with same color, collapse.
auto pred_min = pred->min();
this->remove(pred);
n->assign_min(pred_min);
}
} else if (pred_plain_colored_p) { // can pull @a pred right to cover.
pred->assign_max(remaining.max());
} else if (!remaining.empty() && plain_color_p) { // Must add new range if plain color valid.
this->insert_before(n, _fa.make(remaining.min(), remaining.max(), plain_color));
}
return *this;
}
// Invariant: @n has right overlap with @a remaining
// If there's a gap on the left, fill from @a r.min to @a n.min - 1
if (plain_color_p && remaining.min() < n->min()) {
if (n->payload() == plain_color) {
if (pred && pred->payload() == n->payload()) {
auto pred_min{pred->min()};
this->remove(pred);
n->assign_min(pred_min);
} else {
n->assign_min(remaining.min());
}
} else {
auto n_min_minus_1{n->min()};
--n_min_minus_1;
if (pred && pred->payload() == plain_color) {
pred->assign_max(n_min_minus_1);
} else {
this->insert_before(n, _fa.make(remaining.min(), n_min_minus_1, plain_color));
}
}
}
// Invariant: Space in @a range and to the left of @a n has been filled.
// Create a node with the blend for the overlap and then update / replace @a n as needed.
auto max{right_ext_p ? remaining.max() : n->max()}; // smallest boundary of range and @a n.
unique_node fill{_fa.make(n->min(), max, n->payload()), node_cleaner};
bool fill_p = blender(fill->payload(), color); // fill or clear?
auto next_n = next(n); // cache this in case @a n is removed.
remaining.assign_min(++METRIC{fill->max()}); // Update what is left to fill.
// Clean up the range for @a n
if (fill_p) {
// Check if @a pred is suitable for extending right to cover the target range.
bool pred_adj_p =
nullptr != (pred = prev(n)) && pred->range().is_left_adjacent_to(fill->range()) && pred->payload() == fill->payload();
if (right_ext_p) {
if (n->payload() == fill->payload()) {
n->assign_min(fill->min());
} else {
n->assign_min(range_max_plus_1);
if (pred_adj_p) {
pred->assign_max(fill->max());
} else {
this->insert_before(n, fill.release());
}
}
return *this; // @a n extends past @a remaining, -> all done.
} else {
// Collapse in to previous range if it's adjacent and the color matches.
if (pred_adj_p) {
this->remove(n);
pred->assign_max(fill->max());
} else {
this->insert_before(n, fill.release());
this->remove(n);
}
}
} else if (right_ext_p) {
n->assign_min(range_max_plus_1);
return *this;
} else {
this->remove(n);
}
// Everything up to @a n.max is correct, time to process next node.
n = next_n;
}
// Arriving here means there are no more ranges past @a range (those cases return from the loop).
// Therefore the final fill node is always last in the tree.
if (plain_color_p && !remaining.empty()) {
// Check if the last node can be extended to cover because it's left adjacent.
// Can decrement @a range_min because if there's a range to the left, @a range_min is not minimal.
n = _list.tail();
if (n && n->max() >= --metric_type{remaining.min()} && n->payload() == plain_color) {
n->assign_max(range.max());
} else {
this->append(_fa.make(remaining.min(), remaining.max(), plain_color));
}
}
return *this;
}
template <typename METRIC, typename PAYLOAD>
void
DiscreteSpace<METRIC, PAYLOAD>::clear() {
for (auto &node : _list) {
std::destroy_at(&node.payload());
}
_list.clear();
_root = nullptr;
_arena.clear();
_fa.clear();
}
}} // namespace swoc::SWOC_VERSION_NS