Zip and Skip Tries
Loading...
Searching...
No Matches
Classes | Public Types | Public Member Functions | Static Public Member Functions | Protected Member Functions | Protected Attributes | Related Symbols | List of all members
SkipTrie< CHAR_T, CHAR_SIZE_BITS > Class Template Reference

Implements a skip list data structure optimized for storing and retrieving BitString keys. More...

#include <SkipTrie.hpp>

Inheritance diagram for SkipTrie< CHAR_T, CHAR_SIZE_BITS >:
Inheritance graph
[legend]

Classes

struct  EqualOrSuccessor
 Helper struct to return a Node pointer, a flag indicating equality, and an LCP value. More...
 
class  Iterator
 Provides forward/backward iteration over the keys in the base level of the SkipTrie. More...
 
struct  Node
 Represents a single node within the SkipTrie structure. More...
 
struct  NodeLCP
 Helper struct to return a Node pointer and an associated LCP value. More...
 
struct  ResultLCP
 Helper struct to return a comparison result (ordering) and an LCP value. More...
 

Public Types

using KEY_T = BitString< CHAR_T, CHAR_SIZE_BITS >
 Alias for the key type used in the SkipTrie, based on BitString.
 

Public Member Functions

 SkipTrie ()
 Constructs an empty SkipTrie.
 
bool insert (const KEY_T *key) noexcept
 Inserts a new key into the skip list with a randomly generated height.
 
bool insert (const KEY_T *key, size_t height) noexcept
 Inserts a new key into the skip list with a specified height.
 
bool contains (const KEY_T *key) const noexcept
 Checks if a specific key exists within the skip list.
 
bool remove (const KEY_T *key) noexcept
 Removes a key from the skip list.
 
std::ostream & print (std::ostream &os) const noexcept
 Prints a textual representation of the skip list structure, layer by layer, to an output stream.
 
std::string to_string () const noexcept
 Generates a string representation of the skip list structure.
 
size_t size () const noexcept
 Returns the number of keys currently stored in the skip list.
 
size_t height () const noexcept
 Returns the current maximum height of the skip list.
 
size_t lcp (const KEY_T *key) const noexcept
 Calculates the Longest Common Prefix (LCP) between a given key and its position in the skip list search path.
 
size_t lcp_with_others (const KEY_T *key) const noexcept
 Calculates the maximum Longest Common Prefix (LCP) between a given key and its immediate neighbors (predecessor and successor) at the base level.
 
std::vector< const KEY_T * > suffix_search (const KEY_T *key) const noexcept
 Finds all keys in the skip list that have the given key as a prefix.
 
std::vector< const KEY_T * > range_search (const KEY_T *key1, const KEY_T *key2) const noexcept
 Finds all keys within a specified lexicographical range [key1, key2).
 
std::unordered_map< size_t, size_tget_lcp_group_sizes () const noexcept
 Calculates the sizes of groups of consecutive nodes sharing the same LCP value at the base level.
 
Iterator begin () const noexcept
 Returns an iterator pointing to the first key in the skip list (at the base level).
 
Iterator end () const noexcept
 Returns an iterator pointing past the last key in the skip list (the tail sentinel at the base level).
 

Static Public Member Functions

static size_t get_random_height ()
 Generates a random height for a new node based on a geometric distribution.
 

Protected Member Functions

void add_layer () noexcept
 Adds a new, empty layer on top of the current skip list structure.
 
void remove_layer () noexcept
 Removes the top-most layer of the skip list if it is empty (contains only sentinels).
 
std::shared_ptr< Nodeinsert_recursive (const KEY_T *key, Node *curr, size_t height, Direction direction, size_t lcp, size_t current_height) noexcept
 Recursively finds the correct position and inserts links for a new node at a specific level and below.
 
Direction iter_layer (const KEY_T *key, Node *&curr, Direction direction, size_t &lcp) const noexcept
 Iterates horizontally along a single layer to find the node preceding the insertion/search point for a given key.
 
virtual NodeLCP find_first (const KEY_T *key, const bool require_level0=false) const noexcept
 Finds the node containing the exact key, returning the node and the search path LCP.
 
virtual EqualOrSuccessor find_equal_or_successor (const KEY_T *key, const bool require_level0) const noexcept
 Finds the node containing the exact key or its immediate successor.
 
virtual ResultLCP compare (const KEY_T *key1, const KEY_T *key2, size_t lcp) const noexcept
 Compares two keys starting from a known common prefix length.
 

Protected Attributes

size_t m_size
 Stores the total number of keys (nodes) in the base level of the skip list.
 
size_t m_height
 Stores the current maximum height (number of levels) of the skip list.
 
std::shared_ptr< Nodem_head = nullptr
 Shared pointer to the head sentinel node of the top-most layer.
 
std::shared_ptr< Nodem_lower_head = nullptr
 Shared pointer to the head sentinel node of the base layer (level 0).
 
std::shared_ptr< Nodem_lower_tail = nullptr
 Shared pointer to the tail sentinel node of the base layer (level 0).
 

Related Symbols

(Note that these are not member symbols.)

template<typename CHAR_T , unsigned CHAR_SIZE_BITS>
std::ostream & operator<< (std::ostream &os, const SkipTrie< CHAR_T, CHAR_SIZE_BITS > &ssl)
 Overload of the stream insertion operator (<<) for SkipTrie.
 

Detailed Description

template<typename CHAR_T, unsigned CHAR_SIZE_BITS = sizeof(CHAR_T) * 8>
class SkipTrie< CHAR_T, CHAR_SIZE_BITS >

