/** * @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 //#ifdef SKIPLIST_THREAD_SUPPORT // #include //#endif #include #ifdef INCLUDE_METHODS_THAT_USE_STREAMS #include #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 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 > 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 &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 *pNode); const Node *_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 _nodeRefs; /// Comparison function. _Compare _compare; typename Node::_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 bool HeadNode::has(const T &value) const { _throwIfValueDoesNotCompare(value); #ifdef SKIPLIST_THREAD_SUPPORT std::lock_guard 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 const T &HeadNode::at(size_t index) const { #ifdef SKIPLIST_THREAD_SUPPORT std::lock_guard lock(gSkipListMutex); #endif const Node *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 void HeadNode::at(size_t index, size_t count, std::vector &dest) const { #ifdef SKIPLIST_THREAD_SUPPORT std::lock_guard lock(gSkipListMutex); #endif dest.clear(); const Node *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 size_t HeadNode::index(const T& value) const { _throwIfValueDoesNotCompare(value); size_t idx; #ifdef SKIPLIST_THREAD_SUPPORT std::lock_guard 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 size_t HeadNode::size() const { return _count; } template size_t HeadNode::height() const { #ifdef SKIPLIST_THREAD_SUPPORT std::lock_guard 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 size_t HeadNode::height(size_t idx) const { #ifdef SKIPLIST_THREAD_SUPPORT std::lock_guard lock(gSkipListMutex); #endif const Node *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 size_t HeadNode::width(size_t idx, size_t level) const { #ifdef SKIPLIST_THREAD_SUPPORT std::lock_guard lock(gSkipListMutex); #endif // Will throw if out of range. const Node *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 const Node *HeadNode::_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 *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 void HeadNode::insert(const T &value) { #ifdef SKIPLIST_THREAD_SUPPORT std::lock_guard lock(gSkipListMutex); #ifdef SKIPLIST_THREAD_SUPPORT_TRACE std::cout << "HeadNode insert() thread: " << std::this_thread::get_id() << std::endl; #endif #endif Node *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 &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 void HeadNode::_adjRemoveRefs(size_t level, Node *pNode) { assert(pNode); SwappableNodeRefStack &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 T HeadNode::remove(const T &value) { #ifdef SKIPLIST_THREAD_SUPPORT std::lock_guard lock(gSkipListMutex); #ifdef SKIPLIST_THREAD_SUPPORT_TRACE std::cout << "HeadNode remove() thread: " << std::this_thread::get_id() << std::endl; #endif #endif Node *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 void HeadNode::_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 void HeadNode::_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 IntegrityCheck HeadNode::_lacksIntegrityCyclicReferences() const { assert(_nodeRefs.height()); // Check for cyclic references at each level for (size_t level = 0; level < _nodeRefs.height(); ++level) { Node *p1 = _nodeRefs[level].pNode; Node *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 IntegrityCheck HeadNode::_lacksIntegrityWidthAccumulation() const { assert(_nodeRefs.height()); for (size_t level = 1; level < _nodeRefs.height(); ++level) { const Node *pl = _nodeRefs[level].pNode; const Node *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 IntegrityCheck HeadNode::_lacksIntegrityNodeReferencesNotInList() const { assert(_nodeRefs.height()); IntegrityCheck result; std::set*> nodeSet; const Node *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 IntegrityCheck HeadNode::_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 *node = _nodeRefs[0].pNode; const Node *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 IntegrityCheck HeadNode::lacksIntegrity() const { #ifdef SKIPLIST_THREAD_SUPPORT std::lock_guard 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 *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 size_t HeadNode::size_of() const { #ifdef SKIPLIST_THREAD_SUPPORT std::lock_guard 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 *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 HeadNode::~HeadNode() { // Hmm could this deadlock? #ifdef SKIPLIST_THREAD_SUPPORT std::lock_guard 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 *node = _nodeRefs[0].pNode; const Node *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 void HeadNode::dotFile(std::ostream &os) const { #ifdef SKIPLIST_THREAD_SUPPORT std::lock_guard 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 << " "; 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 *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 << " 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 void HeadNode::dotFileFinalise(std::ostream &os) const { #ifdef SKIPLIST_THREAD_SUPPORT std::lock_guard lock(gSkipListMutex); #endif if (_dot_file_subgraph > 0) { // Link the nodes together with an invisible node. // node0 [shape=record, label = " | | | | | | | | | ", // style=invis, // width=0.01]; os << "node0 [shape=record, label = \""; for (size_t i = 0; i < _dot_file_subgraph; ++i) { os << " | "; } 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