Files
email-tracker/external/duckdb/third_party/skiplist/Node.h
2025-10-24 19:21:19 -05:00

642 lines
21 KiB
C++
Executable File

/**
* @file
*
* Project: skiplist
*
* Concurrency Tests.
*
* Created by Paul Ross on 03/12/2015.
*
* Copyright (c) 2015-2023 Paul Ross. All rights reserved.
*
* @code
* MIT License
*
* Copyright (c) 2015-2023 Paul Ross
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
* @endcode
*/
#ifndef SkipList_Node_h
#define SkipList_Node_h
#include "IntegrityEnums.h"
#if __cplusplus < 201103L
#define nullptr NULL
#endif
/**************************** Node *********************************/
/**
* @brief A single node in a Skip List containing a value and references to other downstream Node objects.
*
* @tparam T The type of the Skip List Node values.
* @tparam _Compare A comparison function for type T.
*/
template <typename T, typename _Compare>
class Node {
public:
struct _Pool {
explicit _Pool(_Compare _cmp) : _compare(_cmp), cache(nullptr) {
}
~_Pool() {
delete cache;
}
Node *Allocate(const T &value) {
if (cache) {
Node *result = cache;
cache = nullptr;
result->Initialize(value);
return result;
}
return new Node(value, _compare, *this);
}
T Release(Node *pNode) {
T result = pNode->value();
std::swap(pNode, cache);
delete pNode;
return result;
}
_Compare _compare;
Node* cache;
pcg32_fast prng;
};
Node(const T &value, _Compare _cmp, _Pool &pool);
// Const methods
//
/// Returns the node value
const T &value() const { return _value; }
// Returns true if the value is present in the skip list from this node onwards.
bool has(const T &value) const;
// Returns the value at the index in the skip list from this node onwards.
// Will return nullptr is not found.
const Node<T, _Compare> *at(size_t idx) const;
// Computes index of the first occurrence of a value
bool index(const T& value, size_t &idx, size_t level) const;
/// Number of linked lists that this node engages in, minimum 1.
size_t height() const { return _nodeRefs.height(); }
// Return the pointer to the next node at level 0
const Node<T, _Compare> *next() const;
// Return the width at given level.
size_t width(size_t level) const;
// Return the node pointer at given level, only used for HeadNode
// integrity checks.
const Node<T, _Compare> *pNode(size_t level) const;
// Non-const methods
/// Get a reference to the node references
SwappableNodeRefStack<T, _Compare> &nodeRefs() { return _nodeRefs; }
/// Get a reference to the node references
const SwappableNodeRefStack<T, _Compare> &nodeRefs() const { return _nodeRefs; }
// Insert a node
Node<T, _Compare> *insert(const T &value);
// Remove a node
Node<T, _Compare> *remove(size_t call_level, const T &value);
// An estimate of the number of bytes used by this node
size_t size_of() const;
#ifdef INCLUDE_METHODS_THAT_USE_STREAMS
void dotFile(std::ostream &os, size_t suffix = 0) const;
void writeNode(std::ostream &os, size_t suffix = 0) const;
#endif // INCLUDE_METHODS_THAT_USE_STREAMS
// Integrity checks, returns non-zero on failure
IntegrityCheck lacksIntegrity(size_t headnode_height) const;
IntegrityCheck lacksIntegrityRefsInSet(const std::set<const Node<T, _Compare>*> &nodeSet) const;
protected:
Node<T, _Compare> *_adjRemoveRefs(size_t level, Node<T, _Compare> *pNode);
void Initialize(const T &value) {
_value = value;
_nodeRefs.clear();
do {
_nodeRefs.push_back(this, _nodeRefs.height() ? 0 : 1);
} while (_pool.prng() < _pool.prng.max() / 2);
}
protected:
T _value;
SwappableNodeRefStack<T, _Compare> _nodeRefs;
// Comparison function
_Compare _compare;
_Pool &_pool;
private:
// Prevent cctor and operator=
Node(const Node &that);
Node &operator=(const Node &that) const;
};
/**
* Constructor.
* This also creates a SwappableNodeRefStack of random height by tossing a virtual coin.
*
* @tparam T The type of the Skip List Node values.
* @tparam _Compare A comparison function for type T.
* @param value The value of the Node.
* @param _cmp The comparison function.
*/
template <typename T, typename _Compare>
Node<T, _Compare>::Node(const T &value, _Compare _cmp, _Pool &pool) : \
_value(value), _compare(_cmp), _pool(pool) {
Initialize(value);
}
/**
* Returns true if the value is present in the skip list from this node onwards.
*
* @tparam T The type of the Skip List Node values.
* @tparam _Compare A comparison function for type T.
* @param value The value to look for.
* @return true if the value is present in the skip list from this node onwards.
*/
template <typename T, typename _Compare>
bool Node<T, _Compare>::has(const T &value) const {
assert(_nodeRefs.height());
assert(value == value); // value can not be NaN for example
// Effectively: if (value > _value) {
if (_compare(_value, value)) {
for (size_t l = _nodeRefs.height(); l-- > 0;) {
if (_nodeRefs[l].pNode && _nodeRefs[l].pNode->has(value)) {
return true;
}
}
return false;
}
// Effectively: return value == _value; // false if value smaller
return !_compare(value, _value) && !_compare(_value, value);
}
/**
* Return a pointer to the n'th node.
* Start (or continue) from the highest level, drop down a level if not found.
* Return nullptr if not found at level 0.
*
* @tparam T The type of the Skip List Node values.
* @tparam _Compare A comparison function for type T.
* @param idx The index from hereon. If zero return this.
* @return Pointer to the Node or nullptr.
*/
template <typename T, typename _Compare>
const Node<T, _Compare> *Node<T, _Compare>::at(size_t idx) const {
assert(_nodeRefs.height());
if (idx == 0) {
return this;
}
for (size_t l = _nodeRefs.height(); l-- > 0;) {
if (_nodeRefs[l].pNode && _nodeRefs[l].width <= idx) {
return _nodeRefs[l].pNode->at(idx - _nodeRefs[l].width);
}
}
return nullptr;
}
/**
* Computes index of the first occurrence of a value.
*
* @tparam T The type of the Skip List Node values.
* @tparam _Compare A comparison function for type T.
* @param value The value to find.
* @param idx The current index, this will be updated.
* @param level The current level to search from.
* @return true if found, false otherwise.
*/
template <typename T, typename _Compare>
bool Node<T, _Compare>::index(const T& value, size_t &idx, size_t level) const {
assert(_nodeRefs.height());
assert(value == value); // value can not be NaN for example
assert(level < _nodeRefs.height());
// Search has overshot, try again at a lower level.
//if (_value > value) {
if (_compare(value, _value)) {
return false;
}
// First check if we match but we have been approached at a high level
// as there may be an earlier node of the same value but with fewer
// node references. In that case this search has to fail and try at a
// lower level.
// If however the level is 0 and we match then set the idx to 0 to mark us.
// Effectively: if (_value == value) {
if (!_compare(value, _value) && !_compare(_value, value)) {
if (level > 0) {
return false;
}
idx = 0;
return true;
}
// Now work our way down
// NOTE: We initialise l as level + 1 because l-- > 0 will decrement it to
// the correct initial value
for (size_t l = level + 1; l-- > 0;) {
assert(l < _nodeRefs.height());
if (_nodeRefs[l].pNode && _nodeRefs[l].pNode->index(value, idx, l)) {
idx += _nodeRefs[l].width;
return true;
}
}
return false;
}
/**
* Return the pointer to the next node at level 0.
*
* @tparam T The type of the Skip List Node values.
* @tparam _Compare A comparison function for type T.
* @return The next node at level 0.
*/
template <typename T, typename _Compare>
const Node<T, _Compare> *Node<T, _Compare>::next() const {
assert(_nodeRefs.height());
return _nodeRefs[0].pNode;
}
/**
* Return the width at given level.
*
* @tparam T The type of the Skip List Node values.
* @tparam _Compare A comparison function for type T.
* @param level The requested level.
* @return The width.
*/
template <typename T, typename _Compare>
size_t Node<T, _Compare>::width(size_t level) const {
assert(level < _nodeRefs.height());
return _nodeRefs[level].width;
}
/**
* Return the node pointer at given level, only used for HeadNode integrity checks.
*
* @tparam T The type of the Skip List Node values.
* @tparam _Compare A comparison function for type T.
* @param level The requested level.
* @return The Node.
*/
template <typename T, typename _Compare>
const Node<T, _Compare> *Node<T, _Compare>::pNode(size_t level) const {
assert(level < _nodeRefs.height());
return _nodeRefs[level].pNode;
}
/**
* Insert a new node with a value.
*
* @tparam T The type of the Skip List Node values.
* @tparam _Compare A comparison function for type T.
* @param value The value of the Node to insert.
* @return Pointer to the new Node or nullptr on failure.
*/
template <typename T, typename _Compare>
Node<T, _Compare> *Node<T, _Compare>::insert(const T &value) {
assert(_nodeRefs.height());
assert(_nodeRefs.noNodePointerMatches(this));
assert(! _nodeRefs.canSwap());
assert(value == value); // NaN check for double
// Effectively: if (value < _value) {
if (_compare(value, _value)) {
return nullptr;
}
// Recursive search for where to put the node
Node<T, _Compare> *pNode = nullptr;
size_t level = _nodeRefs.height();
// Effectively: if (value >= _value) {
if (! _compare(value, _value)) {
for (level = _nodeRefs.height(); level-- > 0;) {
if (_nodeRefs[level].pNode) {
pNode = _nodeRefs[level].pNode->insert(value);
if (pNode) {
break;
}
}
}
}
// Effectively: if (! pNode && value >= _value) {
if (! pNode && !_compare(value, _value)) {
// Insert new node here
pNode = _pool.Allocate(value);
level = 0;
}
assert(pNode); // Should never get here unless a NaN has slipped through
// Adjust references by marching up and recursing back.
SwappableNodeRefStack<T, _Compare> &thatRefs = pNode->_nodeRefs;
if (! thatRefs.canSwap()) {
// Have an existing node or new node that is all swapped.
// All I need to do is adjust my overshooting nodes and return
// this for the caller to do the same.
level = thatRefs.height();
while (level < _nodeRefs.height()) {
_nodeRefs[level].width += 1;
++level;
}
// The caller just has to increment its references that overshoot this
assert(! _nodeRefs.canSwap());
return this;
}
// March upwards
if (level < thatRefs.swapLevel()) {
assert(level == thatRefs.swapLevel() - 1);
// This will happen when say a 3 high node, A, finds a 2 high
// node, B, that creates a new 2+ high node. A will be at
// level 1 and the new node will have swapLevel == 2 after
// B has swapped.
// Add the level to the accumulator at the next level
thatRefs[thatRefs.swapLevel()].width += _nodeRefs[level].width;
++level;
}
size_t min_height = std::min(_nodeRefs.height(), thatRefs.height());
while (level < min_height) {
assert(thatRefs.canSwap());
assert(level == thatRefs.swapLevel());
assert(level < thatRefs.height());
assert(_nodeRefs[level].width > 0);
assert(thatRefs[level].width > 0);
_nodeRefs[level].width -= thatRefs[level].width - 1;
assert(_nodeRefs[level].width > 0);
thatRefs.swap(_nodeRefs);
if (thatRefs.canSwap()) {
assert(thatRefs[thatRefs.swapLevel()].width == 0);
thatRefs[thatRefs.swapLevel()].width = _nodeRefs[level].width;
}
++level;
}
// Upwards march complete, now recurse back ('left').
if (! thatRefs.canSwap()) {
// All done with pNode locally.
assert(level == thatRefs.height());
assert(thatRefs.height() <= _nodeRefs.height());
assert(level == thatRefs.swapLevel());
// Adjust my overshooting nodes
while (level < _nodeRefs.height()) {
_nodeRefs[level].width += 1;
++level;
}
// The caller just has to increment its references that overshoot this
assert(! _nodeRefs.canSwap());
pNode = this;
}
return pNode;
}
/**
* Adjust the Node references.
*
* @tparam T The type of the Skip List Node values.
* @tparam _Compare A comparison function for type T.
* @param level The level of the caller's node.
* @param pNode The Node to swap references with.
* @return The Node with swapped references.
*/
template <typename T, typename _Compare>
Node<T, _Compare> *Node<T, _Compare>::_adjRemoveRefs(size_t level, Node<T, _Compare> *pNode) {
assert(pNode);
SwappableNodeRefStack<T, _Compare> &thatRefs = pNode->_nodeRefs;
assert(pNode != this);
if (level < thatRefs.swapLevel()) {
assert(level == thatRefs.swapLevel() - 1);
++level;
}
if (thatRefs.canSwap()) {
assert(level == thatRefs.swapLevel());
while (level < _nodeRefs.height() && thatRefs.canSwap()) {
assert(level == thatRefs.swapLevel());
// Compute the new width for the new node
thatRefs[level].width += _nodeRefs[level].width - 1;
thatRefs.swap(_nodeRefs);
++level;
}
assert(thatRefs.canSwap() || thatRefs.allNodePointerMatch(pNode));
}
// Decrement my widths as my refs are over the top of the missing pNode.
while (level < _nodeRefs.height()) {
_nodeRefs[level].width -= 1;
++level;
thatRefs.incSwapLevel();
}
assert(! _nodeRefs.canSwap());
return pNode;
}
/**
* Remove a Node with the given value to be removed.
* The return value must be deleted, the other Nodes have been adjusted as required.
*
* @tparam T The type of the Skip List Node values.
* @tparam _Compare A comparison function for type T.
* @param call_level Level the caller Node is at.
* @param value Value of the detached Node to remove.
* @return A pointer to the Node to be free'd or nullptr on failure.
*/
template <typename T, typename _Compare>
Node<T, _Compare> *Node<T, _Compare>::remove(size_t call_level,
const T &value) {
assert(_nodeRefs.height());
assert(_nodeRefs.noNodePointerMatches(this));
Node<T, _Compare> *pNode = nullptr;
// Effectively: if (value >= _value) {
if (!_compare(value, _value)) {
for (size_t level = call_level + 1; level-- > 0;) {
if (_nodeRefs[level].pNode) {
// Make progress to the right
pNode = _nodeRefs[level].pNode->remove(level, value);
if (pNode) {
return _adjRemoveRefs(level, pNode);
}
}
// Make progress down
}
}
if (! pNode) { // Base case
// We only admit to being the node to remove if the caller is
// approaching us from level 0. It is entirely likely that
// the same (or an other) caller can see us at a higher level
// but the recursion stack will not have been set up in the correct
// step wise fashion so that the lower level references will
// not be swapped.
// Effectively: if (call_level == 0 && value == _value) {
if (call_level == 0 && !_compare(value, _value) && !_compare(_value, value)) {
_nodeRefs.resetSwapLevel();
return this;
}
}
assert(pNode == nullptr);
return nullptr;
}
/*
* This checks the internal consistency of a Node. It returns 0
* if successful, non-zero on error. The tests are:
*
* - Height must be >= 1
* - Height must not exceed HeadNode height.
* - NULL pointer must not have a non-NULL above them.
* - Node pointers must not be self-referential.
*/
/**
* This checks the internal consistency of a Node. It returns 0
* if successful, non-zero on error. The tests are:
*
* - Height must be >= 1
* - Height must not exceed HeadNode height.
* - NULL pointer must not have a non-NULL above them.
* - Node pointers must not be self-referential.
*
* @tparam T The type of the Skip List Node values.
* @tparam _Compare A comparison function for type T.
* @param headnode_height Height of HeadNode.
* @return An IntegrityCheck enum.
*/
template <typename T, typename _Compare>
IntegrityCheck Node<T, _Compare>::lacksIntegrity(size_t headnode_height) const {
IntegrityCheck result = _nodeRefs.lacksIntegrity();
if (result) {
return result;
}
if (_nodeRefs.height() == 0) {
return NODE_HEIGHT_ZERO;
}
if (_nodeRefs.height() > headnode_height) {
return NODE_HEIGHT_EXCEEDS_HEADNODE;
}
// Test: All nodes above a nullprt must be nullptr
size_t level = 0;
while (level < _nodeRefs.height()) {
if (! _nodeRefs[level].pNode) {
break;
}
++level;
}
while (level < _nodeRefs.height()) {
if (_nodeRefs[level].pNode) {
return NODE_NON_NULL_AFTER_NULL;
}
++level;
}
// No reference should be to self.
if (! _nodeRefs.noNodePointerMatches(this)) {
return NODE_SELF_REFERENCE;
}
return INTEGRITY_SUCCESS;
}
/**
* Checks that this Node is in the set held by the HeadNode.
*
* @tparam T The type of the Skip List Node values.
* @tparam _Compare A comparison function for type T.
* @param nodeSet Set of Nodes held by the HeadNode.
* @return An IntegrityCheck enum.
*/
template <typename T, typename _Compare>
IntegrityCheck Node<T, _Compare>::lacksIntegrityRefsInSet(const std::set<const Node<T, _Compare>*> &nodeSet) const {
size_t level = 0;
while (level < _nodeRefs.height()) {
if (nodeSet.count(_nodeRefs[level].pNode) == 0) {
return NODE_REFERENCES_NOT_IN_GLOBAL_SET;
}
++level;
}
return INTEGRITY_SUCCESS;
}
/**
* Returns an estimate of the memory usage of an instance.
*
* @tparam T The type of the Skip List Node values.
* @tparam _Compare A comparison function for type T.
* @return The memory estimate of this Node.
*/
template <typename T, typename _Compare>
size_t Node<T, _Compare>::size_of() const {
// sizeof(*this) includes the size of _nodeRefs but _nodeRefs.size_of()
// includes sizeof(_nodeRefs) so we need to subtract to avoid double counting
return sizeof(*this) + _nodeRefs.size_of() - sizeof(_nodeRefs);
}
#ifdef INCLUDE_METHODS_THAT_USE_STREAMS
/**
* Writes out this Node address.
*
* @tparam T The type of the Skip List Node values.
* @tparam _Compare A comparison function for type T.
* @param os Where to write.
* @param suffix The suffix (node number).
*/
template <typename T, typename _Compare>
void Node<T, _Compare>::writeNode(std::ostream &os, size_t suffix) const {
os << "\"node";
os << suffix;
os << std::hex << this << std::dec << "\"";
}
/**
* Writes out a fragment of a DOT file representing this Node.
*
* @tparam T The type of the Skip List Node values.
* @tparam _Compare A comparison function for type T.
* @param os Wheere to write.
* @param suffix The node number.
*/
template <typename T, typename _Compare>
void Node<T, _Compare>::dotFile(std::ostream &os, size_t suffix) const {
assert(_nodeRefs.height());
writeNode(os, suffix);
os << " [" << std::endl;
os << "label = \"";
for (size_t level = _nodeRefs.height(); level-- > 0;) {
os << " { <w" << level + 1 << "> " << _nodeRefs[level].width;
os << " | <f" << level + 1 << "> ";
os << std::hex << _nodeRefs[level].pNode << std::dec;
os << " }";
os << " |";
}
os << " <f0> " << _value << "\"" << std::endl;
os << "shape = \"record\"" << std::endl;
os << "];" << std::endl;
// Now edges
for (size_t level = 0; level < _nodeRefs.height(); ++level) {
writeNode(os, suffix);
os << ":f" << level + 1 << " -> ";
_nodeRefs[level].pNode->writeNode(os, suffix);
// writeNode(os, suffix);
// os << ":f" << i + 1 << " [];" << std::endl;
os << ":w" << level + 1 << " [];" << std::endl;
}
assert(_nodeRefs.height());
if (_nodeRefs[0].pNode) {
_nodeRefs[0].pNode->dotFile(os, suffix);
}
}
#endif // INCLUDE_METHODS_THAT_USE_STREAMS
/************************** END: Node *******************************/
#endif // SkipList_Node_h