Implements a skip list data structure optimized for storing and retrieving BitString keys.

This class uses a multi-level linked list (skip list) structure to store keys. Each node can have connections to the next/previous node on the same level and a connection to a corresponding node on the level below. Random heights are assigned to nodes upon insertion to maintain probabilistic balance, aiming for logarithmic average time complexity for search, insertion, and deletion. The structure leverages Longest Common Prefix (LCP) information stored between adjacent nodes to speed up comparisons during traversal.

Template Parameters
CHAR_TThe underlying character type used in the keys (BitString).
CHAR_SIZE_BITSThe number of significant bits per character in CHAR_T. Defaults to sizeof(CHAR_T) * 8.
See also
BitString

Definition at line 53 of file SkipTrie.hpp.

Member Typedef Documentation

◆ KEY_T

template<typename CHAR_T , unsigned CHAR_SIZE_BITS = sizeof(CHAR_T) * 8>
using SkipTrie< CHAR_T, CHAR_SIZE_BITS >::KEY_T = BitString<CHAR_T, CHAR_SIZE_BITS>

Alias for the key type used in the SkipTrie, based on BitString.

Definition at line 57 of file SkipTrie.hpp.

Constructor & Destructor Documentation

◆ SkipTrie()

template<typename CHAR_T , unsigned CHAR_SIZE_BITS>
SkipTrie< CHAR_T, CHAR_SIZE_BITS >::SkipTrie ( )

Constructs an empty SkipTrie.

Initializes the size and height to zero. Head and tail pointers are initially null. Layers are added dynamically as needed during insertion.

Definition at line 571 of file SkipTrie.hpp.

572 : m_size(0), m_height(0)
573{
574}
size_t m_size
Stores the total number of keys (nodes) in the base level of the skip list.
Definition SkipTrie.hpp:213
size_t m_height
Stores the current maximum height (number of levels) of the skip list.
Definition SkipTrie.hpp:215

Member Function Documentation

◆ add_layer()

template<typename CHAR_T , unsigned CHAR_SIZE_BITS>
void SkipTrie< CHAR_T, CHAR_SIZE_BITS >::add_layer ( )
protectednoexcept

Adds a new, empty layer on top of the current skip list structure.

Creates new head and tail sentinel nodes for the new top layer. Links the new head's down pointer to the old head and the new tail's down pointer to the old tail. Updates m_head and increments m_height. Initializes m_lower_head and m_lower_tail if this is the first layer being added.

Note
Called by insert when a node's random height exceeds the current maximum height.

Definition at line 590 of file SkipTrie.hpp.

591{
592 // Create new sentinel nodes for the layer.
593 std::shared_ptr<Node> new_head = std::make_shared<Node>();
594 std::shared_ptr<Node> new_tail = std::make_shared<Node>();
595
596 // Link the new head down to the old head (or null if no layers yet).
597 new_head->down = m_head;
598 // Link the new tail down to the old tail (or null if no layers yet).
599 // Access old tail via old head's next pointer.
600 if (m_head)
601 {
602 new_tail->down = m_head->next;
603 }
604 // Link new head and tail horizontally.
605 new_head->next = new_tail;
606 new_tail->prev = new_head.get();
607
608 // Update the main head pointer to the new head.
610 // Increment the height counter.
611 ++m_height;
612
613 // If this is the very first layer added, set the lower head/tail pointers.
614 if (!m_lower_head)
615 {
618 }
619}
std::shared_ptr< Node > m_lower_head
Shared pointer to the head sentinel node of the base layer (level 0).
Definition SkipTrie.hpp:254
std::shared_ptr< Node > m_head
Shared pointer to the head sentinel node of the top-most layer.
Definition SkipTrie.hpp:252
std::shared_ptr< Node > m_lower_tail
Shared pointer to the tail sentinel node of the base layer (level 0).
Definition SkipTrie.hpp:256
T * alloc_to_device(size_t size)
Allocates memory on the CUDA device.

◆ begin()

template<typename CHAR_T , unsigned CHAR_SIZE_BITS>
SkipTrie< CHAR_T, CHAR_SIZE_BITS >::Iterator SkipTrie< CHAR_T, CHAR_SIZE_BITS >::begin ( ) const
noexcept

Returns an iterator pointing to the first key in the skip list (at the base level).

Returns
Iterator An iterator positioned at the first element after the head sentinel.

Definition at line 529 of file SkipTrie.hpp.

530{
531 // Returns iterator starting from the first actual node after the head sentinel at the base level.
532 return Iterator(m_lower_head->next.get(), Direction::FORWARD);
533}
@ FORWARD
Represents traversal towards the end of the list (successor nodes).

◆ compare()

template<typename CHAR_T , unsigned CHAR_SIZE_BITS>
auto SkipTrie< CHAR_T, CHAR_SIZE_BITS >::compare ( const KEY_T key1,
const KEY_T key2,
size_t  lcp 
) const
protectedvirtualnoexcept

Compares two keys starting from a known common prefix length.

Performs a sequential comparison of key1 and key2, assuming the first lcp characters are identical. Uses BitString::seq_k_compare for the actual comparison.

Parameters
key1Pointer to the first BitString key.
key2Pointer to the second BitString key.
lcpThe length of the known common prefix (in characters) from which to start the comparison.
Returns
ResultLCP A struct containing the comparison result (std::strong_ordering) and the total LCP length found.
Note
This is the default comparison implementation. It can be overridden by derived classes (like ParallelSkipTrie).
See also
BitString::seq_k_compare

