xo-alloc/include/xo/ordinaltree/RedBlackTree.hpp

3158 lines
132 KiB
C++

/* @file RedBlackTree.hpp */
/* provides red-black tree with order statistics.
*/
#pragma once
#include "xo/indentlog/scope.hpp"
#include "xo/indentlog/print/pad.hpp"
#include "xo/indentlog/print/quoted.hpp"
#include <concepts>
#include <iterator>
#include <array>
#include <cmath>
#include <cassert>
#include <stdexcept>
namespace xo {
namespace tree {
/* concept for the 'Reduce' argument to RedBlackTree<...>
*
* here:
* T = class implementing reduce feature, e.g. SumReduce<...>
* T::value_type = type for output of reduce function.
*
* Value = value_type for rb-tree that supports ordinal statistics
*
* e.g.
* struct ReduceCountAndSum {
* using value_type = std::pair<uint32_t, int64_t>:
*
* value_type nil() { return value_type(0, 0); }
* value_type operator()(value_type const & acc, int64_t val)
* { return value_type(acc.first + val.first, acc.second + val.second); }
* value_type operator()(value_type const & a1, value_type const & a2)
* { return value_type(a1.first + a2.first, a1.second + a2.second); }
* };
*
* Reduce.nil() -> nominal reduction i.e. reduce on empty set
* Reduce.leaf(v) -> reduction on set {v}
*
* in general: at some internal node, tree splits set of key/value pairs on some key k1,
* with a left subtree lh, and a right subtree rh.
*
* for a binary tree we want to maintain:
* - r1: reduce applied to collection
* lh + {k1} = reduce(reduce(lh), k1)
* - r2: reduce applied to collection
* lh + {k1} + rh = reduce.combine(r1, reduce(r2))
*
*/
template <class T, typename Value>
concept ReduceConcept = requires(T r, Value v, typename T::value_type a) {
typename T::value_type;
{ r.nil() } -> std::same_as<typename T::value_type>;
{ r.leaf(v) } -> std::same_as<typename T::value_type>;
{ r(a, v) } -> std::same_as<typename T::value_type>;
{ r.combine(a, a) } -> std::same_as<typename T::value_type>;
};
/* reduce function that disappears at compile time */
template<typename NodeValue>
struct NullReduce;
/* red-black tree with order statistics
*
* require:
* - Key is equality comparable
* - Key, Value, Reduce are copyable and null-constructible
* - Reduce.value_type = Accumulator
* - Reduce.operator() :: (Accumulator x Key) -> Accumulator
* - Reduce.operator() :: (Accumulator x Accumulator) -> Accumulator
*/
template <typename Key, typename Value, typename Reduce = NullReduce<Key>>
class RedBlackTree;
namespace detail {
enum Color { C_Invalid = -1, C_Black, C_Red, N_Color };
enum Direction { D_Invalid = -1, D_Left, D_Right, N_Direction };
inline Direction other(Direction d) {
return static_cast<Direction>(1 - d);
} /*other*/
template <typename Key, typename Value, typename Reduce>
class RbTreeUtil;
/* xo::tree::detail::Node
*
* Require:
* - Key.operator<
* - Key.operator==
*
*/
template <typename Key,
typename Value,
typename Reduce>
class Node {
public:
using ReducedValue = typename Reduce::value_type;
using ContentsType = std::pair<Key, Value>;
using value_type = std::pair<Key const , Value>;
public:
Node() = default;
Node(value_type const & kv_pair,
std::pair<ReducedValue, ReducedValue> const & r)
: color_(C_Red), size_(1), contents_{kv_pair}, reduced_(r) {}
Node(value_type && kv_pair,
std::pair<ReducedValue, ReducedValue> && r)
: color_(C_Red), size_(1),
contents_{std::move(kv_pair)},
reduced_{std::move(r)} {}
static Node * make_leaf(value_type const & kv_pair,
ReducedValue const & leaf_rv) {
return new Node(kv_pair,
std::pair<ReducedValue, ReducedValue>(leaf_rv, leaf_rv));
} /*make_leaf*/
static Node * make_leaf(value_type && kv_pair,
ReducedValue const & leaf_rv) {
return new Node(kv_pair,
std::pair<ReducedValue, ReducedValue>(leaf_rv, leaf_rv));
} /*make_leaf*/
/* return #of key/vaue pairs in tree rooted at x. */
static size_t tree_size(Node *x) {
if (x)
return x->size();
else
return 0;
} /*tree_size*/
static bool is_black(Node *x) {
if (x)
return x->is_black();
else
return true;
} /*is_black*/
static bool is_red(Node *x) {
if (x)
return x->is_red();
else
return false;
} /*is_red*/
static Direction child_direction(Node *p, Node *n) {
if (p) {
return p->child_direction(n);
} else {
return D_Invalid;
}
} /*child_direction*/
static ReducedValue reduce_aux(Reduce reduce, Node *x)
{
if(x)
return x->reduced2();
else
return reduce.nil();
} /*reduce_aux*/
/* calculate reduced values for node x.
* does not used x.reduced
*/
static std::pair<ReducedValue,
ReducedValue> reduced_pair(Reduce r, Node const * x)
{
if(!x)
assert(false);
ReducedValue r1 = r(reduce_aux(r, x->left_child()),
x->value());
ReducedValue r2 = r.combine(r1,
reduce_aux(r, x->right_child()));
return std::pair<ReducedValue, ReducedValue>(r1, r2);
} /*reduced_pair*/
/* replace root pointer *pp_root with x;
* set x parent pointer to nil
*/
static void replace_root_reparent(Node *x, Node **pp_root) {
*pp_root = x;
if (x)
x->parent_ = nullptr;
} /*replace_root_reparent*/
size_t size() const { return size_; }
/* const access */
ContentsType const & contents() const { return contents_; }
/* non-const value access.
*
* editorial: would prefer to return
* std::pair<Key const, Value> &
* here, so that tree[k].first = newk
* prohibited, but std::pair<Key const, Value>
* is considered unrelated to std::pair<Key, Value>,
* so l-value conversion not allowed
*/
ContentsType & contents() { return contents_; }
Node *parent() const { return parent_; }
Node *child(Direction d) const { return child_v_[d]; }
Node *left_child() const { return child_v_[0]; }
Node *right_child() const { return child_v_[1]; }
ReducedValue const & reduced1() const { return reduced_.first; }
ReducedValue const & reduced2() const { return reduced_.second; }
/* true if this node has 0 children */
bool is_leaf() const {
return ((child_v_[0] == nullptr) && (child_v_[1] == nullptr));
}
/* identify which child x represents
* Require:
* - x != nullptr
* - x is either this->left_child() or this->right_child()
*/
Direction child_direction(Node *x) {
if (x == this->left_child())
return D_Left;
else if (x == this->right_child())
return D_Right;
else
return D_Invalid;
} /*child_direction*/
bool is_black() const { return this->color_ == C_Black; }
bool is_red() const { return this->color_ == C_Red; }
bool is_red_left() const { return is_red(this->left_child()); }
bool is_red_right() const { return is_red(this->right_child()); }
/* true if this node is red, and either child is red */
bool is_red_violation() const {
if (this->color_ == C_Red) {
Node *left = this->left_child();
Node *right = this->right_child();
if (left && left->is_red())
return true;
if (right && right->is_red())
return true;
}
return false;
} /*is_red_violation*/
Color color() const { return color_; }
Key const & key() const { return contents_.first; }
Value const & value() const { return contents_.second; }
/* recalculate size from immediate childrens' sizes
* editor bait: recalc_local_size()
*/
void local_recalc_size(Reduce const & reduce_fn) {
using xo::scope;
using xo::xtag;
//constexpr char const * c_self = "Node::local_recalc_size";
constexpr bool c_logging_enabled = false;
scope log(XO_DEBUG(c_logging_enabled));
this->size_ = (1
+ Node::tree_size(this->left_child())
+ Node::tree_size(this->right_child()));
/* (note: want reduce applied to all of left subtree) */
this->reduced_ = Node::reduced_pair(reduce_fn, this);
log && log("done recalc for key k, value v, reduced r",
xtag("k", this->key()),
xtag("v", this->value()),
xtag("r1", this->reduced1()),
xtag("r2", this->reduced2()));
} /*local_recalc_size*/
private:
void assign_color(Color x) { this->color_ = x; }
void assign_size(size_t z) { this->size_ = z; }
void assign_child_reparent(Direction d, Node *new_x) {
Node *old_x = this->child_v_[d];
// trying to fix old_x can be counterproductive,
// since old_x->parent_ may already have been corrected,
//
if (old_x && (old_x->parent_ == this))
old_x->parent_ = nullptr;
this->child_v_[d] = new_x;
if (new_x) {
new_x->parent_ = this;
}
} /*assign_child_reparent*/
/* replace child that points to x, with child that points to x_new
* and return direction of the child that was replaced
*
* Require:
* - x is a child of *this
* - x_new is not a child of *this
*
* promise:
* - x is nullptr or x.parent is nullptr
* - x_new is nullptr or x_new.parent is this
*/
Direction replace_child_reparent(Node *x, Node *x_new) {
Direction d = this->child_direction(x);
if (d == D_Left || d == D_Right) {
this->assign_child_reparent(d, x_new);
return d;
} else {
return D_Invalid;
}
} /*replace_child_reparent*/
friend class RbTreeUtil<Key, Value, Reduce>;
friend class xo::tree::RedBlackTree<Key, Value, Reduce>;
private:
/* red | black */
Color color_ = C_Red;
/* size of subtree (#of key/value pairs) rooted at this node */
size_t size_ = 0;
/* .first = key associated with this node
* .second = value associated with this node
* .third = reduced value
*/
ContentsType contents_;
/* accumulator for some binary function of Values.
* must be associative, since value will be produced
* by any testing of calls to Reduce::combine().
*
* e.g. {a, b, c, d} could be reduced by:
* r(r(a,b), r(c,d))
* or
* r(a, r(r(b, c), d))
* etc.
*
* examples:
* - count #of keys
* - sum key values
*
* .reduced.first: reduce applied to all values with keys <= .contents.first
* .reduced.second: reduce applied to all values in this subtree.
*/
std::pair<ReducedValue, ReducedValue> reduced_;
/* pointer to parent node, nullptr iff this is the root node */
Node *parent_ = nullptr;
/*
* .child_v[0] = left child
* .child_v[1] = right child
*
* invariants:
* - if .child_v[x] non-null, then .child_v[0]->parent = this
* - a red node may not have red children
*/
std::array<Node *, 2> child_v_ = {nullptr, nullptr};
}; /*Node*/
enum IteratorDirection {
/* ID_Forward. forward iterator
* ID_Reverse. reverse iterator
*/
ID_Forward,
ID_Reverse
}; /*IteratorDirection*/
/* specify iterator location relative to Iterator::node.
* using this to make it possible to correctly decrement an
* iterator at RedBlackTree::end().
*
* IL_BeforeBegin. if non-empty tree, .node is the first node
* in the tree (the one with smallest key),
* and iterator refers to the location
* "one before" that first node.
* IL_Regular. iterator refers to member of the tree
* given by Iterator::node
* IL_AfterEnd. if non-empty tree, .node is the last node
* in the tree (the one with largest key),
* and iterator refers the the location
* "one after" that last node.
*/
enum IteratorLocation {
IL_BeforeBegin,
IL_Regular,
IL_AfterEnd,
}; /*IteratorLocation*/
/* require:
* - Reduce::value_type
*/
template <typename Key,
typename Value,
typename Reduce>
class RbTreeUtil {
public:
using RbNode = Node<Key, Value, Reduce>;
using ReducedValue = typename Reduce::value_type;
using value_type = std::pair<Key const, Value>;
public:
/* return #of key/vaue pairs in tree rooted at x. */
static size_t tree_size(RbNode *x) {
if (x)
return x->size();
else
return 0;
} /*tree_size*/
static bool is_black(RbNode *x) {
if (x)
return x->is_black();
else
return true;
} /*is_black*/
static bool is_red(RbNode *x) {
if (x)
return x->is_red();
else
return false;
} /*is_red*/
/* for every node n in tree, call fn(n, d').
* d' is the depth of the node n relative to starting point x,
* not counting red nodes.
* make calls in increasing key order (i.e. inorder traversal)
* argument d is the black-height of tree above x
*
* Require:
* - fn(x, d)
*/
template <typename Fn>
static void inorder_node_visitor(RbNode const * x, uint32_t d, Fn && fn) {
if (x) {
/* dd: black depth of child subtrees*/
uint32_t dd = (x->is_black() ? d + 1 : d);
inorder_node_visitor(x->left_child(), dd, fn);
/* dd includes this node */
fn(x, dd);
inorder_node_visitor(x->right_child(), dd, fn);
}
} /*inorder_node_visitor*/
/* note: RedBlackTree.clear() abuses this to visit-and-delete
* all nodes
*/
template <typename Fn>
static void postorder_node_visitor(RbNode const * x, uint32_t d, Fn && fn) {
if (x) {
uint32_t dd = (x->is_black() ? d + 1 : d);
postorder_node_visitor(x->left_child(), dd, fn);
postorder_node_visitor(x->right_child(), dd, fn);
/* dd includes this node */
fn(x, dd);
}
} /*postorder_node_visitor*/
/* return the i'th inorder node (counting from 0)
* belonging to the subtree rooted at N.
*
* behavior not defined if subtree at N contains less than
* (i + 1) nodes
*/
static RbNode * find_ith(RbNode * N, uint32_t i) {
if(!N)
return nullptr;
RbNode * L = N->left_child();
uint32_t n_left = tree_size(L);
if(i < n_left)
return find_ith(L, i);
else if(i == n_left)
return N;
else if(i < N->size_)
return find_ith(N->right_child(), i - (n_left + 1));
else
return nullptr;
} /*find_ith*/
/* starting from x, traverse only left children
* to find node with a nil left child.
*
* This node has the smallest key in subtree N
*/
static RbNode * find_leftmost(RbNode * N) {
while(N) {
RbNode * S = N->left_child();
if(!S)
break;
N = S;
}
return N;
} /*find_leftmost*/
/* return node containing the next key after N->key_ in the tree
* containing N. This will be either a descendant of N,
* or an ancestor of N.
* returns nil if x.key is the largest key in tree containing x.
*/
static RbNode * next_inorder_node(RbNode * N) {
if(!N)
return nullptr;
if(N->right_child())
return find_leftmost(N->right_child());
/* N has no right child -->
* successor is the nearest ancestor with a left child
* on path to N
*/
RbNode * x = N;
while(x) {
RbNode * P = x->parent();
if(P && P->left_child() == x) {
return P;
}
/* path P..N traverses only right-child pointers) */
x = P;
}
/* no ancestor of N with a left child, so N has the largest key
* in the tree
*/
return nullptr;
} /*next_inorder_node*/
/* return node containing the key before N->key_ in the tree containing N.
* This will be either a descendant of N, or an ancestor of N
*/
static RbNode * prev_inorder_node(RbNode * N) {
if(!N)
return nullptr;
if(N->left_child())
return find_rightmost(N->left_child());
/* N has no left child -->
* predecessor is the nearest ancestor with a right child
* on path to N
*/
RbNode * x = N;
while(x) {
RbNode * P = x->parent();
if(P && (P->right_child() == x)) {
return P;
}
/* path P..N traverses only left-child pointers */
x = P;
}
/* no ancestor of N with a right child, so N has the smallest key
* in tree that containing it.
*/
return nullptr;
} /*prev_inorder_node*/
/* compute value of reduce applied to the set K of all keys k[j] in subtree N
* with:
* k[j] <= lub_key if is_closed = true
* k[j] < lub_key if is_closed = false
* return reduce_fn.nil() if K is empty
*/
static ReducedValue reduce_lub(Key const & lub_key,
Reduce const & reduce_fn,
bool is_closed,
RbNode * N)
{
ReducedValue retval = reduce_fn.nil();
for (;;) {
if (!N)
return retval;
if ((N->key() < lub_key) || (is_closed && (N->key() == lub_key))) {
/* all keys k[i] in left subtree of N satisfy k[i] < lub_key
* apply reduce to:
* - left subtree of N
* - N->key() depending on comparison with lub_key
* - any members of right subtree of N, with key < lub_key;
*/
retval = reduce_fn.combine(retval, N->reduced1());
N = N->right_child();
} else {
/* all keys k[j] in right subtree of N do NOT satisfy k[j] <
* lub_key, exclude these. also exclude N->key()
*/
N = N->left_child();
}
}
} /*reduce_lub*/
/* find largest key k such that
* reduce({node j in subtree(N)) | j.key <= k}) < p
*
* ^
* 1 | xxxx
* | xx
* p |....... x
* | x
* | xx .
* | xxxx .
* 0 +---------------->
* ^
* find_cum_glb(p)
*
* here Key is a sample value,
* Value counts #of samples with that key.
*
* find_cum_glb() computes inverse for a monotonically increasing function,
* if reduce(S) = sum {j.value | j in S};
*
* if rbtree stores values for a discrete function f: IR -> IR+,
* then x = find_sum_glb(p)->key() inverts the integral of f, i.e.
* computes:
* x
* /
* |
* sup { x: | f(z) dz < y }
* |
* /
* -oo
*
* Require:
* - Reduce behaves like sum:
* must deliver monotonically increasing values
* with increasing key-values.
*
* (for example: if Value is non-negative and Reduce is SumReduce<Value>)
*/
static RbNode * find_sum_glb(Reduce const & reduce_fn,
RbNode * N,
typename Reduce::value_type y) {
using xo::scope;
using xo::xtag;
constexpr char const * c_self = "RbTreeUtil::find_sum_glb";
constexpr bool c_logging_enabled = false;
scope log(XO_DEBUG(c_logging_enabled));
if(!N) {
log && log(c_self, ": return nullptr");
return nullptr;
}
typename Reduce::value_type left_sum
= RbNode::reduce_aux(reduce_fn, N->left_child());
typename Reduce::value_type right_sum
= RbNode::reduce_aux(reduce_fn, N->right_child());
log && log("with",
xtag("y", y),
xtag("N.key", N->key()),
xtag("N.value", N->value()),
xtag("N.reduced1", N->reduced1()),
xtag("left_sum", left_sum),
xtag("right_sum", right_sum));
if (y <= left_sum) {
return find_sum_glb(reduce_fn, N->left_child(), y);
} else if (y <= N->reduced1() || !N->right_child()) {
log && log("return N");
/* since N.reduced = reduce(left_sum, N.value, right_sum) */
return N;
} else {
/* find bound in non-null right subtree */
return find_sum_glb(reduce_fn, N->right_child(), y - N->reduced1());
}
} /*find_sum_glb*/
/* starting from x, traverse only right children
* to find node with a nil right child
*
* This node has the largest key in subtree N
*/
static RbNode * find_rightmost(RbNode *N) {
while(N) {
RbNode *S = N->right_child();
if (!S)
break;
N = S;
}
return N;
} /*find_rightmost*/
/* find node in x with key k
* return nullptr iff no such node exists.
*/
static RbNode * find(RbNode * x, Key const & k) {
for (;;) {
if (!x)
return nullptr;
if (k < x->key()) {
/* search in left subtree */
x = x->left_child();
} else if (k == x->key()) {
return x;
} else /* k > x->key() */ {
x = x->right_child();
}
}
} /*find*/
/* find greatest lower bound for key k in tree x,
* provided it's tighter than candidate h.
*
* require:
* if h is provided, then x belongs to right subtree of h
* (so any key k' in x satisfies k' > h->key)
*
*/
static RbNode *find_glb_aux(RbNode *x, RbNode *h, Key const &k,
bool is_closed) {
for (;;) {
if (!x)
return h;
if (x->key() < k) {
/* x.key is a lower bound for k */
if (x->right_child() == nullptr) {
/* no tighter lower bounds present in subtree rooted at x */
/* x must be better lower bound than h,
* since when h is non-nil we are searching right subtree of h
*/
return x;
}
/* look for better lower bound in right child */
h = x;
x = x->right_child();
continue;
} else if (is_closed && (x->key() == k)) {
/* x.key is exact match */
return x;
} else {
/* x.key is an upper bound for k. If there's a lower bound,
* it must be in left subtree of x
*/
/* preserving h */
x = x->left_child();
continue;
}
} /*looping over tree nodes*/
} /*find_glb_aux*/
/* find greatest lower bound node for a key, in this subtree
*
* is_open. if true, allow result with N->key = k exactly
* if false, require N->key < k
*/
static RbNode * find_glb(RbNode * x, Key const & k, bool is_closed) {
return find_glb_aux(x, nullptr, k, is_closed);
} /*find_glb*/
#ifdef NOT_IN_USE
/* find least upper bound node for a key, in this subtree*
*
* is_open. if true, allow result with N->key = k exactly
* if false, require N->key > k
*/
static RbNode *find_lub(RbNode *x, Key const &k, bool is_closed) {
if (x->key() > k) {
/* x.key is an upper bound for k */
if (x->left_child() == nullptr) {
/* no tigher upper bound present in subtree rooted at x */
return x;
}
RbNode *y = find_lub(x->left_child(), k, is_closed);
if (y) {
/* found better upper bound in left subtree */
return y;
} else {
return x;
}
} else if (is_closed && (x->key() == k)) {
return x;
} else {
/* x.key is not an upper bound for k */
return find_lub(x->right_child(), k, is_closed);
}
} /*find_lub*/
#endif
/* perform a tree rotation in direction d at node A.
*
* Require:
* - A is non-nil
* - A->child(other(d)) is non-nil
*
* if direction=D_Left:
*
* G G
* | |
* A B <- retval
* / \ / \
* R B ==> A T
* / \ / \
* S T R S
*
* if direction=D_Right:
*
* G G
* | |
* A B <- retval
* / \ / \
* B R ==> T A
* / \ / \
* T S S R
*/
static RbNode *rotate(Direction d, RbNode *A,
Reduce const & reduce_fn,
RbNode **pp_root) {
using xo::scope;
using xo::xtag;
//constexpr char const *c_self = "RbTreeUtil::rotate";
constexpr bool c_logging_enabled = false;
scope log(XO_DEBUG(c_logging_enabled));
Direction other_d = other(d);
RbNode *G = A->parent();
RbNode *B = A->child(other_d);
//RbNode *R = A->child(d); // not using
RbNode *S = B->child(d);
//RbNode *T = B->child(other_d); // not using
if (log.enabled()) {
log("rotate-", (d == D_Left) ? "left" : "right",
" at", xtag("A", A), xtag("A.key", A->key()), xtag("B", B),
xtag("B.key", B->key()));
if (G) {
log("with G", xtag("G", G),
xtag("G.key", G->key()));
// display_aux(D_Invalid /*side*/, G, 0, &lscope);
} else {
log("with A at root");
// display_aux(D_Invalid /*side*/, A, 0, &lscope);
}
}
/* note: this will set A's old child B to have null parent ptr */
A->assign_child_reparent(other_d, S);
A->local_recalc_size(reduce_fn);
B->assign_child_reparent(d, A);
B->local_recalc_size(reduce_fn);
if (G) {
G->replace_child_reparent(A, B);
assert(B->parent() == G);
/* note: G.size not affected by rotation */
} else {
RbNode::replace_root_reparent(B, pp_root);
}
return B;
} /*rotate*/
/* fixup size in N and all ancestors of N,
* after insert/remove affecting N
*/
static void fixup_ancestor_size(Reduce const & reduce_fn, RbNode *N) {
while (N) {
N->local_recalc_size(reduce_fn);
N = N->parent();
}
} /*fixup_ancestor_size*/
/* rebalance to fix possible red-red violation at node G or G->child(d).
*
* diagrams are for d=D_Left;
* mirror left-to-right to get diagram for d=D_Right
*
* G
* d-> / \ <-other_d
* P U
* / \
* R S
*
* relative to prevailing black-height h:
* - P at h
* - U at h
* - may have red-red violation between G and P
*
* Require:
* - tree is in RB-shape, except for possible red-red violation
* between {G,P} or {P,R|S}
* Promise:
* - tree is in RB-shape
*/
static void fixup_red_shape(Direction d, RbNode *G,
Reduce const & reduce_fn,
RbNode **pp_root) {
using xo::scope;
using xo::xtag;
using xo::print::ccs;
//constexpr char const *c_self = "RbTreeUtil::fixup_red_shape";
constexpr bool c_logging_enabled = false;
constexpr bool c_excessive_verify_enabled = false;
scope log(XO_DEBUG(c_logging_enabled));
RbNode *P = G->child(d);
for (uint32_t iter = 0;; ++iter) {
if (c_excessive_verify_enabled)
RbTreeUtil::verify_subtree_ok(reduce_fn, G, nullptr /*&black_height*/);
if (log.enabled()) {
if (G) {
log("consider node G with d-child P",
xtag("iter", iter), xtag("G", G),
xtag("G.col", ccs((G->color() == C_Red) ? "r" : "B")),
xtag("G.key", G->key()),
xtag("d", ccs((d == D_Left) ? "L" : "R")),
xtag("P", P),
xtag("P.col", ccs((P->color() == C_Red) ? "r" : "B")),
xtag("P.key", P->key()));
} else {
log("consider root P", xtag("iter", iter),
xtag("P", P),
xtag("P.col", ccs((P->color() == C_Red) ? "r" : "B")),
xtag("P.key", P->key()));
}
RbTreeUtil::display_aux(D_Invalid /*side*/, G ? G : P, 0 /*d*/,
&log);
} /*if logging enabled*/
if (G && G->is_red_violation()) {
log && log("red-red violation at G - defer");
/* need to fix red-red violation at next level up
*
* . (=G')
* | (=d')
* G* (=P')
* d-> / \ <-other-d
* P* U
* / \
* R S
*/
P = G;
G = G->parent();
d = RbNode::child_direction(G, P);
continue;
}
log && log("check for red violation at P");
if (!P->is_red_violation()) {
log && log("red-shape ok at {G,P}");
/* RB-shape restored */
return;
}
if (!G) {
log && log("make P black to fix red-shape at root");
/* special case: P is root of tree.
* can fix red violation by making P black
*/
P->assign_color(C_Black);
return;
}
Direction other_d = other(d);
RbNode *R = P->child(d);
RbNode *S = P->child(other_d);
RbNode *U = G->child(other_d);
if (log.enabled()) {
log("got R,S,U", xtag("R", R), xtag("S", S),
xtag("U", U));
if (R) {
log("with",
xtag("R.col", ccs(R->color_ == C_Black ? "B" : "r")),
xtag("R.key", R->key()));
}
if (S) {
log("with",
xtag("S.col", ccs(S->color_ == C_Black ? "B" : "r")),
xtag("S.key", S->key()));
}
if (U) {
log("with",
xtag("U.col", ccs(U->color_ == C_Black ? "B" : "r")),
xtag("U.key", U->key()));
}
}
assert(is_black(G));
assert(is_red(P));
assert(is_red(R) || is_red(S));
if (RbNode::is_red(U)) {
/* if d=D_Left:
*
* *=red node
*
* . . (=G')
* | | (=d')
* G G* (=P')
* d-> / \ / \
* P* U* ==> P U
* / \ / \
* (*)R S(*) (*)R S(*)
*
* (*) exactly one of R or S is red (since we have a red-violation
* at P)
*
* Note: this transformation preserves #of black nodes along path
* from root to each of {T, R, S}, so it preserves the "equal
* black-node path" property
*/
G->assign_color(C_Red);
P->assign_color(C_Black);
U->assign_color(C_Black);
log && log("fixed red violation at P, retry 1 level higher");
/* still need to check for red-violation at G's parent */
P = G;
G = G->parent();
d = RbNode::child_direction(G, P);
continue;
}
assert(RbNode::is_black(U));
if (RbNode::is_red(S)) {
log && log("rotate-", (d == D_Left) ? "left" : "right",
" at P", xtag("P", P), xtag("P.key", P->key()),
xtag("S", S), xtag("S.key", S->key()));
/* preparatory step: rotate P in d direction if "inner child"
* (S) is red inner-child = right-child of left-parent or vice
* versa
*
* G G
* / \ / \
* P* U ==> (P'=) S* U
* / \ / \
* R S* (R'=) P*
* / \
* R
*/
RbTreeUtil::rotate(d, P, reduce_fn, pp_root);
if (c_excessive_verify_enabled)
RbTreeUtil::verify_subtree_ok(reduce_fn, S, nullptr /*&black_height*/);
/* (relabel S->P etc. for merged control flow below) */
R = P;
P = S;
}
/*
* G P
* / \ / \
* P* U ==> R* G*
* / \ / \
* R* S S U
*
* ok since every path that went through previously-black G
* now goes through newly-black P
*/
P->assign_color(C_Black);
G->assign_color(C_Red);
log && log("rotate-",
(other_d == D_Left) ? "left" : "right", " at G",
xtag("G", G), xtag("G.key", G->key()));
RbTreeUtil::rotate(other_d, G, reduce_fn, pp_root);
if (c_excessive_verify_enabled) {
RbNode *GG = G ? G->parent() : G;
if (!GG)
GG = P;
if (log.enabled()) {
log("verify subtree at GG", xtag("GG", GG),
xtag("GG.key", GG->key()));
RbTreeUtil::verify_subtree_ok(reduce_fn, GG, nullptr /*&black_height*/);
RbTreeUtil::display_aux(D_Invalid, GG, 0 /*depth*/, &log);
log("fixup complete");
}
}
return;
} /*walk toward root until red violation fixed*/
} /*fixup_red_shape*/
/* insert key-value pair (key, value) into *pp_root.
* on exit *pp_root contains new tree with (key, value) inserted.
* returns true if node was inserted, false if instead an existing node
* with the same key was replaced.
*
* Require:
* - pp_root is non-nil (*pp_root may be nullptr -> empty tree)
* - *pp_root is in RB-shape
*
* allow_replace_flag. if true, v will replace an existing value
* associated with key k.
* if false, preserve existing value.
* when k already exists in *pp_root.
*
* return pair<f,n> with:
* - f=true for new node (k did not exist in tree before this call)
* - f=false for existing node (k already in tree before this call)
* - n=node containing key k
*/
static std::pair<bool, RbNode *>
insert_aux(value_type const & kv_pair,
bool allow_replace_flag,
Reduce const & reduce_fn,
RbNode ** pp_root)
{
using xo::xtag;
//XO_SCOPE2(log, true /*debug_flag*/);
RbNode * N = *pp_root;
Direction d = D_Invalid;
while (N) {
if (kv_pair.first == N->key()) {
if(allow_replace_flag) {
/* match on this key already present in tree
* -> just update assoc'd value
*/
N->contents_.second = kv_pair.second;
}
/* after modifying a node n, must recalculate reductions
* along path [root .. n]
*/
RbTreeUtil::fixup_ancestor_size(reduce_fn, N);
//log && log(xtag("path", (char const *)"A"));
/* since we didn't change the set of nodes,
* tree is still in RB-shape, don't need to call fixup_red_shape()
*/
return std::make_pair(false, N);
}
d = ((kv_pair.first < N->key()) ? D_Left : D_Right);
/* insert into left subtree somewhere */
RbNode *C = N->child(d);
if (!C)
break;
N = C;
}
/* invariant: N->child(d) is nil */
if (N) {
RbNode * new_node = RbNode::make_leaf(kv_pair,
reduce_fn.leaf(kv_pair.second));
N->assign_child_reparent(d, new_node);
assert(is_red(N->child(d)));
/* recalculate Node sizes on path [root .. N] */
RbTreeUtil::fixup_ancestor_size(reduce_fn, N);
/* after adding a node, must rebalance to restore RB-shape */
RbTreeUtil::fixup_red_shape(d, N, reduce_fn, pp_root);
//log && log(xtag("path", (char const *)"B"));
/* note: new_node=N.child(d) is true before call to fixup_red_shape(),
* but not necessarily after
*/
return std::make_pair(true, new_node);
} else {
*pp_root = RbNode::make_leaf(kv_pair,
reduce_fn.leaf(kv_pair.second));
/* tree with a single node might as well be black */
(*pp_root)->assign_color(C_Black);
//(*pp_root)->local_recalc_size(reduce_fn);
/* Node.size will be correct for tree, since
* new node is only node in the tree
*/
//log && log(xtag("path", (char const *)"C"));
return std::make_pair(true, *pp_root);
}
} /*insert_aux*/
/* remove a black node N with no children.
* this will reduce black-height along path to N
* by 1, so will need to rebalance tree
*
* pp_root. pointer to location of tree root;
* may update with new root
*
* Require:
* - N != nullptr
* - N has no child nodes
* - N->parent() != nullptr
*/
static void remove_black_leaf(RbNode *N,
Reduce const & reduce_fn,
RbNode **pp_root)
{
using xo::scope;
using xo::xtag;
using xo::print::ccs;
//constexpr char const *c_self = "RbTreeUtil::remove_black_leaf";
constexpr bool c_logging_enabled = false;
scope log(XO_DEBUG(c_logging_enabled));
assert(pp_root);
RbNode *P = N->parent();
if (!P) {
/* N was the root node, tree now empty */
*pp_root = nullptr;
delete N;
return;
}
/* d: direction in P to immediate child N;
* also sets N.parent to nil
*/
Direction d = P->replace_child_reparent(N, nullptr);
delete N;
/* need to delay this assignment until
* we've determined d
*/
N = nullptr;
/* fixup sizes on path root..P
* subsequent rebalancing rotations will preserve correct .size values
*/
RbTreeUtil::fixup_ancestor_size(reduce_fn, P);
/* other_d, S, C, D will be assigned by loop below
*
* diagram shown with d=D_Left; mirror left-to-right for d=D_Right
*
* P
* d-> / \ <-other_d
* N S
* / \
* C D
*/
Direction other_d;
RbNode *S = nullptr;
RbNode *C = nullptr;
RbNode *D = nullptr;
/* table of outcomes as a function of node color
*
* .=black
* *=red
* x=don't care
*
* #=#of combinations (/16) for P,S,C,D color explained by this row
*
* P S C D case #
* -----------------------
* . . . . Case(1) 1
* x * x x Case(3) 8 P,C,D black is forced by RB rules
* * . . . Case(4) 1
* x . * . Case(5) 2
* x . x * Case(6) 4
* --
* 16
*
*/
while (true) {
assert(is_black(N)); /* reminder: nil is black too */
/* Invariant:
* - either:
* - N is nil (first iteration only), and
* P->child(d) = nil, or:
* - P is nil and non-nil N is tree root, or:
* - N is an immediate child of P,
* and P->child(d) = N
* - N is black
* - all paths that don't go thru N have prevailing black-height h.
* - paths through N have black-height h-1
*/
if (!P) {
/* N is the root node, in which case all paths go through N,
* so black-height is h-1
*/
*pp_root = N;
return;
}
other_d = other(d);
S = P->child(other_d);
/* S can't be nil: since N is non-nil and black,
* it must have a non-nil sibling
*/
assert(S);
C = S->child(d);
D = S->child(other_d);
if (log.enabled()) {
log("rebalance at parent P of curtailed subtree N",
xtag("P", P),
xtag("P.col", ccs(P->color() == C_Black ? "B" : "r")),
xtag("P.key", P->key()));
log("with sibling S, nephews C,D", xtag("S", S),
xtag("S.col", ccs(S->color() == C_Black ? "B" : "r")),
xtag("C", C), xtag("D", D));
}
if (is_black(P) && is_black(S) && is_black(C) && is_black(D)) {
/* Case(1) */
log && log("P,S,C,D all black: mark S red + go up 1 level");
/* diagram with d=D_Left: flip left-to-right for d=D_Right
* =black
* *=red
* _=red or black
*
* P
* / \
* N S
* / \
* C D
*
* relative to prevailing black-height h:
* - N at h-1
* - C at h
* - D at h
*/
S->assign_color(C_Red);
/* now have:
*
* G (=P')
* |
* P (=N')
* / \
* N S*
* / \
* C D
*
* relative to prevailing black-height h:
* - N at h-1
* - C at h-1
* - D at h-1
*
* relabel to one level higher in tree
*/
N = P;
P = P->parent();
d = RbNode::child_direction(P, N);
continue;
} else {
break;
}
} /*loop looking for a red node*/
if (is_red(S)) {
/* Case(3) */
if (log.enabled()) {
log("case 3: S red, P,C,D black -> rotate at P to promote S");
log("case 3: + make P red instead of S");
log("case 3: with",
xtag("P", P),
xtag("P.col", ccs(P->color() == C_Black ? "B" : "r")),
xtag("P.key", P->key()), xtag("S", S),
xtag("S.col", ccs(S->color() == C_Black ? "B" : "r")),
xtag("S.key", S->key()));
}
/* since S is red, {P,C,D} are all black
*
* diagram with d=D_Left: flip left-to-right for d=D_Right
* =black
* *=red
* _=red or black
*
* P
* / \
* N S*
* / \
* C D
*
* relative to prevailing black-height h:
* - N at h-1
* - C at h
* - D at h
*/
assert(is_black(C));
assert(is_black(D));
assert(is_black(P));
assert(is_black(N));
RbTreeUtil::rotate(d, P, reduce_fn, pp_root);
/* after rotation d at P:
*
* S*
* / \
* P D
* / \
* N C
*
* relative to prevailing black-height h:
* - N at h-1 (now goes thru red S)
* - C at H (still goes through black P, red S)
* - D at h-1 (no longer goes thru black P)
*/
P->assign_color(C_Red);
S->assign_color(C_Black);
/* after reversing colors of {P,S}:
*
* S
* / \
* P* D
* / \
* N C (=S')
*
* relative to prevailing black-height h:
* - N at h-1 (now thru black S, red P instead of red S, black P)
* - C at h (now thru black S, red P instead of red S, black P)
* - D at h (now through black S instead of red S, black P)
*/
/* now relabel for subsequent cases */
S = C;
C = S ? S->child(d) : nullptr;
D = S ? S->child(other_d) : nullptr;
}
assert(is_black(S));
if (is_red(P) && is_black(C) && is_black(D)) {
/* Case(4) */
if (log.enabled()) {
log("case 4: P red, N,S,C,D black -> recolor and finish");
log("case 4: with",
xtag("P", P),
xtag("P.col", ccs(P->color() == C_Black ? "B" : "r")),
xtag("P.key", P->key()), xtag("S", S),
xtag("S.col", ccs(S->color() == C_Black ? "B" : "r")),
xtag("S.key", S->key()));
}
assert(is_black(N));
/* diagram with d=D_Left: flip left-to-right for d=D_Right*
* =black
* *=red
* _=red or black
*
* P*
* / \
* N S
* / \
* C D
*
* relative to prevailing black-height h:
* - N at h-1
* - C at h
* - D at h
*/
P->assign_color(C_Black);
S->assign_color(C_Red);
/* after making P black, and S red (swapping colors of P,S):
*
* P
* / \
* N S*
* / \
* C D
*
* relative to prevailing black-height h:
* - N at h
* - C at h
* - D at h
*
* and RB-shape is restored
*/
return;
}
assert(is_black(S) && (is_black(P) || is_red(C) || is_red(D)));
if (is_red(C) && is_black(D)) {
log && log("case 5: C red, S,D black -> rotate at S");
/* diagram with d=D_Left; flip left-to-right for d=D_Right
*
* =black
* *=red
* _=red or black
*
* P_
* / \
* N S
* / \
* C* D
*
* relative to prevailing black-height h:
* - N at h-1
* - C at h
* - D at h
*/
RbTreeUtil::rotate(other_d, S, reduce_fn, pp_root);
assert(P->child(other_d) == C);
/* after other(d) rotation at S:
*
* P_
* / \
* N C*
* \
* S
* \
* D
*
* relative to prevailing black-height h:
* - N at h-1
* - C at h-1 (no longer goes thru black S)
* - S at h (now goes thru red C)
* - D at h (now goes thru red C)
*/
C->assign_color(C_Black);
S->assign_color(C_Red);
/* after exchanging colors of C,S:
*
* P_
* / \
* N C (=S')
* \
* S* (=D')
* \
* D
*
* relative to prevailing black-height h:
* - N at h-1
* - C at h (no longer goes thru black S, but now C black)
* - S at h (no longer red, but now goes thru black C)
* - D at h (now goes thru black C, red S instead of black S)
*/
/* now relabel to match next and final case */
D = S;
S = C;
C = nullptr; /* won't be using C past this point */
assert(D);
assert(D->is_red());
/* fall through to next case */
}
if (is_red(D)) {
log && log("case 6: S black, D red -> rotate at P and finish");
/* diagram with d=D_Left; flip left-to-right for d=D_Right
*
* Sibling is black, and distant child is red
*
* if N=P->left_child():
*
* *=red
* _=red or black
*
* P_
* / \
* N S
* / \
* C_ D*
*
* relative to prevailing black-height h:
* - N at h-1
* - S (+also C,D) at h
*/
RbTreeUtil::rotate(d, P, reduce_fn, pp_root);
/* after rotate at P toward d: *
*
* S
* / \
* P_ D*
* / \
* N C_
*
* Now, relative to prevailing black-height h:
* - N at h+1 (paths to N now visit black S)
* - C at h (paths to C still visit P,S)
* - D at: h if P red,
* h-1 if P black
* (paths to D now skip P)
*/
S->assign_color(P->color());
P->assign_color(C_Black);
D->assign_color(C_Black);
/* after recolor: S to old P color, P to black, D to black.
*
* S_
* / \
* P D
* / \
* N C_
*
* Now, relative to prevailing black-height h:
* - N at h+1 (swapped P, S colors)
* - C at h (paths to C still visit P,S, swapped P,S colors)
* - D at: h if S red (was P red, S black, D red; now S red, D
* black) h if S black (was P black, S black, D red; now S
* black, D black)
*
* RB-shape has been restored
*/
return;
}
} /*remove_black_leaf*/
/* remove node with key k from tree rooted at *pp_root.
* on exit *pp_root contains new tree root.
*
* Require:
* - pp_root is non-null. (*pp_root can be null -> tree is empty)
* - *pp_root is in RB-shape
*
* return true if a node was removed; false otherwise.
*/
static bool erase_aux(Key const &k,
Reduce const & reduce_fn,
RbNode **pp_root) {
using xo::scope;
using xo::xtag;
//constexpr char const *c_self = "RbTreeUtil::erase_aux";
constexpr bool c_logging_enabled = false;
scope log(XO_DEBUG(c_logging_enabled));
RbNode *N = *pp_root;
log && log("enter", xtag("N", N));
/*
* here the triangle ascii art indicates a tree structure,
* of arbitrary size
*
* o <- this
* / \
* o-N-o
* / \
* X
* / \
* o---R
*/
N = RbTreeUtil::find_glb(N, k, true /*is_closed*/);
if (!N || (N->key() != k)) {
/* no node with .key = k present, so cannot remove it */
return false;
}
if (c_logging_enabled)
log && log("got lower bound", xtag("N", N),
xtag("N.key", N->key()));
/* first step is to simplify problem so that we're removing
* a node with 0 or 1 children.
*/
RbNode *X = N->left_child();
if (X == nullptr) {
/* N has 0 or 1 children */
;
} else {
/* R will be 'replacement node' for N */
RbNode *R = RbTreeUtil::find_rightmost(X);
/* R->right_child() is nil by definition
*
* copy R's (key + value) into N;
* N now serves as container for information previously
* represented by R.
*/
N->contents_ = R->contents_;
/* (preserving N->parent_, N->child_v_[]) */
/* now relabel N as new R (R'),
* and relabel R as new N (N').
* Then go to work on reduced problem of deleting N'.
* Problem is redueced since now N' has 0 or 1 child.
*
* (Doesn't matter that N' contains key,values of R,
* since we're going to delete it anyway)
*/
N = R;
/* (preserving R->parent_, R->child_v_[]) */
/* o
* / \
* o-R'o
* /
* X
* / \
* o---N'
*/
}
RbNode *P = N->parent();
/* N has 0 or 1 children
*
* Implications:
* 1. if N is red, it cannot have red children (by RB rules),
* and it cannot have just 1 black child.
* Therefore red N must have 0 children
* -> can delete N without disturbing RB properties
* 2. if N is black:
* 2.1 if N has 1 child S, then S must be red
* (if S were black, that would require N to have a 2nd child
* to preserve equal black-height for all paths)
* -> replace N with S, repainting S black, in place of
* to-be-reclaimed N
* 1.2 if N is black with 0 children, need to rebalance
*/
if (N->is_red()) {
if (N->is_leaf()) {
/* replace pointer to N with nil in N's parent. */
if (P) {
P->replace_child_reparent(N, nullptr);
RbTreeUtil::fixup_ancestor_size(reduce_fn, P);
} else {
/* N was sole root node; tree will be empty after removing it */
*pp_root = nullptr;
}
if (c_logging_enabled)
log && log("delete node", xtag("addr", N));
delete N;
} else {
assert(false);
/* control can't come here for RB-tree,
* because a red node can't have red children, or just one black
* child.
*/
}
} else /*N->is_black()*/ {
RbNode *R = N->left_child();
if (!R)
R = N->right_child();
if (R) {
/* if a black node has one child, that child cannot be black */
assert(R->is_red());
/* replace N with R in N's parent,
* + make R black to preserve black-height
*/
R->assign_color(C_Black);
if (P) {
P->replace_child_reparent(N, R);
RbTreeUtil::fixup_ancestor_size(reduce_fn, P);
} else {
/* N was root node */
RbNode::replace_root_reparent(R, pp_root);
}
if (c_logging_enabled)
log && log("delete node", xtag("addr", N));
delete N;
} else {
/* N is black with no children,
* may need rebalance here
*/
if (P) {
RbTreeUtil::remove_black_leaf(N, reduce_fn, pp_root);
} else {
/* N was root node */
*pp_root = nullptr;
log && log("delete node", xtag("addr", N));
delete N;
}
}
}
return true;
} /*erase_aux*/
/* verify that subtree at N is in RB-shape.
* will cover subset of RedBlackTree class invariants:
*
* RB2. if N = P->child(d), then N->parent()=P
* RB3. all paths to leaves have the same black height
* RB4. no red node has a red parent
* RB5. inorder traversal visits keys in monotonically increasing order
* RB6. Node::size reports the size of the subtree reachable from that node
* via child pointers
* RB7. Node::reduced reports the value of
* f(f(L, Node::value), R)
* where: L is reduced-value for left child,
* R is reduced-value for right child
*
* returns the #of nodes in subtree rooted at N.
*/
static size_t verify_subtree_ok(Reduce const & reduce_fn,
RbNode const * N,
int32_t * p_black_height)
{
using xo::scope;
using xo::xtag;
using xo::print::ccs;
constexpr char const *c_self = "RbTreeUtil::verify_subtree_ok";
// scope lscope(c_self);
/* counts #of nodes in subtree rooted at N */
size_t i_node = 0;
Key const *last_key = nullptr;
/* inorder node index when establishing black_height */
size_t i_black_height = 0;
/* establish on first leaf node encountered */
uint32_t black_height = 0;
auto verify_fn = [c_self,
&reduce_fn,
&i_node,
&last_key,
&i_black_height,
&black_height] (RbNode const *x,
uint32_t bd)
{
/* RB2. if c=x->child(d), then c->parent()=x */
if (x->left_child()) {
XO_EXPECT(x == x->left_child()->parent(),
tostr(c_self, (": expect symmetric child/parent pointers"),
xtag("i", i_node), xtag("node[i]", x),
xtag("key[i]", x->key()),
xtag("child", x->left_child()),
xtag("child.key", x->left_child()->key()),
xtag("child.parent", x->left_child()->parent_)));
}
if (x->right_child()) {
XO_EXPECT(x == x->right_child()->parent(),
tostr(c_self, ": expect symmetric child/parent pointers",
xtag("i", i_node),
xtag("node[i]", x),
xtag("key[i]", x->key()),
xtag("child", x->right_child()),
xtag("child.key", x->right_child()->key()),
xtag("child.parent", x->right_child()->parent_)));
}
/* RB3. all nodes have the same black-height */
if (x->is_leaf()) {
if (black_height == 0) {
black_height = bd;
} else {
XO_EXPECT(black_height == bd,
tostr(c_self,
": expect all RB-tree nodes to have the same "
"black-height",
xtag("i1", i_black_height), xtag("i2", i_node),
xtag("blackheight(i1)", black_height),
xtag("blackheight(i2)", bd)));
}
}
/* RB4. a red node may not have a red parent
* (conversely, a red node may not have a red child)
*/
RbNode *red_child =
((x->left_child() && x->left_child()->is_red())
? x->left_child()
: ((x->right_child() && x->right_child()->is_red())
? x->right_child()
: nullptr));
XO_EXPECT(
x->is_red_violation() == false,
tostr(c_self,
ccs(": expect RB-shape tree to have no red violations but "
"red y is child of red x"),
xtag("i", i_node), xtag("x.addr", x),
xtag("x.col", ccs((x->color_ == C_Black) ? "B" : "r")),
xtag("x.key", x->key()),
xtag("y.addr", red_child),
xtag("y.col", ccs((red_child->color_ == C_Black) ? "B" : "r")),
xtag("y.key", red_child->key())));
/* RB5. inorder traversal visits nodes in strictly increasing key order */
if (last_key) {
XO_EXPECT((*last_key) < x->key(),
tostr(c_self,
": expect inorder traversal to visit keys"
" in strictly increasing order",
xtag("i", i_node), xtag("key[i-1]", *last_key),
xtag("key[i]", x->key())));
}
last_key = &(x->key());
/* RB6. Node::size reports the size of the subtree reachable from that
* node by child pointers.
*/
XO_EXPECT(x->size() == (tree_size(x->left_child())
+ 1
+ tree_size(x->right_child())),
tostr(c_self,
": expect Node::size to be 1 + sum of childrens' size",
xtag("i", i_node),
xtag("key[i]", x->key()),
xtag("left.size", tree_size(x->left_child())),
xtag("right.size", tree_size(x->right_child()))));
/* RB7. Node::reduced reports the value of
* f(f(L, Node::value), R)
* where: L is reduced-value for left child,
* R is reduced-value for right child
*/
auto reduced_pair
= RbNode::reduced_pair(reduce_fn, x);
XO_EXPECT(reduce_fn.is_equal
(x->reduced1(), reduced_pair.first),
tostr(c_self,
": expect Node::reduced to be reduce_fn"
" applied to (.L, .value)",
xtag("node.reduced1", x->reduced1()),
xtag("reduced_pair.first", reduced_pair.first)));
XO_EXPECT(reduce_fn.is_equal
(x->reduced2(), reduced_pair.second),
tostr(c_self,
": expect Node::reduced to be reduce_fn"
" applied to (.L, .value, .R)",
xtag("node.reduced2", x->reduced2()),
xtag("reduce2_expr", reduced_pair.second)));
++i_node;
};
RbTreeUtil::inorder_node_visitor(N, 0 /*d*/, verify_fn);
if (p_black_height)
*p_black_height = black_height;
return i_node;
} /*verify_subtree_ok*/
/* display tree structure, 1 line per node.
* indent by node depth + d
*/
static void display_aux(Direction side, RbNode const *N, uint32_t d,
xo::scope *p_scope) {
using xo::pad;
using xo::xtag;
using xo::print::ccs;
if (N) {
p_scope->log(pad(d),
xtag("addr", N),
xtag("par", N->parent()),
xtag("side", ccs((side == D_Left) ? "L"
: (side == D_Right) ? "R"
: "root")),
xtag("col", ccs(N->is_black() ? "B" : "r")),
xtag("key", N->key()),
xtag("value", N->value()),
xtag("wt", N->size()),
xtag("reduced1", N->reduced1()),
xtag("reduced2", N->reduced2()));
display_aux(D_Left, N->left_child(), d + 1, p_scope);
display_aux(D_Right, N->right_child(), d + 1, p_scope);
}
} /*display_aux*/
static void display(RbNode const *N, uint32_t d) {
using xo::scope;
scope log(XO_DEBUG(true /*debug_flag*/));
display_aux(D_Invalid, N, d, &log);
} /*display*/
}; /*RbTreeUtil*/
/* xo::tree::detail::RedBlackTreeLhsBase
*
* use for const version of RedBlackTree::operator[].
*
* Require: RbNode is either
* RedBlackTree::RbNode
* or
* RedBlackTree::RbNode const
*/
template <class RedBlackTree, class RbNode>
class RedBlackTreeLhsBase {
public:
using mapped_type = typename RedBlackTree::mapped_type;
using RbUtil = typename RedBlackTree::RbUtil;
public:
RedBlackTreeLhsBase() = default;
RedBlackTreeLhsBase(RedBlackTree * tree, RbNode * node)
: p_tree_(tree), node_(node)
{}
operator mapped_type const & () const {
using xo::tostr;
if (!this->node_) {
throw std::runtime_error
(tostr("rbtree: attempt to use empty lhs object as rvalue"));
}
return this->node_->contents().second;
} /*operator value_type const &*/
protected:
RedBlackTree * p_tree_ = nullptr;
/* invariant: if non-nil, .node belongs to .*p_tree */
RbNode * node_ = nullptr;
}; /*RedBlackTreeLhsBase*/
template<class RedBlackTree>
class RedBlackTreeConstLhs : public RedBlackTreeLhsBase<RedBlackTree const,
typename RedBlackTree::RbNode const>
{
public:
RedBlackTreeConstLhs() = default;
RedBlackTreeConstLhs(RedBlackTree const * tree,
typename RedBlackTree::RbNode const * node)
: RedBlackTreeLhsBase<RedBlackTree const,
typename RedBlackTree::RbNode const>(tree, node) {}
}; /*RedBlackTreeConstLhs*/
/* xo::tree::detail::RedBlackTreeLhs
*
* use for RedBlackTree::operator[].
* can't return a regular lvalue,
* because assignment within a Node N invalidates partial sums along
* the path from tree root to N.
*
* instead interpolate instance of this class, that can intercept
* asasignments.
*/
template <class RedBlackTree>
class RedBlackTreeLhs : public RedBlackTreeLhsBase<RedBlackTree,
typename RedBlackTree::RbNode>
{
public:
using value_type = typename RedBlackTree::value_type;
using key_type = typename RedBlackTree::key_type;
using mapped_type = typename RedBlackTree::mapped_type;
using RbUtil = typename RedBlackTree::RbUtil;
using RbNode = typename RedBlackTree::RbNode;
public:
RedBlackTreeLhs() = default;
RedBlackTreeLhs(RedBlackTree * tree, typename RedBlackTree::RbNode * node, key_type key)
: RedBlackTreeLhsBase<RedBlackTree, RbNode>(tree, node), key_(key) {}
RedBlackTreeLhs & operator=(mapped_type const & v) {
using xo::tostr;
if(this->p_tree_) {
if(this->node_) {
this->node_->contents().second = v;
/* after modifying a node n,
* must recalculate reductions along path [root .. n]
*/
RbUtil::fixup_ancestor_size(this->p_tree_->reduce_fn(),
this->node_);
} else {
/* insert (key, v) pair into this tree */
this->p_tree_->insert(value_type(this->key_, v));
}
} else {
assert(false);
throw std::runtime_error
(tostr("rbtree: attempt to apply operator= thru empty lhs object"));
}
return *this;
} /*operator=*/
RedBlackTreeLhs & operator+=(mapped_type const & v) {
using xo::tostr;
if(this->p_tree_) {
if(this->node_) {
this->node_->contents().second += v;
/* after modifying value at node n,
* must recalculate order statistics along path [root .. n]
*/
RbUtil::fixup_ancestor_size(this->p_tree_->reduce_fn(),
this->node_);
} else {
/* for form's sake, in case value_type is something unusual */
mapped_type v2;
v2 += v;
/* insert (key, v) pair into this tree */
this->p_tree_->insert(value_type(this->key_, v2));
}
} else {
assert(false);
throw std::runtime_error
(tostr("rbtree: attempt to apply operator+= through empty lhs object"));
}
return *this;
} /*operator+=*/
/* TODO:
* - operator-=()
* - operator*=()
* - operator/=()
*/
private:
/* capture key k used in expression tree[k]
* Invariant:
* - if .node is non-null, then .node.key = key
*/
key_type key_;
}; /*RedBlackTreeLhs*/
/* tragically, we can't partially specialize an alias template.
* however we /can/ partially specialize a struct that nests a typealias.
*/
template <typename Key,
typename Value,
typename Reduce,
bool IsConst>
struct NodeTypeTraits { using NodeType = void; };
template <typename Key,
typename Value,
typename Reduce>
struct NodeTypeTraits<Key, Value, Reduce, false> {
using NativeNodeType = Node<Key, Value, Reduce>;
using NodeType = NativeNodeType;
using ContentsType = typename NodeType::ContentsType;
using NodePtrType = NodeType *;
};
template <typename Key,
typename Value,
typename Reduce>
struct NodeTypeTraits<Key, Value, Reduce, true> {
using NativeNodeType = Node<Key, Value, Reduce>;
using NodeType = NativeNodeType const;
using ContentsType = typename NodeType::ContentsType const;
using NodePtrType = NodeType const *;
};
/* xo::tree::detail::IteratorBase
*
* shared between const & and non-const red-black-tree iterators.
*
* editor bait: BaseIterator
*/
template <typename Key,
typename Value,
typename Reduce,
bool IsConst>
class IteratorBase {
public:
using RbUtil = RbTreeUtil<Key, Value, Reduce>;
using RbNode = Node<Key, Value, Reduce>;
using Traits = NodeTypeTraits<Key, Value, Reduce, IsConst>;
using ReducedValue = typename Reduce::value_type;
using RbNativeNodeType = typename Traits::NativeNodeType;
using RbNodePtrType = typename Traits::NodePtrType;
using RbContentsType = typename Traits::ContentsType;
protected:
IteratorBase() = default;
IteratorBase(IteratorDirection dirn, IteratorLocation loc, RbNodePtrType node)
: dirn_{dirn}, location_{loc}, node_{node} {}
IteratorBase(IteratorBase const & x) = default;
static IteratorBase prebegin_aux(RbNodePtrType node) {
return IteratorBase(ID_Forward, IL_BeforeBegin, node);
} /*prebegin_aux*/
static IteratorBase begin_aux(RbNodePtrType node) {
return IteratorBase(ID_Forward, node ? IL_Regular : IL_AfterEnd, node);
} /*begin_aux*/
static IteratorBase end_aux(RbNodePtrType node) {
return IteratorBase(ID_Forward, IL_AfterEnd, node);
} /*end_aux*/
static IteratorBase rprebegin_aux(RbNodePtrType node) {
return IteratorBase(ID_Reverse, IL_AfterEnd, node);
} /*rprebegin_aux*/
static IteratorBase rbegin_aux(RbNodePtrType node) {
return IteratorBase(ID_Reverse,
(node ? IL_Regular : IL_BeforeBegin),
node);
} /*rbegin_aux*/
static IteratorBase rend_aux(RbNodePtrType node) {
return IteratorBase(ID_Reverse,
IL_BeforeBegin,
node);
} /*rend_aux*/
public:
IteratorLocation location() const { return location_; }
RbNodePtrType node() const { return node_; }
ReducedValue const & reduced() const { return node_->reduced(); }
RbContentsType & operator*() const {
this->check_regular();
return this->node_->contents();
} /*operator**/
RbContentsType * operator->() const {
return &(this->operator*());
}
/* true for "just before beginning" and "just after the end" states.
* false otherwise
*/
bool is_sentinel() const { return (this->location_ != IL_Regular); }
/* true unless iterator is in a sentinel state */
bool is_dereferenceable() const { return !this->is_sentinel(); }
/* deferenceable iterators are truth-y;
* sentinel iterators are false-y
*/
operator bool() const { return this->is_dereferenceable(); }
bool operator==(IteratorBase const & x) const {
return (this->location_ == x.location_) && (this->node_ == x.node_);
} /*operator==*/
bool operator!=(IteratorBase const & x) const {
return (this->location_ != x.location_) || (this->node_ != x.node_);
} /*operator!=*/
void print(std::ostream & os) const {
using xo::xtag;
os << "<rbtree-iterator"
<< xtag("dirn", dirn_)
<< xtag("loc", location_)
<< xtag("node", node_)
<< ">";
} /*print*/
/* pre-increment */
IteratorBase & operator++() {
return ((this->dirn_ == ID_Forward)
? this->next_step()
: this->prev_step());
} /*operator++*/
/* pre-decrement */
IteratorBase & operator--() {
return ((this->dirn_ == ID_Forward)
? this->prev_step()
: this->next_step());
} /*operator--*/
protected:
void check_regular() const {
using xo::tostr;
if(this->location_ != IL_Regular)
throw std::runtime_error(tostr("rbtree iterator: cannot deref iterator"
" in non-regular state"));
} /*check_regular*/
private:
IteratorBase & next_step() {
switch(this->location_) {
case IL_BeforeBegin:
/* .node is first node in tree */
this->location_ = IL_Regular;
break;
case IL_Regular:
{
RbNodePtrType next_node
= RbUtil::next_inorder_node(const_cast<RbNativeNodeType *>(this->node_));
if(next_node) {
this->node_ = next_node;
} else {
this->location_ = IL_AfterEnd;
}
}
break;
case IL_AfterEnd:
break;
} /*operator++*/
return *this;
} /*next_step*/
IteratorBase & prev_step() {
switch(this->location_) {
case IL_BeforeBegin:
break;
case IL_Regular:
{
RbNode * prev_node = RbUtil::prev_inorder_node(const_cast<RbNativeNodeType *>(this->node_));
if(prev_node) {
this->node_ = prev_node;
} else {
this->location_ = IL_BeforeBegin;
}
}
break;
case IL_AfterEnd:
/* .node is already last node in tree */
this->location_ = IL_Regular;
break;
}
return *this;
} /*prev_step*/
protected:
/* ID_Forward, ID_Reverse */
IteratorDirection dirn_ = ID_Forward;
/* IL_BeforeBegin, IL_Regular, IL_AfterEnd */
IteratorLocation location_ = IL_AfterEnd;
/* location = IL_BeforeBegin: .node is leftmost node in tree
* location = IL_Regular: .node is some node in tree,
* iterator refers to that node.
* location = IL_AfterEnd: .node is rightmost node in tree
*/
RbNodePtrType node_ = nullptr;
}; /*IteratorBase*/
/* xo::tree::detail::Iterator
*
* inorder iterator over nodes in a red-black tree.
* invalidated on insert or remove operations on the parent tree.
*
* satisfies the std::bidirectional_iterator concept
*/
template <typename Key,
typename Value,
typename Reduce>
class Iterator : public IteratorBase<Key, Value, Reduce, false /*!IsConst*/> {
public:
using iterator_concept = std::bidirectional_iterator_tag;
using RbIteratorBase = IteratorBase<Key, Value, Reduce, false /*!IsConst*/>;
using RbNode = typename RbIteratorBase::RbNode;
using RbUtil = typename RbIteratorBase::RbUtil;
using ReducedValue = typename Reduce::value_type;
public:
Iterator() = default;
Iterator(IteratorDirection dirn, IteratorLocation loc, RbNode * n)
: RbIteratorBase(dirn, loc, n) {}
Iterator(Iterator const & x) = default;
Iterator(RbIteratorBase const & x) : RbIteratorBase(x) {}
Iterator(RbIteratorBase && x) : RbIteratorBase(std::move(x)) {}
static Iterator begin_aux(RbNode const * n) { return RbIteratorBase::begin_aux(n); }
static Iterator end_aux(RbNode const * n) { return RbIteratorBase::end_aux(n); }
static Iterator rbegin_aux(RbNode const * n) { return RbIteratorBase::rbegin_aux(n); }
static Iterator rend_aux(RbNode const * n) { return RbIteratorBase::rend_aux(n); }
/* pre-increment */
Iterator & operator++() {
RbIteratorBase::operator++();
return *this;
} /*operator++*/
/* post-increment */
Iterator operator++(int) {
Iterator retval = *this;
++(*this);
return retval;
} /*operator++(int)*/
/* pre-decrement */
Iterator & operator--() {
RbIteratorBase::operator--();
return *this;
} /*operator--*/
/* post-decrement */
Iterator operator--(int) {
Iterator retval = *this;
--(*this);
return retval;
} /*operator--(int)*/
}; /*Iterator*/
/* xo::tree::detail::ConstIterator
*
* inorder iterator over nodes in a red-black tree.
* invalidated on insert or remove operations on the parent tree.
*
* satisfies the std::bidirectional_iterator concept
*/
template <typename Key,
typename Value,
typename Reduce>
class ConstIterator : public IteratorBase<Key, Value, Reduce, true /*IsConst*/> {
public:
using iterator_concept = std::bidirectional_iterator_tag;
using RbIteratorBase = IteratorBase<Key, Value, Reduce, true /*IsConst*/>;
using RbNode = typename RbIteratorBase::RbNode;
using RbUtil = typename RbIteratorBase::RbUtil;
using ReducedValue = typename Reduce::value_type;
public:
ConstIterator() = default;
ConstIterator(IteratorDirection dirn, IteratorLocation loc, RbNode const * node)
: RbIteratorBase(dirn, loc, node) {}
ConstIterator(ConstIterator const & x) = default;
ConstIterator(RbIteratorBase const & x) : RbIteratorBase(x) {}
ConstIterator(RbIteratorBase && x) : RbIteratorBase(std::move(x)) {}
static ConstIterator prebegin_aux(RbNode const * n) { return RbIteratorBase::prebegin_aux(n); }
static ConstIterator begin_aux(RbNode const * n) { return RbIteratorBase::begin_aux(n); }
static ConstIterator end_aux(RbNode const * n) { return RbIteratorBase::end_aux(n); }
static ConstIterator rprebegin_aux(RbNode const * n) { return RbIteratorBase::rprebegin_aux(n); }
static ConstIterator rbegin_aux(RbNode const * n) { return RbIteratorBase::rbegin_aux(n); }
static ConstIterator rend_aux(RbNode const * n) { return RbIteratorBase::rend_aux(n); }
/* pre-increment */
ConstIterator & operator++() {
RbIteratorBase::operator++();
return *this;
} /*operator++*/
/* post-increment */
ConstIterator operator++(int) {
ConstIterator retval = *this;
++(*this);
return retval;
} /*operator++(int)*/
/* pre-decrement */
ConstIterator & operator--() {
RbIteratorBase::operator--();
return *this;
} /*operator--*/
/* post-decrement */
ConstIterator operator--(int) {
ConstIterator retval = *this;
--(*this);
return retval;
} /*operator--(int)*/
}; /*ConstIterator*/
} /*namespace detail*/
struct null_reduce_value {};
/* for null reduce, just have it return empty struct;
* otherwise breaks verification (e.g. verify_subtree_ok() below)
*/
template<typename NodeValue>
struct NullReduce {
static constexpr bool is_null_reduce() { return true; }
static constexpr bool is_monotonic() { return false; }
/* data type for reduced values */
using value_type = null_reduce_value;
value_type nil() const { return value_type(); }
value_type leaf(NodeValue const & /*x*/) const {
return nil();
}
value_type operator()(value_type /*x*/,
NodeValue const & /*value*/) const { return nil(); }
value_type combine(value_type /*x*/,
value_type /*y*/) const { return nil(); }
bool is_equal(value_type /*x*/, value_type /*y*/) const { return true; }
}; /*NullReduce*/
inline std::ostream & operator<<(std::ostream & os,
null_reduce_value /*x*/)
{
os << "{}";
return os;
} /*operator<<*/
/* just counts #of distinct values;
* redundant, same as detail::Node<>::size_.
* providing for completeness' sake
*/
template <typename Value>
class OrdinalReduce {
public:
using value_type = std::size_t;
public:
static constexpr bool is_monotonic() { return true; }
value_type nil() const { return 0; }
value_type leaf(Value const & /*x*/) const {
return 1;
} /*leaf*/
value_type operator()(value_type acc,
Value const & /*x*/) const {
/* counts #of values */
return acc + 1;
}
value_type combine(value_type x, value_type y) const { return x + y; }
bool is_equal(value_type x, value_type y) const { return x == y; }
}; /*OrdinalReduce*/
/* reduction for inverting the integral of a non-negative discrete function
* computes sum of values for each subtree
*/
template<typename Value>
struct SumReduce {
using value_type = Value;
static constexpr bool is_monotonic() { return true; }
value_type nil() const { return -std::numeric_limits<value_type>::infinity(); }
value_type leaf(Value const & x) const {
return x;
} /*leaf*/
value_type operator()(value_type reduced,
Value const & x) const {
/* sums tree values */
if(std::isfinite(reduced)) {
return reduced + x;
} else {
/* omit -oo reduced value from .nil() */
return x;
}
} /*operator()*/
value_type combine(value_type const & x,
value_type const & y) const {
/* omit -oo reduced value from .nil() */
if(!std::isfinite(x))
return y;
if(!std::isfinite(y))
return x;
return x + y;
} /*combine*/
bool is_equal(value_type const & x, value_type const & y) const { return x == y; }
}; /*SumReduce*/
/* red-black tree with order statistics
*/
template <typename Key, typename Value, typename Reduce>
class RedBlackTree {
static_assert(ReduceConcept<Reduce, Value>);
//static_assert(requires(Reduce r) { r.nil(); }, "missing .nil() method");
public:
using key_type = Key;
using mapped_type = Value;
using value_type = std::pair<Key const, Value>;
using ReducedValue = typename Reduce::value_type;
using RbTreeLhs = detail::RedBlackTreeLhs<RedBlackTree<Key, Value, Reduce>>;
using RbTreeConstLhs = detail::RedBlackTreeConstLhs<RedBlackTree<Key, Value, Reduce>>;
using RbUtil = detail::RbTreeUtil<Key, Value, Reduce>;
using RbNode = detail::Node<Key, Value, Reduce>;
using Direction = detail::Direction;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using iterator = detail::Iterator<Key, Value, Reduce>;
using const_iterator = detail::ConstIterator<Key, Value, Reduce>;
public:
RedBlackTree() = default;
bool empty() const { return size_ == 0; }
size_type size() const { return size_; }
size_type max_size() const { return std::numeric_limits<difference_type>::max(); }
Reduce const & reduce_fn() const { return reduce_fn_; }
/* forward const iterators (canonical names) */
/* iterator "one before beginning" */
const_iterator cprebegin() const {
return const_iterator::prebegin_aux(RbUtil::find_leftmost(this->root_));
} /*cprebegin*/
const_iterator cbegin() const {
return const_iterator::begin_aux(RbUtil::find_leftmost(this->root_));
} /*begin*/
const_iterator cend() const {
return const_iterator::end_aux(RbUtil::find_rightmost(this->root_));
} /*end*/
/* forward const iterators (overloaded names) */
const_iterator prebegin() const { return this->cprebegin(); }
const_iterator begin() const { return this->cbegin(); }
const_iterator end() const { return this->cend(); }
/* forward non-const iterators */
iterator prebegin() {
return iterator::prebegin_aux(RbUtil::find_leftmost(this->root_));
} /*prebegin*/
iterator begin() {
return iterator::begin_aux(RbUtil::find_leftmost(this->root_));
} /*begin*/
iterator end() {
return iterator::end_aux(RbUtil::find_rightmost(this->root_));
} /*end*/
/* reverse const iterators (canonical names) */
/* reverse-iterator, "one after end" */
const_iterator crprebegin() const {
return const_iterator::rprebegin_aux(RbUtil::find_rightmost(this->root_));
} /*crprebegin*/
const_iterator crbegin() const {
return const_iterator::rbegin_aux(RbUtil::find_rightmost(this->root_));
} /*crbegin*/
const_iterator crend() const {
return const_iterator::rend_aux(RbUtil::find_leftmost(this->root_));
} /*crend*/
/* reverse const iterators (overloaded names) */
const_iterator rprebegin() const { return this->crprebegin(); }
const_iterator rbegin() const { return this->crbegin(); }
const_iterator rend() const { return this->crend(); }
/* reverse non-const iterators */
iterator rprebegin() {
return iterator::rprebegin_aux(RbUtil::find_rightmost(this->root_));
} /*rprebegin*/
iterator rbegin() {
return iterator::rbegin_aux(RbUtil::find_rightmost(this->root_));
} /*rbegin*/
iterator rend() {
return iterator::rend_aux(RbUtil::find_leftmost(this->root_));
} /*rend*/
/* require:
* - .size() > 0
*/
Key const & min_key() const { return this->cbegin().first; }
/* require:
* - .size() > 0
*/
Key const & max_key() const { const_iterator ix = this->cend(); --ix; return ix->first; }
/* visit tree contents in increasing key order
*
* Require:
* - Fn(std::pair<Key, Value> const &)
*/
template<typename Fn>
void visit_inorder(Fn && fn) {
auto visitor_fn = [&fn](RbNode const * x, uint32_t /*d*/) { fn(x->contents()); };
RbUtil::inorder_node_visitor(this->root_,
0 /*depth -- will be ignored*/,
visitor_fn);
} /*visit_inorder*/
/* if i in [0 .. .size], return iterator referring to ith inorder node in tree
* otherwise return this->end()
*/
const_iterator find_ith(uint32_t i) const {
RbNode * node = RbUtil::find_ith(this->root_, i);
if(node) {
return const_iterator(detail::ID_Forward, detail::IL_Regular, node);
} else {
return this->end();
}
} /*find_ith*/
iterator find_ith(uint32_t i) {
RbNode * node = RbUtil::find_ith(this->root_, i);
if(node) {
return iterator(detail::IL_Regular, node);
} else {
return this->end();
}
} /*find_ith*/
/* find node with key equal to x in this tree.
* on success, return iterator ix with ix->first = x.
* on failure, return this->end()
*/
const_iterator find(Key const & x) const {
RbNode * node = RbUtil::find(this->root_, x);
if(node) {
return const_iterator(detail::ID_Forward, detail::IL_Regular, node);
} else {
return this->end();
}
} /*find*/
iterator find(Key const & x) {
RbNode * node = RbUtil::find(this->root_, x);
if (node) {
return const_iterator(detail::ID_Forward, detail::IL_Regular, node);
} else {
return this->end();
}
} /*find*/
/* find node in tree with largest key k such that:
* k <= x, if is_closed
* k < x, if !is_closed
*
* return iterator to that node.
*
* If no such node exists, return the same value as this->cprebegin();
*
* This satisfies continuity property:
* if: ix = find_glb(k, is_closed),
* then: ix+1 = find_lub(k, !is_closed)
*
* even when ix.is_dereferenceable() is false
*/
const_iterator find_glb(Key const & k, bool is_closed) const {
RbNode * node = RbUtil::find_glb(this->root_, k, is_closed);
if (node) {
return const_iterator(detail::ID_Forward,
detail::IL_Regular,
node);
} else {
return this->cprebegin();
}
} /*find_glb*/
const_iterator find_lub(Key const & k, bool is_closed) const {
const_iterator ix = this->find_glb(k, !is_closed);
return ++ix;
} /*find_lub*/
/* RbTreeConstLhs provides rvalue-substitute for lookup-only in const RedBlackTree
* instances
*/
RbTreeConstLhs operator[](Key const & k) const
{
RbNode const * node = RbUtil::find(this->root_, k);
return RbTreeConstLhs(this, node);
} /*operator[]*/
/* RbTreeLhs defers assignment, so that rbtree can update values of
* Node::reduce along path from root to Node n with n.key = k
*
*
* Note:
* 1. return value remains valid across subsequent inserts and assignments,
* so this is legal:
* RbTree rbtree = ...;
* auto v = rbtree[key1];
*
* rbtree[key2] = ...;
* rbtree.insert(key3, value3);
*
* v = ...;
*
* 2. return value is not valid across removes, even of distinct keys,
* so this is ILLEGAL:
* RbTree rbtree = ...;
* auto v = rbtree[key1];
*
* assert(key1 != key2);
*
* rbtree.remove(key2);
*
* v = ...; // undefined behavior,
* // v.node contents may have been copied and v.node deleted
*/
RbTreeLhs operator[](Key const & k) {
std::pair<bool, RbNode *> insert_result
= RbUtil::insert_aux(value_type(k, Value() /*used iff creating new node*/),
false /*allow_replace_flag*/,
this->reduce_fn_,
&(this->root_));
return RbTreeLhs(this, insert_result.second, k);
} /*operator[]*/
/* compute value of reduce applied to the set K of all keys k[j] in subtree
* N with:
* - k[j] <= lub_key if is_closed = true
* - k[j] < lub_key if is_closed = false
* return reduce_fn.nil() if K is empty
*/
ReducedValue reduce_lub(Key const &lub_key, bool is_closed) const {
return RbUtil::reduce_lub(lub_key,
this->reduce_fn_,
is_closed,
this->root_);
} /*reduce_lub*/
/* Provided Reduce computes sum, and we call this rbtree f
* with keys k[i] and values v[i]:
*
* returns iterator pointing to i'th key-value pair {k[i],v[i]} in this tree,
* with reduced value r(i) (i.e. RbNode::reduced1);
* where r(i) is the result of reducing all values v[j] with j<=i
*
* editor bait: invert_integral
*/
const_iterator cfind_sum_glb(ReducedValue const & y) const {
using xo::tostr;
using xo::xtag;
//char const * c_self = "RedBlackTree::find_sum_glb";
RbNode * N = RbUtil::find_sum_glb(this->reduce_fn_,
this->root_,
y);
if(!N) {
/* for no-lower-bound edge cases, return iterator ix
* pointing to 'before the beginning' of this tree.
*
* will have
* ix.is_deferenceable() == false
* (bool)ix == false
*/
return const_iterator(detail::ID_Forward,
detail::IL_BeforeBegin,
RbUtil::find_leftmost(this->root_));
}
return const_iterator(detail::ID_Forward,
detail::IL_Regular,
N);
} /*cfind_sum_glb*/
const_iterator find_sum_glb(ReducedValue const & y) const {
return this->cfind_sum_glb(y);
} /*find_sum_glb*/
/* non-const version of .cfind_sum_glb() */
iterator find_sum_glb(ReducedValue const & y) {
const_iterator ix = this->cfind_sum_glb(y);
return iterator(ix.location(),
const_cast<RbNode *>(ix.node()));
} /*find_sum_glb*/
void clear() {
auto visitor_fn = [](RbNode const * x, uint32_t /*d*/) {
/* RbUtil.postorder_node_visitor() isn't expecting us to
* alter node, but will not examine it after it's deleted
*/
RbNode * xx = const_cast<RbNode *>(x);
delete xx;
};
RbUtil::postorder_node_visitor(this->root_,
0 /*depth -- ignored by lambda*/,
visitor_fn);
this->size_ = 0;
this->root_ = nullptr;
} /*clear*/
std::pair<iterator, bool>
insert(std::pair<Key const, Value> const & kv_pair) {
std::pair<bool, RbNode *> insert_result
= RbUtil::insert_aux(kv_pair,
true /*allow_replace_flag*/,
this->reduce_fn_,
&(this->root_));
if (insert_result.first)
++(this->size_);
return (std::pair<iterator, bool>
(iterator(detail::ID_Forward,
detail::IL_Regular,
insert_result.second),
insert_result.first));
} /*insert*/
std::pair<iterator, bool>
insert(std::pair<Key const, Value> && kv_pair) {
using xo::scope;
using xo::xtag;
constexpr bool c_logging_enabled = false;
scope log(XO_DEBUG(c_logging_enabled));
std::pair<bool, RbNode *> insert_result
= RbUtil::insert_aux(std::move(kv_pair),
true /*allow_replace_flag*/,
this->reduce_fn_,
&(this->root_));
if (insert_result.first)
++(this->size_);
return (std::pair<iterator, bool>
(iterator(detail::ID_Forward,
detail::IL_Regular,
insert_result.second),
insert_result.first));
} /*insert*/
bool erase(Key const & k) {
bool retval = RbUtil::erase_aux(k,
this->reduce_fn_,
&(this->root_));
if (retval)
--(this->size_);
return retval;
} /*erase*/
/* verify class invariants.
* unless implementation is broken, or client manages
* to violate api rules, this will always return true.
*
* RB0. if root node is nil then .size is 0
* RB1. if root node is non-nil, then root->parent() is nil,
* and .size = root->size
* RB2. if N = P->child(d), then N->parent()=P
* RB3. all paths to leaves have the same black height
* RB4. no red node has a red parent
* RB5. inorder traversal visits keys in monotonically increasing order
* RB6. Node::size reports the size of the subtree reachable from that node
* via child pointers
* RB7. Node::reduced reports the value of
* f(f(L, Node::value), R)
* where: L is reduced-value for left child,
* R is reduced-value for right child
* RB8. RedBlackTree.size() equals the #of nodes in tree
*/
bool verify_ok(bool /*throw_flag_not_implemented*/ = true) const {
using xo::scope;
using xo::tostr;
using xo::xtag;
constexpr const char *c_self = "RedBlackTree::verify_ok";
constexpr bool c_logging_enabled = false;
scope log(XO_DEBUG(c_logging_enabled));
/* RB0. */
if (root_ == nullptr) {
XO_EXPECT(size_ == 0, tostr(c_self, ": expect .size=0 with null root",
xtag("size", size_)));
}
/* RB1. */
if (root_ != nullptr) {
XO_EXPECT(root_->parent_ == nullptr,
tostr(c_self, ": expect root->parent=nullptr",
xtag("parent", root_->parent_)));
XO_EXPECT(root_->size_ == this->size_,
tostr(c_self, ": expect self.size=root.size",
xtag("self.size", size_),
xtag("root.size", root_->size_)));
}
/* height (counting only black nodes) of tree */
int32_t black_height = 0;
/* n_node: #of nodes in this->root_ */
size_t n_node = RbUtil::verify_subtree_ok(this->reduce_fn_,
this->root_,
&black_height);
/* RB8. RedBlackTree.size() equals #of nodes in tree */
XO_EXPECT(n_node == this->size_,
tostr(c_self, ": expect self.size={#of nodes n in tree}",
xtag("self.size", size_),
xtag("n", n_node)));
if (c_logging_enabled)
log && log(xtag("size", this->size_),
xtag("blackheight", black_height));
return true;
} /*verify_ok*/
void display() const { RbUtil::display(this->root_, 0); } /*display*/
private:
/* #of key/value pairs in this tree */
size_t size_ = 0;
/* root of red/black tree */
RbNode * root_ = nullptr;
/* .reduce_fn :: (Accumulator x Key) -> Accumulator */
Reduce reduce_fn_;
}; /*RedBlackTree*/
template <typename Key,
typename Value,
typename Reduce>
inline std::ostream &
operator<<(std::ostream &os,
RedBlackTree<Key, Value, Reduce> const &tree)
{
tree.display();
return os;
} /*operator<<*/
template <typename Key,
typename Value,
typename Reduce,
bool IsConst>
inline std::ostream &
operator<<(std::ostream & os,
detail::IteratorBase<Key, Value, Reduce, IsConst> const & iter)
{
iter.print(os);
return os;
} /*operator<<*/
} /*namespace tree*/
} /*namespace xo*/
/* end RedBlackTree.hpp */