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

935 lines
30 KiB
C++
Executable File

/**
* @file
*
* Project: skiplist
*
* Created by Paul Ross on 03/12/2015.
*
* Copyright (c) 2015-2023 Paul Ross. All rights reserved.
*
* @code
* MIT License
*
* Copyright (c) 2017-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_HeadNode_h
#define SkipList_HeadNode_h
#include <functional>
//#ifdef SKIPLIST_THREAD_SUPPORT
// #include <mutex>
//#endif
#include <vector>
#ifdef INCLUDE_METHODS_THAT_USE_STREAMS
#include <sstream>
#endif // INCLUDE_METHODS_THAT_USE_STREAMS
#include "IntegrityEnums.h"
/** HeadNode
*
* @brief A HeadNode is a skip list. This is the single node leading to all other content Nodes.
*
* Example:
*
* @code
* OrderedStructs::SkipList::HeadNode<double> sl;
* for (int i = 0; i < 100; ++i) {
* sl.insert(i * 22.0 / 7.0);
* }
* sl.size(); // 100
* sl.at(50); // Value of 50 pi
* sl.remove(sl.at(50)); // Remove 50 pi
* @endcode
*
* Created by Paul Ross on 03/12/2015.
*
* Copyright (c) 2015-2023 Paul Ross. All rights reserved.
*
* @tparam T The type of the Skip List Node values.
* @tparam _Compare A comparison function for type T.
*/
template <typename T, typename _Compare=std::less<T>>
class HeadNode {
public:
/**
* Constructor for and Empty Skip List.
*
* @param cmp The comparison function for comparing Node values.
*/
HeadNode(_Compare cmp=_Compare()) : _count(0), _compare(cmp), _pool(cmp) {
#ifdef INCLUDE_METHODS_THAT_USE_STREAMS
_dot_file_subgraph = 0;
#endif
}
// Const methods
//
// Returns true if the value is present in the skip list.
bool has(const T &value) const;
// Returns the value at the index in the skip list.
// Will throw an OrderedStructs::SkipList::IndexError if index out of range.
const T &at(size_t index) const;
// Find the value at index and write count values to dest.
// Will throw a SkipList::IndexError if any index out of range.
// This is useful for rolling median on even length lists where
// the caller might want to implement the mean of two values.
void at(size_t index, size_t count, std::vector<T> &dest) const;
// Computes index of the first occurrence of a value
// Will throw a ValueError if the value does not exist in the skip list
size_t index(const T& value) const;
// Number of values in the skip list.
size_t size() const;
// Non-const methods
//
// Insert a value.
void insert(const T &value);
// Remove a value and return it.
// Will throw a ValueError is value not present.
T remove(const T &value);
// Const methods that are mostly used for debugging and visualisation.
//
// Number of linked lists that are in the skip list.
size_t height() const;
// Number of linked lists that the node at index has.
// Will throw a SkipList::IndexError if idx out of range.
size_t height(size_t idx) const;
// The skip width of the node at index has.
// May throw a SkipList::IndexError
size_t width(size_t idx, size_t level) const;
#ifdef INCLUDE_METHODS_THAT_USE_STREAMS
void dotFile(std::ostream &os) const;
void dotFileFinalise(std::ostream &os) const;
#endif // INCLUDE_METHODS_THAT_USE_STREAMS
// Returns non-zero if the integrity of this data structure is compromised
// This is a thorough but expensive check!
IntegrityCheck lacksIntegrity() const;
// Estimate of the number of bytes used by the skip list
size_t size_of() const;
virtual ~HeadNode();
protected:
void _adjRemoveRefs(size_t level, Node<T, _Compare> *pNode);
const Node<T, _Compare> *_nodeAt(size_t idx) const;
protected:
// Standardised way of throwing a ValueError
void _throwValueErrorNotFound(const T &value) const;
void _throwIfValueDoesNotCompare(const T &value) const;
// Internal integrity checks
IntegrityCheck _lacksIntegrityCyclicReferences() const;
IntegrityCheck _lacksIntegrityWidthAccumulation() const;
IntegrityCheck _lacksIntegrityNodeReferencesNotInList() const;
IntegrityCheck _lacksIntegrityOrder() const;
protected:
/// Number of nodes in the list.
size_t _count;
/// My node references, the size of this is the largest height in the list
SwappableNodeRefStack<T, _Compare> _nodeRefs;
/// Comparison function.
_Compare _compare;
typename Node<T, _Compare>::_Pool _pool;
#ifdef INCLUDE_METHODS_THAT_USE_STREAMS
/// Used to count how many sub-graphs have been plotted
mutable size_t _dot_file_subgraph;
#endif
private:
/// Prevent cctor and operator=
HeadNode(const HeadNode &that);
HeadNode &operator=(const HeadNode &that) const;
};
/**
* Returns true if the value is present in the skip list.
*
* @tparam T Type of the values in the Skip List.
* @tparam _Compare Compare function.
* @param value Value to check if it is in the Skip List.
* @return true if in the Skip List.
*/
template <typename T, typename _Compare>
bool HeadNode<T, _Compare>::has(const T &value) const {
_throwIfValueDoesNotCompare(value);
#ifdef SKIPLIST_THREAD_SUPPORT
std::lock_guard<std::mutex> lock(gSkipListMutex);
#endif
for (size_t l = _nodeRefs.height(); l-- > 0;) {
assert(_nodeRefs[l].pNode);
if (_nodeRefs[l].pNode->has(value)) {
return true;
}
}
return false;
}
/**
* Returns the value at a particular index.
* Will throw an OrderedStructs::SkipList::IndexError if index out of range.
*
* If @ref SKIPLIST_THREAD_SUPPORT is defined this will block.
*
* See _throw_exceeds_size() that does the throw.
*
* @tparam T Type of the values in the Skip List.
* @tparam _Compare Compare function.
* @param index The index.
* @return The value at that index.
*/
template <typename T, typename _Compare>
const T &HeadNode<T, _Compare>::at(size_t index) const {
#ifdef SKIPLIST_THREAD_SUPPORT
std::lock_guard<std::mutex> lock(gSkipListMutex);
#endif
const Node<T, _Compare> *pNode = _nodeAt(index);
assert(pNode);
return pNode->value();
}
/**
* Find the count number of value starting at index and write them to dest.
*
* Will throw a OrderedStructs::SkipList::IndexError if any index out of range.
*
* This is useful for rolling median on even length lists where the caller might want to implement the mean of two
* values.
*
* @tparam T Type of the values in the Skip List.
* @tparam _Compare Compare function.
* @param index The index.
* @param count The number of values to retrieve.
* @param dest The vector of values
*/
template <typename T, typename _Compare>
void HeadNode<T, _Compare>::at(size_t index, size_t count,
std::vector<T> &dest) const {
#ifdef SKIPLIST_THREAD_SUPPORT
std::lock_guard<std::mutex> lock(gSkipListMutex);
#endif
dest.clear();
const Node<T, _Compare> *pNode = _nodeAt(index);
// _nodeAt will (should) throw an IndexError so this
// assert should always be true
assert(pNode);
while (count) {
if (! pNode) {
_throw_exceeds_size(_count);
}
dest.push_back(pNode->value());
pNode = pNode->next();
--count;
}
}
/**
* Computes index of the first occurrence of a value
* Will throw a OrderedStructs::SkipList::ValueError if the value does not exist in the skip list
* Will throw a OrderedStructs::SkipList::FailedComparison if the value is not comparable.
*
* @tparam T Type of the values in the Skip List.
* @tparam _Compare Compare function.
* @param value The value to search for.
* @return
*/
template <typename T, typename _Compare>
size_t HeadNode<T, _Compare>::index(const T& value) const {
_throwIfValueDoesNotCompare(value);
size_t idx;
#ifdef SKIPLIST_THREAD_SUPPORT
std::lock_guard<std::mutex> lock(gSkipListMutex);
#endif
for (size_t l = _nodeRefs.height(); l-- > 0;) {
assert(_nodeRefs[l].pNode);
if (_nodeRefs[l].pNode->index(value, idx, l)) {
idx += _nodeRefs[l].width;
assert(idx > 0);
return idx - 1;
}
}
_throwValueErrorNotFound(value);
return 0;
}
/**
* Return the number of values in the Skip List.
*
* @tparam T Type of the values in the Skip List.
* @tparam _Compare Compare function.
* @return The number of values in the Skip List.
*/
template <typename T, typename _Compare>
size_t HeadNode<T, _Compare>::size() const {
return _count;
}
template <typename T, typename _Compare>
size_t HeadNode<T, _Compare>::height() const {
#ifdef SKIPLIST_THREAD_SUPPORT
std::lock_guard<std::mutex> lock(gSkipListMutex);
#endif
size_t val = _nodeRefs.height();
return val;
}
/**
* Return the number of linked lists that the node at index has.
*
* Will throw a OrderedStructs::SkipList::IndexError if the index out of range.
*
* @tparam T Type of the values in the Skip List.
* @tparam _Compare Compare function.
* @param idx The index of the Skip List node.
* @return The number of linked lists that the node at the index has.
*/
template <typename T, typename _Compare>
size_t HeadNode<T, _Compare>::height(size_t idx) const {
#ifdef SKIPLIST_THREAD_SUPPORT
std::lock_guard<std::mutex> lock(gSkipListMutex);
#endif
const Node<T, _Compare> *pNode = _nodeAt(idx);
assert(pNode);
return pNode->height();
}
/**
* The skip width of the Node at index has at the given level.
* Will throw an IndexError if the index is out of range.
*
* @tparam T Type of the values in the Skip List.
* @tparam _Compare Compare function.
* @param idx The index.
* @param level The level.
* @return Width of Node.
*/
template <typename T, typename _Compare>
size_t HeadNode<T, _Compare>::width(size_t idx, size_t level) const {
#ifdef SKIPLIST_THREAD_SUPPORT
std::lock_guard<std::mutex> lock(gSkipListMutex);
#endif
// Will throw if out of range.
const Node<T, _Compare> *pNode = _nodeAt(idx);
assert(pNode);
if (level >= pNode->height()) {
_throw_exceeds_size(pNode->height());
}
return pNode->nodeRefs()[level].width;
}
/**
* Find the Node at the given index.
* Will throw an IndexError if the index is out of range.
*
* @tparam T Type of the values in the Skip List.
* @tparam _Compare Compare function.
* @param idx The index.
* @return The Node.
*/
template <typename T, typename _Compare>
const Node<T, _Compare> *HeadNode<T, _Compare>::_nodeAt(size_t idx) const {
if (idx < _count) {
for (size_t l = _nodeRefs.height(); l-- > 0;) {
if (_nodeRefs[l].pNode && _nodeRefs[l].width <= idx + 1) {
size_t new_index = idx + 1 - _nodeRefs[l].width;
const Node<T, _Compare> *pNode = _nodeRefs[l].pNode->at(new_index);
if (pNode) {
return pNode;
}
}
}
}
assert(idx >= _count);
_throw_exceeds_size(_count);
// Should not get here as _throw_exceeds_size() will always throw.
return NULL;
}
/**
* Insert a value.
*
* @tparam T Type of the values in the Skip List.
* @tparam _Compare Compare function.
* @param value
*/
template <typename T, typename _Compare>
void HeadNode<T, _Compare>::insert(const T &value) {
#ifdef SKIPLIST_THREAD_SUPPORT
std::lock_guard<std::mutex> lock(gSkipListMutex);
#ifdef SKIPLIST_THREAD_SUPPORT_TRACE
std::cout << "HeadNode insert() thread: " << std::this_thread::get_id() << std::endl;
#endif
#endif
Node<T, _Compare> *pNode = nullptr;
size_t level = _nodeRefs.height();
_throwIfValueDoesNotCompare(value);
while (level-- > 0) {
assert(_nodeRefs[level].pNode);
pNode = _nodeRefs[level].pNode->insert(value);
if (pNode) {
break;
}
}
if (! pNode) {
pNode = _pool.Allocate(value);
level = 0;
}
assert(pNode);
SwappableNodeRefStack<T, _Compare> &thatRefs = pNode->nodeRefs();
if (thatRefs.canSwap()) {
// Expand this to that
while (_nodeRefs.height() < thatRefs.height()) {
_nodeRefs.push_back(nullptr, _count + 1);
}
if (level < thatRefs.swapLevel()) {
// Happens when we were originally, say 3 high (max height of any
// previously seen node). Then a node is created
// say 5 high. In that case this will be at level 2 and
// thatRefs.swapLevel() will be 3
assert(level + 1 == thatRefs.swapLevel());
thatRefs[thatRefs.swapLevel()].width += _nodeRefs[level].width;
++level;
}
// Now swap
while (level < _nodeRefs.height() && thatRefs.canSwap()) {
assert(thatRefs.canSwap());
assert(level == thatRefs.swapLevel());
_nodeRefs[level].width -= thatRefs[level].width - 1;
thatRefs.swap(_nodeRefs);
if (thatRefs.canSwap()) {
assert(thatRefs[thatRefs.swapLevel()].width == 0);
thatRefs[thatRefs.swapLevel()].width = _nodeRefs[level].width;
}
++level;
}
// Check all references swapped
assert(! thatRefs.canSwap());
// Check that all 'this' pointers created on construction have been moved
assert(thatRefs.noNodePointerMatches(pNode));
}
if (level < thatRefs.swapLevel()) {
// Happens when we are, say 5 high then a node is created
// and consumed by the next node say 3 high. In that case this will be
// at level 2 and thatRefs.swapLevel() will be 3
assert(level + 1 == thatRefs.swapLevel());
++level;
}
// Increment my widths as my references are now going over the top of
// pNode.
while (level < _nodeRefs.height() && level >= thatRefs.height()) {
_nodeRefs[level++].width += 1;
}
++_count;
#ifdef SKIPLIST_THREAD_SUPPORT
#ifdef SKIPLIST_THREAD_SUPPORT_TRACE
std::cout << "HeadNode insert() thread: " << std::this_thread::get_id() << " DONE" << std::endl;
#endif
#endif
}
/**
* Adjust references >= level for removal of the node pNode.
*
* @tparam T Type of the values in the Skip List.
* @tparam _Compare Compare function.
* @param level Current level.
* @param pNode Node to swap references with.
*/
template <typename T, typename _Compare>
void HeadNode<T, _Compare>::_adjRemoveRefs(size_t level,
Node<T, _Compare> *pNode) {
assert(pNode);
SwappableNodeRefStack<T, _Compare> &thatRefs = pNode->nodeRefs();
// Swap all remaining levels
// This assertion checks that if swapping can take place we must be at the
// same level.
assert(! thatRefs.canSwap() || 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;
if (! thatRefs.canSwap()) {
break;
}
}
assert(! thatRefs.canSwap());
// Decrement my widths as my references are now going over the top of
// pNode.
while (level < _nodeRefs.height()) {
_nodeRefs[level++].width -= 1;
}
// Decrement my stack while top has a NULL pointer.
while (_nodeRefs.height() && ! _nodeRefs[_nodeRefs.height() - 1].pNode) {
_nodeRefs.pop_back();
}
}
/**
* Remove a Node with a value.
* May throw a ValueError if the value is not found.
*
* @tparam T Type of the values in the Skip List.
* @tparam _Compare Compare function.
* @param value The value in the Node to remove.
* @return The value removed.
*/
template <typename T, typename _Compare>
T HeadNode<T, _Compare>::remove(const T &value) {
#ifdef SKIPLIST_THREAD_SUPPORT
std::lock_guard<std::mutex> lock(gSkipListMutex);
#ifdef SKIPLIST_THREAD_SUPPORT_TRACE
std::cout << "HeadNode remove() thread: " << std::this_thread::get_id() << std::endl;
#endif
#endif
Node<T, _Compare> *pNode = nullptr;
size_t level;
_throwIfValueDoesNotCompare(value);
for (level = _nodeRefs.height(); level-- > 0;) {
assert(_nodeRefs[level].pNode);
pNode = _nodeRefs[level].pNode->remove(level, value);
if (pNode) {
break;
}
}
if (! pNode) {
_throwValueErrorNotFound(value);
}
// Take swap level as some swaps will have been dealt with by the remove() above.
_adjRemoveRefs(pNode->nodeRefs().swapLevel(), pNode);
--_count;
T ret_val = _pool.Release(pNode);
#ifdef SKIPLIST_THREAD_SUPPORT_TRACE
std::cout << "HeadNode remove() thread: " << std::this_thread::get_id() << " DONE" << std::endl;
#endif
return ret_val;
}
/**
* Throw a ValueError in a consistent fashion.
*
* @tparam T Type of the values in the Skip List.
* @tparam _Compare Compare function.
* @param value The value to put into the ValueError.
*/
template <typename T, typename _Compare>
void HeadNode<T, _Compare>::_throwValueErrorNotFound(const T &value) const {
#ifdef INCLUDE_METHODS_THAT_USE_STREAMS
std::ostringstream oss;
oss << "Value " << value << " not found.";
std::string err_msg = oss.str();
#else
std::string err_msg = "Value not found.";
#endif
throw ValueError(err_msg);
}
/**
* Checks that the value == value.
* This will throw a FailedComparison if that is not the case, for example NaN.
*
* @note
* The Node class is (should be) not directly accessible by the user so we can just assert(value == value) in Node.
*
* @tparam T Type of the values in the Skip List.
* @tparam _Compare Compare function.
* @param value
*/
template <typename T, typename _Compare>
void HeadNode<T, _Compare>::_throwIfValueDoesNotCompare(const T &value) const {
if (value != value) {
throw FailedComparison(
"Can not work with something that does not compare equal to itself.");
}
}
/**
* This tests that at every level >= 0 the sequence of node pointers
* at that level does not contain a cyclic reference.
*
* @tparam T Type of the values in the Skip List.
* @tparam _Compare Compare function.
* @return An IntegrityCheck enum.
*/
template <typename T, typename _Compare>
IntegrityCheck HeadNode<T, _Compare>::_lacksIntegrityCyclicReferences() const {
assert(_nodeRefs.height());
// Check for cyclic references at each level
for (size_t level = 0; level < _nodeRefs.height(); ++level) {
Node<T, _Compare> *p1 = _nodeRefs[level].pNode;
Node<T, _Compare> *p2 = _nodeRefs[level].pNode;
while (p1 && p2) {
p1 = p1->nodeRefs()[level].pNode;
if (p2->nodeRefs()[level].pNode) {
p2 = p2->nodeRefs()[level].pNode->nodeRefs()[level].pNode;
} else {
p2 = nullptr;
}
if (p1 && p2 && p1 == p2) {
return HEADNODE_DETECTS_CYCLIC_REFERENCE;
}
}
}
return INTEGRITY_SUCCESS;
}
/**
* This tests that at every level > 0 the node to node width is the same
* as the accumulated node to node widths at level - 1.
*
* @tparam T Type of the values in the Skip List.
* @tparam _Compare Compare function.
* @return An IntegrityCheck enum.
*/
template <typename T, typename _Compare>
IntegrityCheck HeadNode<T, _Compare>::_lacksIntegrityWidthAccumulation() const {
assert(_nodeRefs.height());
for (size_t level = 1; level < _nodeRefs.height(); ++level) {
const Node<T, _Compare> *pl = _nodeRefs[level].pNode;
const Node<T, _Compare> *pl_1 = _nodeRefs[level - 1].pNode;
assert(pl && pl_1); // No nulls allowed in HeadNode
size_t wl = _nodeRefs[level].width;
size_t wl_1 = _nodeRefs[level - 1].width;
while (true) {
while (pl != pl_1) {
assert(pl_1); // Could only happen if a lower reference was NULL and the higher non-NULL.
wl_1 += pl_1->width(level - 1);
pl_1 = pl_1->pNode(level - 1);
}
if (wl != wl_1) {
return HEADNODE_LEVEL_WIDTHS_MISMATCH;
}
if (pl == nullptr && pl_1 == nullptr) {
break;
}
wl = pl->width(level);
wl_1 = pl_1->width(level - 1);
pl = pl->pNode(level);
pl_1 = pl_1->pNode(level - 1);
}
}
return INTEGRITY_SUCCESS;
}
/**
* This tests the integrity of each Node.
*
* @tparam T Type of the values in the Skip List.
* @tparam _Compare Compare function.
* @return An IntegrityCheck enum.
*/
template <typename T, typename _Compare>
IntegrityCheck HeadNode<T, _Compare>::_lacksIntegrityNodeReferencesNotInList() const {
assert(_nodeRefs.height());
IntegrityCheck result;
std::set<const Node<T, _Compare>*> nodeSet;
const Node<T, _Compare> *pNode = _nodeRefs[0].pNode;
assert(pNode);
// First gather all nodes, slightly awkward code here is so that
// NULL is always included.
nodeSet.insert(pNode);
do {
pNode = pNode->next();
nodeSet.insert(pNode);
} while (pNode);
assert(nodeSet.size() == _count + 1); // All nodes plus NULL
// Then test each node does not have pointers that are not in nodeSet
pNode = _nodeRefs[0].pNode;
while (pNode) {
result = pNode->lacksIntegrityRefsInSet(nodeSet);
if (result) {
return result;
}
pNode = pNode->next();
}
return INTEGRITY_SUCCESS;
}
/**
* Integrity check. Traverse the lowest level and check that the ordering
* is correct according to the compare function. The HeadNode checks that the
* Node(s) have correctly applied the compare function.
*
* @tparam T Type of the values in the Skip List.
* @tparam _Compare Compare function.
* @return An IntegrityCheck enum.
*/
template <typename T, typename _Compare>
IntegrityCheck HeadNode<T, _Compare>::_lacksIntegrityOrder() const {
if (_nodeRefs.height()) {
// Traverse the lowest level list iteratively deleting as we go
// Doing this recursivley could be expensive as we are at level 0.
const Node<T, _Compare> *node = _nodeRefs[0].pNode;
const Node<T, _Compare> *next;
while (node) {
next = node->next();
if (next && _compare(next->value(), node->value())) {
return HEADNODE_DETECTS_OUT_OF_ORDER;
}
node = next;
}
}
return INTEGRITY_SUCCESS;
}
/**
* Full integrity check.
* This calls the other integrity check functions.
*
* @tparam T Type of the values in the Skip List.
* @tparam _Compare Compare function.
* @return An IntegrityCheck enum.
*/
template <typename T, typename _Compare>
IntegrityCheck HeadNode<T, _Compare>::lacksIntegrity() const {
#ifdef SKIPLIST_THREAD_SUPPORT
std::lock_guard<std::mutex> lock(gSkipListMutex);
#endif
if (_nodeRefs.height()) {
IntegrityCheck result = _nodeRefs.lacksIntegrity();
if (result) {
return result;
}
if (! _nodeRefs.noNodePointerMatches(nullptr)) {
return HEADNODE_CONTAINS_NULL;
}
// Check all nodes for integrity
const Node<T, _Compare> *pNode = _nodeRefs[0].pNode;
while (pNode) {
result = pNode->lacksIntegrity(_nodeRefs.height());
if (result) {
return result;
}
pNode = pNode->next();
}
// Check count against total number of nodes
pNode = _nodeRefs[0].pNode;
size_t total = 0;
while (pNode) {
total += pNode->nodeRefs()[0].width;
pNode = pNode->next();
}
if (total != _count) {
return HEADNODE_COUNT_MISMATCH;
}
result = _lacksIntegrityWidthAccumulation();
if (result) {
return result;
}
result = _lacksIntegrityCyclicReferences();
if (result) {
return result;
}
result = _lacksIntegrityNodeReferencesNotInList();
if (result) {
return result;
}
result = _lacksIntegrityOrder();
if (result) {
return result;
}
}
return INTEGRITY_SUCCESS;
}
/**
* Returns an estimate of the memory usage of an instance.
*
* @tparam T Type of the values in the Skip List.
* @tparam _Compare Compare function.
* @return The size of the memory estimate.
*/
template <typename T, typename _Compare>
size_t HeadNode<T, _Compare>::size_of() const {
#ifdef SKIPLIST_THREAD_SUPPORT
std::lock_guard<std::mutex> lock(gSkipListMutex);
#endif
// sizeof(*this) includes the size of _nodeRefs but _nodeRefs.size_of()
// includes sizeof(_nodeRefs) so we need to subtract to avoid double counting
size_t ret_val = sizeof(*this) + _nodeRefs.size_of() - sizeof(_nodeRefs);
if (_nodeRefs.height()) {
const Node<T, _Compare> *node = _nodeRefs[0].pNode;
while (node) {
ret_val += node->size_of();
node = node->next();
}
}
return ret_val;
}
/**
* Destructor.
* This deletes all Nodes.
*
* @tparam T Type of the values in the Skip List.
* @tparam _Compare Compare function.
*/
template <typename T, typename _Compare>
HeadNode<T, _Compare>::~HeadNode() {
// Hmm could this deadlock?
#ifdef SKIPLIST_THREAD_SUPPORT
std::lock_guard<std::mutex> lock(gSkipListMutex);
#endif
if (_nodeRefs.height()) {
// Traverse the lowest level list iteratively deleting as we go
// Doing this recursivley could be expensive as we are at level 0.
const Node<T, _Compare> *node = _nodeRefs[0].pNode;
const Node<T, _Compare> *next;
while (node) {
next = node->next();
delete node;
--_count;
node = next;
}
}
assert(_count == 0);
}
#ifdef INCLUDE_METHODS_THAT_USE_STREAMS
/**
* Create a DOT file of the internal representation.
*
* @tparam T Type of the values in the Skip List.
* @tparam _Compare Compare function.
* @param os Where to write the DOT file.
*/
template <typename T, typename _Compare>
void HeadNode<T, _Compare>::dotFile(std::ostream &os) const {
#ifdef SKIPLIST_THREAD_SUPPORT
std::lock_guard<std::mutex> lock(gSkipListMutex);
#endif
if (_dot_file_subgraph == 0) {
os << "digraph SkipList {" << std::endl;
os << "label = \"SkipList.\"" << std::endl;
os << "graph [rankdir = \"LR\"];" << std::endl;
os << "node [fontsize = \"12\" shape = \"ellipse\"];" << std::endl;
os << "edge [];" << std::endl;
os << std::endl;
}
os << "subgraph cluster" << _dot_file_subgraph << " {" << std::endl;
os << "style=dashed" << std::endl;
os << "label=\"Skip list iteration " << _dot_file_subgraph << "\"" << std::endl;
os << std::endl;
os << "\"HeadNode" << _dot_file_subgraph;
os << "\" [" << std::endl;
os << "label = \"";
// Write out the fields
if (_nodeRefs.height()) {
for (size_t level = _nodeRefs.height(); level-- > 0;) {
os << "{ " << _nodeRefs[level].width << " | ";
os << "<f" << level + 1 << "> ";
os << std::hex << _nodeRefs[level].pNode << std::dec;
os << "}";
if (level > 0) {
os << " | ";
}
}
} else {
os << "Empty HeadNode";
}
os << "\"" << std::endl;
os << "shape = \"record\"" << std::endl;
os << "];" << std::endl;
// Edges for head node
for (size_t level = 0; level < _nodeRefs.height(); ++level) {
os << "\"HeadNode";
os << _dot_file_subgraph;
os << "\":f" << level + 1 << " -> ";
_nodeRefs[level].pNode->writeNode(os, _dot_file_subgraph);
os << ":w" << level + 1 << " [];" << std::endl;
}
os << std::endl;
// Now all nodes via level 0, if non-empty
if (_nodeRefs.height()) {
Node<T, _Compare> *pNode = this->_nodeRefs[0].pNode;
pNode->dotFile(os, _dot_file_subgraph);
}
os << std::endl;
// NULL, the sentinal node
if (_nodeRefs.height()) {
os << "\"node";
os << _dot_file_subgraph;
os << "0x0\" [label = \"";
for (size_t level = _nodeRefs.height(); level-- > 0;) {
os << "<w" << level + 1 << "> NULL";
if (level) {
os << " | ";
}
}
os << "\" shape = \"record\"];" << std::endl;
}
// End: "subgraph cluster1 {"
os << "}" << std::endl;
os << std::endl;
_dot_file_subgraph += 1;
}
/**
* Finalise the DOT file of the internal representation.
*
* @tparam T Type of the values in the Skip List.
* @tparam _Compare Compare function.
* @param os Where to write the DOT file.
*/
template <typename T, typename _Compare>
void HeadNode<T, _Compare>::dotFileFinalise(std::ostream &os) const {
#ifdef SKIPLIST_THREAD_SUPPORT
std::lock_guard<std::mutex> lock(gSkipListMutex);
#endif
if (_dot_file_subgraph > 0) {
// Link the nodes together with an invisible node.
// node0 [shape=record, label = "<f0> | <f1> | <f2> | <f3> | <f4> | <f5> | <f6> | <f7> | <f8> | ",
// style=invis,
// width=0.01];
os << "node0 [shape=record, label = \"";
for (size_t i = 0; i < _dot_file_subgraph; ++i) {
os << "<f" << i << "> | ";
}
os << "\", style=invis, width=0.01];" << std::endl;
// Now:
// node0:f0 -> HeadNode [style=invis];
// node0:f1 -> HeadNode1 [style=invis];
for (size_t i = 0; i < _dot_file_subgraph; ++i) {
os << "node0:f" << i << " -> HeadNode" << i;
os << " [style=invis];" << std::endl;
}
_dot_file_subgraph = 0;
}
os << "}" << std::endl;
}
#endif // INCLUDE_METHODS_THAT_USE_STREAMS
/************************** END: HeadNode *******************************/
#endif // SkipList_HeadNode_h