Reimplemented in ParallelSkipTrie< CHAR_T, CHAR_SIZE_BITS >.

Definition at line 638 of file SkipTrie.hpp.

639{
640 // Delegate comparison to the BitString's sequential compare method.
641 auto [comparison, next_lcp] = key1->seq_k_compare(*key2, lcp);
642 return { comparison, next_lcp };
643}
size_t lcp(const KEY_T *key) const noexcept
Calculates the Longest Common Prefix (LCP) between a given key and its position in the skip list sear...
Definition SkipTrie.hpp:878

◆ contains()

template<typename CHAR_T , unsigned CHAR_SIZE_BITS>
bool SkipTrie< CHAR_T, CHAR_SIZE_BITS >::contains ( const KEY_T key) const
noexcept

Checks if a specific key exists within the skip list.

Uses find_first to locate the key.

Parameters
keyA pointer to the BitString key to search for.
Returns
bool True if the key is found in the skip list, false otherwise.
See also
find_first

Definition at line 726 of file SkipTrie.hpp.

727{
728 // Check if find_first returns a non-null node.
729 return find_first(key).node != nullptr;
730}
virtual NodeLCP find_first(const KEY_T *key, const bool require_level0=false) const noexcept
Finds the node containing the exact key, returning the node and the search path LCP.
Definition SkipTrie.hpp:716
Node * node
Pointer to the found node (or null if not found).
Definition SkipTrie.hpp:315

◆ end()

template<typename CHAR_T , unsigned CHAR_SIZE_BITS>
SkipTrie< CHAR_T, CHAR_SIZE_BITS >::Iterator SkipTrie< CHAR_T, CHAR_SIZE_BITS >::end ( ) const
noexcept

Returns an iterator pointing past the last key in the skip list (the tail sentinel at the base level).

Returns
Iterator An iterator positioned at the tail sentinel node.

Definition at line 536 of file SkipTrie.hpp.

537{
538 // Returns iterator pointing to the tail sentinel at the base level.
539 return Iterator(m_lower_tail.get(), Direction::FORWARD);
540}

◆ find_equal_or_successor()

template<typename CHAR_T , unsigned CHAR_SIZE_BITS>
SkipTrie< CHAR_T, CHAR_SIZE_BITS >::EqualOrSuccessor SkipTrie< CHAR_T, CHAR_SIZE_BITS >::find_equal_or_successor ( const KEY_T key,
const bool  require_level0 
) const
protectedvirtualnoexcept

Finds the node containing the exact key or its immediate successor.

Traverses the skip list from the top layer down, using iter_layer at each level to narrow down the position. Returns the node found (either the exact match or the first node lexicographically greater than the key) and indicates whether an exact match was found. Also returns the LCP computed along the search path.

Parameters
keyA pointer to the BitString key to search for.
require_level0If true, ensures the search descends fully to level 0 before returning the node. Defaults to false.
Returns
EqualOrSuccessor A struct containing the found node (match or successor), an equality flag, and the search path LCP.
Note
This is a core search function used by contains, insert, remove, etc.
See also
iter_layer

Reimplemented in ParallelSkipTrie< CHAR_T, CHAR_SIZE_BITS >.

Definition at line 922 of file SkipTrie.hpp.

923{
924 // Handle empty list case.
925 if (!m_head)
926 {
927 return { nullptr, false, 0 };
928 }
929
930 Node* curr = m_head.get();
931 Node* prev = nullptr;
933 size_t lcp = 0;
934
935 // Traverse down the levels.
936 while (curr)
937 {
938 // Find predecessor on current level.
940
942 {
943 while (require_level0 && curr->down)
944 {
945 curr = curr->down.get();
946 }
947
948 return { curr, true, lcp };
949 }
950
951 prev = curr;
952 curr = curr->down.get();
954 }
955
956 return { direction == Direction::FORWARD ? prev->next.get() : prev, false, lcp };
957}
Direction
Enumerates the possible traversal directions within the skip list structure.
Definition SkipTrie.hpp:32
@ INPLACE
Indicates no traversal needed, typically when the exact key is found.
Direction iter_layer(const KEY_T *key, Node *&curr, Direction direction, size_t &lcp) const noexcept
Iterates horizontally along a single layer to find the node preceding the insertion/search point for ...
Definition SkipTrie.hpp:646

◆ find_first()

template<typename CHAR_T , unsigned CHAR_SIZE_BITS>
SkipTrie< CHAR_T, CHAR_SIZE_BITS >::NodeLCP SkipTrie< CHAR_T, CHAR_SIZE_BITS >::find_first ( const KEY_T key,
const bool  require_level0 = false 
) const
protectedvirtualnoexcept

Finds the node containing the exact key, returning the node and the search path LCP.

Uses find_equal_or_successor to perform the search. If the key is found (is_equal is true), returns the node and LCP. Otherwise, returns nullptr for the node.

Parameters
keyA pointer to the BitString key to find.
require_level0If true, ensures the returned node (if found) is from the base level (level 0). Defaults to false.
Returns
NodeLCP A struct containing the pointer to the found node (or nullptr) and the LCP computed during the search.
See also
find_equal_or_successor

Definition at line 716 of file SkipTrie.hpp.

717{
718 // Use find_equal_or_successor to perform the search.
719 auto [node, is_equal, lcp] = find_equal_or_successor(key, require_level0);
720
721 // Return the node only if an exact match was found.
722 return { is_equal ? node : nullptr, lcp };
723}
virtual EqualOrSuccessor find_equal_or_successor(const KEY_T *key, const bool require_level0) const noexcept
Finds the node containing the exact key or its immediate successor.
Definition SkipTrie.hpp:922

◆ get_lcp_group_sizes()

template<typename CHAR_T , unsigned CHAR_SIZE_BITS>
std::unordered_map< size_t, size_t > SkipTrie< CHAR_T, CHAR_SIZE_BITS >::get_lcp_group_sizes ( ) const
noexcept

Calculates the sizes of groups of consecutive nodes sharing the same LCP value at the base level.

Iterates through the base level of the skip list, tracking changes in LCP values between adjacent nodes. Uses a stack-like approach to determine the extent (size) of consecutive nodes that share a common LCP value greater than or equal to the LCP values of surrounding nodes.

Returns
std::unordered_map<size_t, size_t> A map where the key is the LCP length and the value is the maximum size of a consecutive group found sharing at least that LCP length with their neighbors.
Note
This can be useful for analyzing the structure and prefix sharing within the dataset.

Definition at line 989 of file SkipTrie.hpp.

990{
991 std::unordered_map<size_t, size_t> lcp_group_sizes;
992
993 std::vector<size_t> lcps_we_are_greater_than;
994 std::vector<size_t> index_first_encountered;
995
996 Node* curr = m_lower_head->next.get();
997
998 size_t i = 0;
999
1000 while (curr->key)
1001 {
1002 size_t lcp = curr->lcp_next;
1003
1004 while (!lcps_we_are_greater_than.empty() && lcp < lcps_we_are_greater_than.back())
1005 {
1006 size_t popped_lcp = lcps_we_are_greater_than.back();
1007 lcps_we_are_greater_than.pop_back();
1008
1009 size_t group_size = i - index_first_encountered.back();
1010 index_first_encountered.pop_back();
1011
1013 }
1014
1016 {
1017 lcps_we_are_greater_than.push_back(lcp);
1018 index_first_encountered.push_back(i);
1019 }
1020 // If current_lcp is equal to top, do nothing (extend the current group).
1021
1022 // Move to the next node and increment the gap index.
1023 curr = curr->next.get();
1024 }
1025
1026 return lcp_group_sizes;
1027}

◆ get_random_height()

template<typename CHAR_T , unsigned CHAR_SIZE_BITS>
size_t SkipTrie< CHAR_T, CHAR_SIZE_BITS >::get_random_height ( )
static

Generates a random height for a new node based on a geometric distribution.

The height determines how many levels the new node will participate in. Uses a static random number generator (geometric distribution with p=0.5).

Returns
size_t The randomly generated height (number of levels above the base level).
Warning
Not thread-safe if called concurrently from multiple threads due to static generator usage.

Definition at line 577 of file SkipTrie.hpp.

578{
579 // Use static variables for random number generation.
580 // Note: Not thread-safe if multiple threads call this concurrently.
581 static std::random_device rd; // Obtain a random seed from the hardware device
582 static std::mt19937 gen(rd()); // Seed the default random engine
583 // static std::mt19937 gen(1); // Use for deterministic testing (fixed seed)
584 static std::geometric_distribution<size_t> dist(0.5); // p=0.5 gives 50% chance of 0, 25% of 1, etc.
585
586 return dist(gen);
587}

◆ height()

template<typename CHAR_T , unsigned CHAR_SIZE_BITS = sizeof(CHAR_T) * 8>
size_t SkipTrie< CHAR_T, CHAR_SIZE_BITS >::height ( ) const
inlinenoexcept

Returns the current maximum height of the skip list.

The height is the number of levels in the skip list (0-indexed). An empty list has height 0 (after first insertion, height becomes >= 1).

Returns
size_t The maximum height.

Definition at line 150 of file SkipTrie.hpp.

150{ return m_height; }

◆ insert() [1/2]

template<typename CHAR_T , unsigned CHAR_SIZE_BITS>
bool SkipTrie< CHAR_T, CHAR_SIZE_BITS >::insert ( const KEY_T key)
noexcept

Inserts a new key into the skip list with a randomly generated height.

Calls get_random_height() to determine the height and then calls the height-specific insert method.

Parameters
keyA pointer to the BitString key to insert. The SkipTrie stores this pointer directly.
Returns
bool True if the key was inserted successfully, false if the key already exists.
Warning
Key Lifetime: The lifetime of the object pointed to by key must exceed the lifetime of the SkipTrie.
See also
insert(const KEY_T*, size_t)
get_random_height()

Definition at line 733 of file SkipTrie.hpp.

734{
735 // Insert with a randomly generated height.
736 return insert(key, get_random_height());
737}
bool insert(const KEY_T *key) noexcept
Inserts a new key into the skip list with a randomly generated height.
Definition SkipTrie.hpp:733
static size_t get_random_height()
Generates a random height for a new node based on a geometric distribution.
Definition SkipTrie.hpp:577

◆ insert() [2/2]

template<typename CHAR_T , unsigned CHAR_SIZE_BITS>
bool SkipTrie< CHAR_T, CHAR_SIZE_BITS >::insert ( const KEY_T key,
size_t  height 
)
noexcept

Inserts a new key into the skip list with a specified height.

Adds necessary layers if the specified height exceeds the current maximum height. Then, calls the recursive insertion helper function to place the node at the correct position across all levels up to the specified height. Increments the size if insertion is successful.

Parameters
keyA pointer to the BitString key to insert. The SkipTrie stores this pointer directly.
heightThe height (number of levels above the base) for the new node.
Returns
bool True if the key was inserted successfully, false if the key already exists.
Warning
Key Lifetime: The lifetime of the object pointed to by key must exceed the lifetime of the SkipTrie.
See also
insert_recursive
add_layer

Definition at line 740 of file SkipTrie.hpp.

741{
742 // Ensure the skip list has enough layers for the specified height.
743 // Need height + 1 levels (0 to height). m_height is number of levels.
744 while (height + 1 >= m_height)
745 {
746 add_layer();
747 }
748
749 // Call the recursive insertion helper. Starts from top head node (m_head).
750 // Initial direction is FORWARD, initial LCP is 0, current height starts at m_height - 1.
751 // insert_recursive returns nullptr if key already exists.
752 bool already_exists = insert_recursive(key, m_head.get(), height, Direction::FORWARD, 0, m_height - 1) == nullptr;
753
754 if (already_exists)
755 {
756 return false; // Key was not inserted because it already existed.
757 }
758
759 // Increment size only if insertion was successful.
760 ++m_size;
761 return true;
762}
size_t height() const noexcept
Returns the current maximum height of the skip list.
Definition SkipTrie.hpp:150
void add_layer() noexcept
Adds a new, empty layer on top of the current skip list structure.
Definition SkipTrie.hpp:590
std::shared_ptr< Node > insert_recursive(const KEY_T *key, Node *curr, size_t height, Direction direction, size_t lcp, size_t current_height) noexcept
Recursively finds the correct position and inserts links for a new node at a specific level and below...
Definition SkipTrie.hpp:765

◆ insert_recursive()

template<typename CHAR_T , unsigned CHAR_SIZE_BITS>
std::shared_ptr< typename SkipTrie< CHAR_T, CHAR_SIZE_BITS >::Node > SkipTrie< CHAR_T, CHAR_SIZE_BITS >::insert_recursive ( const KEY_T key,
Node curr,
size_t  height,
Direction  direction,
size_t  lcp,
size_t  current_height 
)
protectednoexcept

Recursively finds the correct position and inserts links for a new node at a specific level and below.

Traverses the current level (current_height) using iter_layer to find the predecessor node (curr) for the key. Recursively calls itself for the level below (current_height - 1). If the insertion height matches the current_height, it creates the new node at this level, links it between curr and curr->next, sets its down pointer to the node returned by the recursive call (or null at base level), and updates LCP values.

Parameters
keyA pointer to the BitString key being inserted.
currThe starting node for traversal at the current level.
heightThe target insertion height for the new node (determines up to which level it's inserted).
directionThe initial traversal direction inherited from the level above.
lcpThe LCP length calculated so far along the search path from the level above.
current_heightThe current level being processed in the recursion (starts from m_height - 1).
Returns
std::shared_ptr<Node> A shared pointer to the newly created node at this level, or nullptr if the key already existed or if current_height is above the target height. This pointer is used to link the down pointer from the level above.
See also
iter_layer

Definition at line 765 of file SkipTrie.hpp.

766{
767 // Find the predecessor node (`curr`) on the current level.
769
770 // If iter_layer found an exact match, return nullptr to signal duplication.
772 {
773 return nullptr;
774 }
775
776 // Recursively insert on the level below, unless we are at the base level.
777 std::shared_ptr<Node> child = nullptr;
778 if (current_height > 0)
779 {
781
782 // If recursive call returned nullptr, propagate nullptr up.
783 if (!child)
784 {
785 return nullptr;
786 }
787 }
788
789
790 // If the current level is above the target insertion height, just return the child pointer
791 // (or nullptr if base level) without inserting at this level.
793 {
794 return child;
795 }
796
797 // --- Insert node at the current level ---
798
799 // Create the new node.
800 std::shared_ptr<Node> new_node = std::make_shared<Node>();
801 new_node->key = key;
802 new_node->down = child; // Link down to the node created/returned from the level below.
803
804 // Link the new node horizontally based on the direction determined by iter_layer.
805 // `curr` is the predecessor node found by iter_layer.
806 switch (next_direction)
807 {
808 case Direction::FORWARD: // Insert new_node after curr
809 new_node->next = curr->next;
810 new_node->prev = curr;
811 curr->next->prev = new_node.get(); // Update next node's prev pointer
812 curr->next = new_node;
813
814 // Update LCP values.
815 new_node->lcp_next = curr->lcp_next; // New node inherits LCP curr had with its old next.
816 curr->lcp_next = lcp; // LCP between curr and new_node is the final LCP from iter_layer.
817 break;
818 case Direction::BACKWARD: // Insert new_node before curr (i.e., after curr->prev)
819 new_node->next = curr->prev->next; // Should be `curr` itself
820 new_node->prev = curr->prev;
821 curr->prev->next = new_node; // Update previous node's next pointer
822 curr->prev = new_node.get();
823
824 // Update LCP values.
825 new_node->lcp_next = lcp; // LCP between new_node and curr is the final LCP from iter_layer.
826 // new_node->prev->lcp_next remains unchanged (LCP between prev and new_node).
827 break;
829 // This case should not be reached here due to the check after iter_layer.
830 // If it were, it would indicate an error.
831 break;
832 }
833
834 // Return the newly created node so the level above can link its `down` pointer.
835 return new_node;
836}
@ BACKWARD
Represents traversal towards the beginning of the list (predecessor nodes).

◆ iter_layer()

template<typename CHAR_T , unsigned CHAR_SIZE_BITS>
Direction SkipTrie< CHAR_T, CHAR_SIZE_BITS >::iter_layer ( const KEY_T key,
Node *&  curr,
Direction  direction,
size_t lcp 
) const
protectednoexcept

Iterates horizontally along a single layer to find the node preceding the insertion/search point for a given key.

Starts from curr and moves in the specified direction. Compares the key with the keys of subsequent nodes, using LCP values for optimization. Updates curr to point to the immediate predecessor of the key's position on this layer. Updates the lcp parameter with the LCP value computed during the final relevant comparison.

Parameters
keyA pointer to the BitString key being searched for or inserted.
[in,out]currA reference to the pointer to the current node. Updated by the function to the predecessor node.
directionThe initial traversal direction (usually FORWARD or BACKWARD inherited from the level above).
[in,out]lcpA reference to the LCP length calculated so far. Updated by the function.
Returns
Direction The direction the search should proceed on the level below (FORWARD or BACKWARD), or INPLACE if the exact key was found.
See also
compare

Definition at line 646 of file SkipTrie.hpp.

647{
648 // If already found inplace, no need to iterate.
650 {
651 return Direction::INPLACE;
652 }
653
654 // Get the next node in the current direction.
655 Node* next = curr->next_node(direction);
656
657 // Iterate while the next node exists and the LCP allows potential match/crossing.
658 // Check `next->key` to ensure we don't compare against the tail sentinel.
659 // `curr->lcp(direction)` gives LCP between `curr` and `next`.
660 while (next->key && curr->lcp(direction) >= lcp)
661 {
662 // Optimization: If LCP between curr and next is strictly greater than current path LCP,
663 // we know the target key cannot be between curr and next based on prefixes. Skip over next.
664 if (curr->lcp(direction) > lcp)
665 {
666 curr = next;
667 next = curr->next_node(direction);
668 continue;
669 }
670
671 // If LCPs are equal, we must perform a full comparison starting from `lcp`.
672 auto [comparison, next_lcp] = compare(key, next->key, lcp);
673 // Update the path LCP with the result of the comparison.
674 lcp = next_lcp;
675
676 // Move to the next node.
677 curr = next;
678
679 // Check comparison result:
680 if (comparison == std::strong_ordering::equal)
681 {
682 // Exact match found.
683 return Direction::INPLACE;
684 }
685 else if (comparison == std::strong_ordering::less)
686 {
687 // Key is less than curr->key.
689 {
690 // If moving forward, we've gone past the insertion point. Need to go backward on level below.
691 return Direction::BACKWARD;
692 }
693 // If already moving backward, continue moving backward.
694 }
695 else // comparison == std::strong_ordering::greater
696 {
697 // Key is greater than curr->key.
699 {
700 // If moving backward, we've gone past the insertion point. Need to go forward on level below.
701 return Direction::FORWARD;
702 }
703 // If already moving forward, continue moving forward.
704 }
705
706 // Get the next node for the next iteration.
707 next = curr->next_node(direction);
708 }
709
710 // Loop finished: `next` is either the tail sentinel or `curr->lcp(direction) < lcp`.
711 // The correct direction to proceed on the level below is the current `direction`.
712 return direction;
713}
virtual ResultLCP compare(const KEY_T *key1, const KEY_T *key2, size_t lcp) const noexcept
Compares two keys starting from a known common prefix length.
Definition SkipTrie.hpp:638

◆ lcp()

template<typename CHAR_T , unsigned CHAR_SIZE_BITS>
size_t SkipTrie< CHAR_T, CHAR_SIZE_BITS >::lcp ( const KEY_T key) const
noexcept

Calculates the Longest Common Prefix (LCP) between a given key and its position in the skip list search path.

Performs a search (find_first) for the key and returns the LCP value computed during that search. If the key is found, this LCP is typically the full length of the key. If not found, it's the LCP with the path leading to the insertion point.

Parameters
keyA pointer to the BitString key.
Returns
size_t The calculated LCP length (in characters).
See also
find_first

Definition at line 878 of file SkipTrie.hpp.

879{
880 // Find the node (or insertion point) and return the LCP computed during search.
881 return find_first(key).lcp;
882}
size_t lcp
The LCP value computed during the search leading to this node.
Definition SkipTrie.hpp:317

◆ lcp_with_others()

template<typename CHAR_T , unsigned CHAR_SIZE_BITS>
size_t SkipTrie< CHAR_T, CHAR_SIZE_BITS >::lcp_with_others ( const KEY_T key) const
noexcept

Calculates the maximum Longest Common Prefix (LCP) between a given key and its immediate neighbors (predecessor and successor) at the base level.

Finds the node for the key (or its successor if not present) using find_first at level 0. Returns the maximum of the LCP with the next node and the LCP with the previous node at the base level. If the key is not found, it returns the LCP computed during the search for its potential position.

Parameters
keyA pointer to the BitString key.
Returns
size_t The maximum LCP length with its neighbors (or the search path LCP if not found).
See also
find_first
Node::lcp

Definition at line 885 of file SkipTrie.hpp.

886{
887 auto [node, lcp] = find_first(key, true);
888
889 if (!node)
890 {
891 return lcp;
892 }
893
894 return std::max(node->lcp(Direction::FORWARD), node->lcp(Direction::BACKWARD));
895}

◆ print()

template<typename CHAR_T , unsigned CHAR_SIZE_BITS>
std::ostream & SkipTrie< CHAR_T, CHAR_SIZE_BITS >::print ( std::ostream &  os) const
noexcept

Prints a textual representation of the skip list structure, layer by layer, to an output stream.

Traverses each layer from head to tail, printing node keys and the LCP values between adjacent nodes. Useful for debugging and visualizing the structure.

Parameters
osThe output stream (e.g., std::cout) to write the representation to.
Returns
std::ostream& A reference to the output stream os for chaining.

Definition at line 1038 of file SkipTrie.hpp.

1039{
1040 // print layer by layer
1041 auto left = m_head;
1042 size_t height = m_height - 1;
1043 while (left)
1044 {
1045 os << "Height: " << height << "|\t";
1046 os << "HEAD -0-> ";
1047 auto curr = left->next;
1048 while (curr->next)
1049 {
1050 // Print the key and the LCP to the next node.
1051 // Assumes KEY_T has an overloaded operator<<.
1052 os << *curr->key << " -" << curr->lcp_next << "-> ";
1053 curr = curr->next;
1054 }
1055
1056 os << "TAIL\n";
1057 left = left->down;
1058 --height;
1059 }
1060
1061 return os;
1062}

◆ range_search()

template<typename CHAR_T , unsigned CHAR_SIZE_BITS>
std::vector< const BitString< CHAR_T, CHAR_SIZE_BITS > * > SkipTrie< CHAR_T, CHAR_SIZE_BITS >::range_search ( const KEY_T key1,
const KEY_T key2 
) const
noexcept

Finds all keys within a specified lexicographical range [key1, key2).

Locates the starting node (equal to or successor of key1) using find_equal_or_successor. Locates the ending node (equal to or successor of key2). Iterates forward from the start node up to (but not including) the end node at the base level, collecting pointers to the keys.

Parameters
key1A pointer to the BitString representing the lower bound of the range (inclusive).
key2A pointer to the BitString representing the upper bound of the range (exclusive).
Returns
std::vector<const KEY_T*> A vector containing pointers to all keys within the specified range.
See also
find_equal_or_successor

Definition at line 960 of file SkipTrie.hpp.

961{
962 std::vector<const KEY_T*> keys;
963
964 // Find the starting node (>= key1) at the base level.
965 Node* curr = find_equal_or_successor(key1, true).node;
966
967 if (!curr)
968 {
969 return keys;
970 }
971
972 auto [end, is_equal] = find_equal_or_successor(key2, true);
973
974 if (is_equal)
975 {
976 end = end->next.get();
977 }
978
979 while (curr != end)
980 {
981 keys.push_back(curr->key);
982 curr = curr->next.get();
983 }
984
985 return keys;
986}
void push_back(CHAR_T c) noexcept
Appends a single character to the end of the BitString.
Iterator end() const noexcept
Returns an iterator pointing past the last key in the skip list (the tail sentinel at the base level)...
Definition SkipTrie.hpp:536
Node * node
Pointer to the node found (either exact match or the successor).
Definition SkipTrie.hpp:339

◆ remove()

template<typename CHAR_T , unsigned CHAR_SIZE_BITS>
bool SkipTrie< CHAR_T, CHAR_SIZE_BITS >::remove ( const KEY_T key)
noexcept

Removes a key from the skip list.

Finds the node containing the key using find_first. If found, removes the node and its corresponding nodes on all levels below it by adjusting pointers. Decrements the size. Removes empty top layers if necessary.

Parameters
keyA pointer to the BitString key to remove.
Returns
bool True if the key was found and removed, false otherwise.
See also
find_first
remove_layer

Definition at line 839 of file SkipTrie.hpp.

840{
841 Node* curr = find_first(key).node;
842
843 // If key not found, return false.
844 if (!curr)
845 {
846 return false;
847 }
848
849 // remove the node and all its children
850 while (curr)
851 {
852 auto prev = curr->prev;
853 auto next = curr->next;
854 prev->lcp_next = std::min(prev->lcp_next, curr->lcp_next);
855 curr = curr->down.get();
856
857 prev->next = next;
858 next->prev = prev;
859 }
860
861 // remove empty layers
862 while (!m_head->down || !m_head->down->next->key)
863 {
864 remove_layer();
865
866 if (m_height == 0)
867 {
868 break;
869 }
870 }
871
872 --m_size;
873
874 return true;
875}
void remove_layer() noexcept
Removes the top-most layer of the skip list if it is empty (contains only sentinels).
Definition SkipTrie.hpp:622
std::shared_ptr< Node > next
Shared pointer to the next node on the same level.
Definition SkipTrie.hpp:228
Node * prev
Raw pointer to the previous node on the same level.
Definition SkipTrie.hpp:230

◆ remove_layer()

template<typename CHAR_T , unsigned CHAR_SIZE_BITS>
void SkipTrie< CHAR_T, CHAR_SIZE_BITS >::remove_layer ( )
protectednoexcept

Removes the top-most layer of the skip list if it is empty (contains only sentinels).

Updates m_head to point to the head of the layer below and decrements m_height. Resets m_lower_head and m_lower_tail if the height becomes zero.

Note
Called by remove to potentially shrink the skip list height after deletions.

Definition at line 622 of file SkipTrie.hpp.

623{
624 // Move the head pointer down to the next layer.
625 m_head = m_head->down;
626 // Decrement the height.
627 --m_height;
628
629 // If height becomes 0, reset lower head/tail pointers.
630 if (m_height == 0)
631 {
632 m_lower_head = nullptr;
633 m_lower_tail = nullptr;
634 }
635}

◆ size()

template<typename CHAR_T , unsigned CHAR_SIZE_BITS = sizeof(CHAR_T) * 8>
size_t SkipTrie< CHAR_T, CHAR_SIZE_BITS >::size ( ) const
inlinenoexcept

Returns the number of keys currently stored in the skip list.

Returns
size_t The total number of keys (nodes at the base level).

Definition at line 142 of file SkipTrie.hpp.

142{ return m_size; }

◆ suffix_search()

template<typename CHAR_T , unsigned CHAR_SIZE_BITS>
std::vector< const BitString< CHAR_T, CHAR_SIZE_BITS > * > SkipTrie< CHAR_T, CHAR_SIZE_BITS >::suffix_search ( const KEY_T key) const
noexcept

Finds all keys in the skip list that have the given key as a prefix.

Performs a search (find_equal_or_successor) to locate the first potential match. If the LCP at that point equals the length of the input key, it iterates forward through the base level as long as the LCP between adjacent nodes is greater than or equal to the input key's length, collecting all matching keys.

Parameters
keyA pointer to the BitString representing the prefix to search for.
Returns
std::vector<const KEY_T*> A vector containing pointers to all keys that have key as a prefix. Returns an empty vector if no such keys are found.
See also
find_equal_or_successor

Definition at line 898 of file SkipTrie.hpp.

899{
900 std::vector<const KEY_T*> keys;
901
902 auto [curr, _, lcp] = find_equal_or_successor(key, true).node;
903
904 if (lcp != key->size())
905 {
906 return keys; // No keys with this prefix found.
907 }
908
909 keys.push_back(curr->key);
910
911 // iterate through the list until the edge lcp is less than the key length
912 while (curr->next->key && curr->lcp_next >= lcp)
913 {
914 curr = curr->next.get();
915 keys.push_back(curr->key);
916 }
917
918 return keys;
919}

◆ to_string()

template<typename CHAR_T , unsigned CHAR_SIZE_BITS>
std::string SkipTrie< CHAR_T, CHAR_SIZE_BITS >::to_string ( ) const
noexcept

Generates a string representation of the skip list structure.

Similar to print, but captures the output into a std::string.

Returns
std::string A string containing the textual representation of the skip list.
See also
print

Definition at line 1065 of file SkipTrie.hpp.

1066{
1067 // Use a string stream to capture the output of the print method.
1068 std::ostringstream oss;
1069 print(oss);
1070 return oss.str();
1071}
std::ostream & print(std::ostream &os) const noexcept
Prints a textual representation of the skip list structure, layer by layer, to an output stream.

Friends And Related Symbol Documentation

◆ operator<<()

template<typename CHAR_T , unsigned CHAR_SIZE_BITS>
std::ostream & operator<< ( std::ostream &  os,
const SkipTrie< CHAR_T, CHAR_SIZE_BITS > &  ssl 
)
related

Overload of the stream insertion operator (<<) for SkipTrie.

Allows printing a SkipTrie object directly to an output stream using os << skipTrie;. Delegates the actual printing logic to the SkipTrie::print method.

Template Parameters
CHAR_TThe character type of the keys.
CHAR_SIZE_BITSThe number of significant bits per character.
Parameters
osThe output stream (e.g., std::cout).
sslThe SkipTrie object to print.
Returns
std::ostream& A reference to the output stream os.

Definition at line 1032 of file SkipTrie.hpp.

1032 {
1033 // Delegate to the print method.
1034 return ssl.print(os);
1035}

Member Data Documentation

◆ m_head

template<typename CHAR_T , unsigned CHAR_SIZE_BITS = sizeof(CHAR_T) * 8>
std::shared_ptr<Node> SkipTrie< CHAR_T, CHAR_SIZE_BITS >::m_head = nullptr
protected

Shared pointer to the head sentinel node of the top-most layer.

Definition at line 252 of file SkipTrie.hpp.

◆ m_height

template<typename CHAR_T , unsigned CHAR_SIZE_BITS = sizeof(CHAR_T) * 8>
size_t SkipTrie< CHAR_T, CHAR_SIZE_BITS >::m_height
protected

Stores the current maximum height (number of levels) of the skip list.

Definition at line 215 of file SkipTrie.hpp.

◆ m_lower_head

template<typename CHAR_T , unsigned CHAR_SIZE_BITS = sizeof(CHAR_T) * 8>
std::shared_ptr<Node> SkipTrie< CHAR_T, CHAR_SIZE_BITS >::m_lower_head = nullptr
protected

Shared pointer to the head sentinel node of the base layer (level 0).

Definition at line 254 of file SkipTrie.hpp.

◆ m_lower_tail

template<typename CHAR_T , unsigned CHAR_SIZE_BITS = sizeof(CHAR_T) * 8>
std::shared_ptr<Node> SkipTrie< CHAR_T, CHAR_SIZE_BITS >::m_lower_tail = nullptr
protected

Shared pointer to the tail sentinel node of the base layer (level 0).

Definition at line 256 of file SkipTrie.hpp.

◆ m_size

template<typename CHAR_T , unsigned CHAR_SIZE_BITS = sizeof(CHAR_T) * 8>
size_t SkipTrie< CHAR_T, CHAR_SIZE_BITS >::m_size
protected

Stores the total number of keys (nodes) in the base level of the skip list.

Definition at line 213 of file SkipTrie.hpp.


The documentation for this class was generated from the following file: