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

Implements a zip trie, a data structure combining properties of tries and zip trees for storing strings efficiently. More...

#include <ZipTrie.hpp>

Inheritance diagram for ZipTrie< CHAR_T, MEMORY_EFFICIENT, RANK_T, CHAR_SIZE_BITS >:
Inheritance graph
[legend]

Classes

struct  AncestorLCPs
 Stores the Longest Common Prefix (LCP) values associated with the path from the root down to a node's parent. More...
 
struct  Bucket
 Represents a single node (bucket) within the ZipTrie's internal storage. More...
 
struct  ComparisonResult
 Holds the result of comparing a search/insert key x with a node v's key, considering the context of ancestor LCPs. More...
 
struct  SearchResults
 Holds the results returned by a search operation (search method). More...
 

Public Types

using LCP_T = std::conditional_t< MEMORY_EFFICIENT, MemoryEfficientLCP, unsigned >
 The type used to store Longest Common Prefix values.
 
using KEY_T = BitString< CHAR_T, CHAR_SIZE_BITS >
 The type used for keys, an instantiation of the BitString template.
 

Public Member Functions

 ZipTrie (unsigned max_size, unsigned max_lcp_length) noexcept
 Constructs an empty ZipTrie.
 
int get_depth (const KEY_T *key) const noexcept
 Calculates the depth of a given key in the trie.
 
int height () const noexcept
 Calculates the height of the trie.
 
double get_average_height () const noexcept
 Calculates the average depth of all nodes currently in the trie.
 
unsigned size () const noexcept
 Returns the number of nodes (keys) currently stored in the trie.
 
bool contains (const KEY_T *key) const noexcept
 Checks if a key exists within the trie.
 
LCP_T lcp (const KEY_T *key) const noexcept
 Finds the Longest Common Prefix (LCP) between the search key and the path taken in the trie.
 
void insert (const KEY_T *key) noexcept
 Inserts a key into the zip trie.
 
const RANK_Tget_root_rank () const noexcept
 Removes a node with a given key from the zip tree. (Not implemented)
 
void set (const KEY_T *key, RANK_T rank, unsigned left, unsigned right, LCP_T predecessor_lcp, LCP_T successor_lcp) noexcept
 Directly adds a pre-constructed bucket to the internal storage vector.
 
void set_root_index (unsigned root_index) noexcept
 Directly sets the root index of the trie.
 
virtual SearchResults search (const KEY_T *key) const noexcept
 Performs a search for a key within the ZipTrie.
 
void to_dot (const std::string &file_path) const noexcept
 Generates a DOT language representation of the trie structure for visualization.
 

Protected Member Functions

MemoryEfficientLCP get_memory_efficient_lcp (size_t num) const noexcept
 Converts a standard LCP length (size_t) into the MemoryEfficientLCP format.
 
template<typename CompareFunction >
SearchResults search_recursive (const KEY_T *key, CompareFunction compare_func) const noexcept
 Recursive helper function for the public search method.
 
template<typename CompareFunction >
unsigned insert_recursive (Bucket *x, unsigned x_index, unsigned v_index, AncestorLCPs ancestor_lcps, CompareFunction compare_func) noexcept
 Recursive helper function for inserting a new node x.
 
std::optional< ComparisonResultk_compare_prefix_check (const Bucket &v, const AncestorLCPs &ancestor_lcps, size_t &out_start_lcp_val) const noexcept
 Performs an initial check based on ancestor LCPs before full key comparison (k-compare optimization).
 
LCP_T convert_lcp (size_t val) const noexcept
 Converts an exact LCP length (e.g., number of matching characters/bits) to the LCP_T type.
 

Protected Attributes

unsigned _root_index
 Index of the root node within the _buckets vector. Initialized to NULLPTR for an empty trie.
 
unsigned _max_lcp_length
 Stores the maximum possible LCP length provided during construction.
 
uint8_t _log_max_size
 Stores the approximate log base 2 of the max_size provided during construction.
 
std::vector< Bucket_buckets
 A contiguous vector storing all the nodes (Buckets) of the trie.
 

Static Protected Attributes

static constexpr unsigned NULLPTR = std::numeric_limits<unsigned>::max()
 Represents a null child pointer index using the maximum value of unsigned.
 

Private Member Functions

ComparisonResult k_compare (const KEY_T *x, const Bucket &v, const AncestorLCPs &ancestor_lcps) const noexcept
 Compares a key x with the key in a node v, utilizing ancestor LCPs for optimization (k-compare).
 
int height (unsigned node_index) const noexcept
 Recursive helper function to calculate the height of the subtree rooted at node_index.
 
uint64_t get_total_depth (unsigned node_index, uint64_t depth) const noexcept
 Recursive helper function to calculate the sum of the depths of all nodes within a subtree.
 
void to_dot_recursive (std::ofstream &os, unsigned node_index) const noexcept
 Recursive helper function to generate the DOT language representation for a subtree.
 

Detailed Description

template<typename CHAR_T, bool MEMORY_EFFICIENT, typename RANK_T = GeometricRank, unsigned CHAR_SIZE_BITS = sizeof(CHAR_T) * 8>
class ZipTrie< CHAR_T, MEMORY_EFFICIENT, RANK_T, CHAR_SIZE_BITS >

Implements a zip trie, a data structure combining properties of tries and zip trees for storing strings efficiently.

A zip trie stores keys (represented by BitString) and associated ranks (RANK_T). It maintains a tree structure based on both key prefixes (like a standard trie) and random ranks (like a zip tree). This combination aims to achieve balanced expected depth (logarithmic in the number of elements) while efficiently handling shared prefixes common in string datasets.

The structure relies on comparing keys based on their Longest Common Prefix (LCP) with ancestor paths (k_compare optimization) to navigate the trie efficiently, avoiding redundant character comparisons. 'Zip' operations based on node ranks ensure the max-heap property (the "zip property") is maintained with respect to ranks, leading to the balanced structure on average.

Template Parameters
CHAR_TThe underlying character type used in the keys (BitString). Typically char or uint8_t.
MEMORY_EFFICIENTIf true, uses MemoryEfficientLCP for storing LCP values, trading precision for memory savings. If false, uses unsigned for exact LCP storage.
RANK_TThe type used for node ranks (default: GeometricRank). Must provide a static get_random() method and support comparison operators (<=>).
CHAR_SIZE_BITSThe number of significant bits per character in CHAR_T. Defaults to sizeof(CHAR_T) * 8. Allows handling types where not all bits are used (e.g., 7-bit ASCII in an 8-bit char, 2-bit nucleotides in DNA strings).
See also
BitString
GeometricRank
MemoryEfficientLCP
k_compare
insert_recursive

Definition at line 125 of file ZipTrie.hpp.

Member Typedef Documentation

◆ KEY_T

template<typename CHAR_T , bool MEMORY_EFFICIENT, typename RANK_T = GeometricRank, unsigned CHAR_SIZE_BITS = sizeof(CHAR_T) * 8>
using ZipTrie< CHAR_T, MEMORY_EFFICIENT, RANK_T, CHAR_SIZE_BITS >::KEY_T = BitString<CHAR_T, CHAR_SIZE_BITS>

The type used for keys, an instantiation of the BitString template.

See also
BitString

Definition at line 137 of file ZipTrie.hpp.

◆ LCP_T

template<typename CHAR_T , bool MEMORY_EFFICIENT, typename RANK_T = GeometricRank, unsigned CHAR_SIZE_BITS = sizeof(CHAR_T) * 8>
using ZipTrie< CHAR_T, MEMORY_EFFICIENT, RANK_T, CHAR_SIZE_BITS >::LCP_T = std::conditional_t<MEMORY_EFFICIENT, MemoryEfficientLCP, unsigned>

The type used to store Longest Common Prefix values.

This is MemoryEfficientLCP if MEMORY_EFFICIENT is true, otherwise unsigned.

Definition at line 132 of file ZipTrie.hpp.

Constructor & Destructor Documentation

◆ ZipTrie()

template<typename CHAR_T , bool MEMORY_EFFICIENT, typename RANK_T , unsigned CHAR_SIZE_BITS>
ZipTrie< CHAR_T, MEMORY_EFFICIENT, RANK_T, CHAR_SIZE_BITS >::ZipTrie ( unsigned  max_size,
unsigned  max_lcp_length 
)
noexcept

Constructs an empty ZipTrie.

Parameters
max_sizeHint for reserving memory in the internal node storage (_buckets). Pre-allocating can potentially avoid reallocations during insertions if the size is known approximately.
max_lcp_lengthThe maximum possible LCP length (in characters or bits, depending on BitString) expected between any two keys. This is crucial for the scaling of MemoryEfficientLCP if enabled.
Note
Setting max_lcp_length accurately is important for the precision of approximate LCPs when MEMORY_EFFICIENT is true.
The constructor calculates _log_max_size based on max_size for LCP scaling.

Definition at line 558 of file ZipTrie.hpp.

561 // Calculate log2(max_size) safely, handling max_size = 0 case.
562 _log_max_size(max_size > 0 ? static_cast<uint8_t>(std::log2(static_cast<double>(max_size))) : 0)
563{
564 // Reserve space in the vector to potentially avoid reallocations if max_size is a good estimate.
565 _buckets.reserve(max_size);
566}
static constexpr unsigned NULLPTR
Represents a null child pointer index using the maximum value of unsigned.
Definition ZipTrie.hpp:326
std::vector< Bucket > _buckets
A contiguous vector storing all the nodes (Buckets) of the trie.
Definition ZipTrie.hpp:379
uint8_t _log_max_size
Stores the approximate log base 2 of the max_size provided during construction.
Definition ZipTrie.hpp:323
unsigned _root_index
Index of the root node within the _buckets vector. Initialized to NULLPTR for an empty trie.
Definition ZipTrie.hpp:312
unsigned _max_lcp_length
Stores the maximum possible LCP length provided during construction.
Definition ZipTrie.hpp:317
T * alloc_to_device(size_t size)
Allocates memory on the CUDA device.

Member Function Documentation

◆ contains()

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

Checks if a key exists within the trie.

Parameters
keyPointer to the key to search for.
Returns
bool True if a node with the exact key exists, false otherwise.
See also
search

Definition at line 569 of file ZipTrie.hpp.

570{
571 // Delegate the actual search logic to the search method.
572 return search(key).contains;
573}
virtual SearchResults search(const KEY_T *key) const noexcept
Performs a search for a key within the ZipTrie.
Definition ZipTrie.hpp:671
bool contains
True if a node with the exact key was found in the trie, false otherwise.
Definition ZipTrie.hpp:273

◆ convert_lcp()

template<typename CHAR_T , bool MEMORY_EFFICIENT, typename RANK_T , unsigned CHAR_SIZE_BITS>
ZipTrie< CHAR_T, MEMORY_EFFICIENT, RANK_T, CHAR_SIZE_BITS >::LCP_T ZipTrie< CHAR_T, MEMORY_EFFICIENT, RANK_T, CHAR_SIZE_BITS >::convert_lcp ( size_t  val) const
inlineprotectednoexcept

Converts an exact LCP length (e.g., number of matching characters/bits) to the LCP_T type.

If MEMORY_EFFICIENT is true, this calls get_memory_efficient_lcp to perform the approximation. Otherwise (if MEMORY_EFFICIENT is false), it performs a simple static cast to unsigned (which is LCP_T).

Parameters
valThe exact LCP length (size_t) to convert.
Returns
LCP_T The LCP value in the appropriate storage format (MemoryEfficientLCP or unsigned).
See also
get_memory_efficient_lcp
Note
This function is conditionally compiled based on MEMORY_EFFICIENT.

Definition at line 636 of file ZipTrie.hpp.

637{
638 if constexpr (MEMORY_EFFICIENT) {
639 // If memory efficiency is enabled, call the approximation function.
641 } else {
642 // Otherwise, just cast the exact value to the LCP type (unsigned).
643 // Ensure the value fits; consider potential truncation if LCP_T were smaller than size_t.
644 return static_cast<LCP_T>(val);
645 }
646}
MemoryEfficientLCP get_memory_efficient_lcp(size_t num) const noexcept
Converts a standard LCP length (size_t) into the MemoryEfficientLCP format.
Definition ZipTrie.hpp:928
std::conditional_t< MEMORY_EFFICIENT, MemoryEfficientLCP, unsigned > LCP_T
The type used to store Longest Common Prefix values.
Definition ZipTrie.hpp:132

◆ get_average_height()

template<typename CHAR_T , bool MEMORY_EFFICIENT, typename RANK_T , unsigned CHAR_SIZE_BITS>
double ZipTrie< CHAR_T, MEMORY_EFFICIENT, RANK_T, CHAR_SIZE_BITS >::get_average_height ( ) const
noexcept

Calculates the average depth of all nodes currently in the trie.

This provides a measure of the average search cost. A perfectly balanced binary tree would have an average depth logarithmic in the number of nodes.

Returns
double The average depth. Returns 0.0 for an empty trie.
See also
get_total_depth

Definition at line 896 of file ZipTrie.hpp.

897{
898 // Get the total number of nodes.
899 unsigned current_size = size();
900 // Handle the case of an empty trie to avoid division by zero.
901 if (current_size == 0)
902 {
903 return 0.0; // Average depth of an empty trie is 0.
904 }
905 // Calculate the sum of depths of all nodes using the recursive helper, starting at the root (depth 0).
907 // Compute the average by dividing the total depth sum by the number of nodes.
908 return static_cast<double>(total_depth_sum) / current_size;
909}
uint64_t get_total_depth(unsigned node_index, uint64_t depth) const noexcept
Recursive helper function to calculate the sum of the depths of all nodes within a subtree.
Definition ZipTrie.hpp:912
unsigned size() const noexcept
Returns the number of nodes (keys) currently stored in the trie.
Definition ZipTrie.hpp:857

◆ get_depth()

template<typename CHAR_T , bool MEMORY_EFFICIENT, typename RANK_T , unsigned CHAR_SIZE_BITS>
int ZipTrie< CHAR_T, MEMORY_EFFICIENT, RANK_T, CHAR_SIZE_BITS >::get_depth ( const KEY_T key) const
noexcept

Calculates the depth of a given key in the trie.

The depth is the number of edges from the root to the node containing the key. The root itself is at depth 0.

Parameters
keyPointer to the key to search for.
Returns
int The depth of the key if found, or -1 if the key is not present in the trie.
See also
search

Definition at line 888 of file ZipTrie.hpp.

889{
890 // Perform a standard search for the key and return the depth field from the results.
891 // If the key is not found, search returns depth -1, which is correctly propagated here.
892 return search(key).depth;
893}
int depth
The depth at which the key was found (number of edges from the root, root is depth 0).
Definition ZipTrie.hpp:284

◆ get_memory_efficient_lcp()

template<typename CHAR_T , bool MEMORY_EFFICIENT, typename RANK_T , unsigned CHAR_SIZE_BITS>
MemoryEfficientLCP ZipTrie< CHAR_T, MEMORY_EFFICIENT, RANK_T, CHAR_SIZE_BITS >::get_memory_efficient_lcp ( size_t  num) const
inlineprotectednoexcept

Converts a standard LCP length (size_t) into the MemoryEfficientLCP format.

This is an internal helper function used only when MEMORY_EFFICIENT is true. It approximates the LCP value to fit into the smaller MemoryEfficientLCP struct, using _log_max_size for scaling the approximation.

Parameters
numThe exact LCP length (typically number of matching characters or bits) to convert.
Returns
MemoryEfficientLCP The approximate LCP value in the memory-efficient representation.
Note
This function is only compiled and used if MEMORY_EFFICIENT is true.
See also
convert_lcp

Definition at line 928 of file ZipTrie.hpp.

929{
930 // Handle LCPs smaller than the log of max size directly.
931 if (num < _log_max_size)
932 {
933 // Store the exact value in 'multiple', with exponent 0.
934 return { 0, static_cast<uint8_t>(num) };
935 }
936
937 // Otherwise, calculate exponent and multiple for approximation.
939 lcp.exp_of_2 = std::log2(num / _log_max_size);
940 lcp.multiple = num / (1u << lcp.exp_of_2);
941
942 return lcp;
943}
LCP_T lcp(const KEY_T *key) const noexcept
Finds the Longest Common Prefix (LCP) between the search key and the path taken in the trie.
Definition ZipTrie.hpp:576
Represents an approximate Longest Common Prefix (LCP) length in a memory-efficient format.
Definition ZipTrie.hpp:37
uint8_t exp_of_2
The exponent part of the LCP representation (base 2).
Definition ZipTrie.hpp:39

◆ get_root_rank()

template<typename CHAR_T , bool MEMORY_EFFICIENT, typename RANK_T , unsigned CHAR_SIZE_BITS>
const RANK_T & ZipTrie< CHAR_T, MEMORY_EFFICIENT, RANK_T, CHAR_SIZE_BITS >::get_root_rank ( ) const
noexcept

Removes a node with a given key from the zip tree. (Not implemented)

Parameters
keyThe key of the node to remove.
Returns
bool True if a node was found and removed, false otherwise.
Note
This functionality is declared but not implemented in the provided code. Implementing removal in zip trees/tries requires 'unzip' operation.

Gets the rank of the root node.

Returns
const RANK_T& A constant reference to the root node's rank.
Warning
Behavior is undefined if the trie is empty (_root_index == NULLPTR). Accessing _buckets[_root_index] would be invalid.

Definition at line 1031 of file ZipTrie.hpp.

1032{
1033 // Assert or check for empty trie might be useful here in a debug build.
1034 // Example: assert(_root_index != NULLPTR && "Cannot get rank of root in an empty trie.");
1035 return _buckets[_root_index].rank;
1036}

◆ get_total_depth()

template<typename CHAR_T , bool MEMORY_EFFICIENT, typename RANK_T , unsigned CHAR_SIZE_BITS>
uint64_t ZipTrie< CHAR_T, MEMORY_EFFICIENT, RANK_T, CHAR_SIZE_BITS >::get_total_depth ( unsigned  node_index,
uint64_t  depth 
) const
privatenoexcept

Recursive helper function to calculate the sum of the depths of all nodes within a subtree.

Used by get_average_height. Traverses the subtree and sums the depth of each node.

Parameters
node_indexIndex of the root of the subtree.
depthThe depth of the node_index node itself (relative to the overall trie root).
Returns
uint64_t The sum of the depths of all nodes in the subtree rooted at node_index. Each node's depth contributes to the sum. Returns 0 for an empty subtree (NULLPTR).
See also
get_average_height()

Definition at line 912 of file ZipTrie.hpp.

913{
914 // Base case: An empty subtree contributes 0 to the total depth sum.
915 if (node_index == NULLPTR)
916 {
917 return 0;
918 }
919
920 // Recursive step: The total depth sum for the subtree rooted at node_index is:
921 // - The depth of the current node (`depth`)
922 // - Plus the total depth sum of the left subtree (nodes in the left subtree are one level deeper)
923 // - Plus the total depth sum of the right subtree (nodes in the right subtree are one level deeper)
924 return depth + get_total_depth(_buckets[node_index].left, depth + 1) + get_total_depth(_buckets[node_index].right, depth + 1);
925}

◆ height() [1/2]

template<typename CHAR_T , bool MEMORY_EFFICIENT, typename RANK_T , unsigned CHAR_SIZE_BITS>
int ZipTrie< CHAR_T, MEMORY_EFFICIENT, RANK_T, CHAR_SIZE_BITS >::height ( ) const
noexcept

Calculates the height of the trie.

The height is the number of edges on the longest path from the root node to any leaf node. An empty trie has height -1. A trie with only a root node has height 0.

Returns
int The height of the trie.

Definition at line 867 of file ZipTrie.hpp.

868{
869 // Delegate to the recursive helper function starting at the root index.
870 return height(_root_index);
871}
int height() const noexcept
Calculates the height of the trie.
Definition ZipTrie.hpp:867

◆ height() [2/2]

template<typename CHAR_T , bool MEMORY_EFFICIENT, typename RANK_T , unsigned CHAR_SIZE_BITS>
int ZipTrie< CHAR_T, MEMORY_EFFICIENT, RANK_T, CHAR_SIZE_BITS >::height ( unsigned  node_index) const
privatenoexcept

Recursive helper function to calculate the height of the subtree rooted at node_index.

Parameters
node_indexIndex of the root of the subtree in the _buckets vector.
Returns
int The height of the subtree (longest path from node_index to a leaf in its subtree). Returns -1 if node_index is NULLPTR (representing an empty subtree).
See also
height()

Definition at line 874 of file ZipTrie.hpp.

875{
876 // Base case: An empty subtree (represented by NULLPTR) has a height of -1.
877 if (node_index == NULLPTR)
878 {
879 return -1;
880 }
881
882 // Recursive step: The height of a non-empty subtree is 1 (for the current node)
883 // plus the maximum of the heights of its left and right subtrees.
884 return std::max(height(_buckets[node_index].left), height(_buckets[node_index].right)) + 1;
885}

◆ insert()

template<typename CHAR_T , bool MEMORY_EFFICIENT, typename RANK_T , unsigned CHAR_SIZE_BITS>
void ZipTrie< CHAR_T, MEMORY_EFFICIENT, RANK_T, CHAR_SIZE_BITS >::insert ( const KEY_T key)
noexcept

Inserts a key into the zip trie.

A new node (Bucket) is created for the key, assigned a random rank using RANK_T::get_random(), and added to the internal _buckets storage. The insert_recursive helper method is then called to place this new node into the correct position according to key order (search tree property) and perform zip operations based on rank comparisons to maintain the max-heap property (max-heap property). The provided key pointer is stored directly in the node; the zip trie does not take ownership.

Parameters
keyPointer to the new key to insert. Must be non-null and point to a valid KEY_T object.
Warning
Key Lifetime: The lifetime of the object pointed to by key must exceed the lifetime of the zip trie or at least until the key is removed (if removal were implemented). The trie stores only the pointer.
Uniqueness: Assumes keys are unique. Inserting a key that already exists (or compares as equal) results in the insertion being effectively skipped by insert_recursive, leaving the tree structure unchanged. However, the new bucket added in this function might remain unused in the _buckets vector.
See also
insert_recursive
RANK_T::get_random

Definition at line 743 of file ZipTrie.hpp.

744{
745 // Add a new bucket to the vector. Initialize with key and a new random rank.
746 // Children and ancestor LCPs will be set during the recursive insertion.
747 _buckets.push_back({ key, RANK_T::get_random() });
748 // Get the index of the newly added bucket.
749 unsigned new_node_index = _buckets.size() - 1;
750
751 // Define the comparison function lambda, similar to the search method.
752 auto sequential_compare = [&](const KEY_T* k, const Bucket& v, const AncestorLCPs& ancestor_lcps)
753 {
754 return this->k_compare(k, v, ancestor_lcps); // Use the standard k_compare
755 };
756
757 // Call the recursive insert helper to place the new node and update the tree structure.
758 // Start recursion from the root (_root_index), passing the new node's pointer and index,
759 // initial empty ancestor LCPs, and the comparison function.
760 // Update the _root_index with the result, as the root might change due to the zip operation.
762}
unsigned insert_recursive(Bucket *x, unsigned x_index, unsigned v_index, AncestorLCPs ancestor_lcps, CompareFunction compare_func) noexcept
Recursive helper function for inserting a new node x.
Definition ZipTrie.hpp:766
BitString< CHAR_T, CHAR_SIZE_BITS > KEY_T
The type used for keys, an instantiation of the BitString template.
Definition ZipTrie.hpp:137
ComparisonResult k_compare(const KEY_T *x, const Bucket &v, const AncestorLCPs &ancestor_lcps) const noexcept
Compares a key x with the key in a node v, utilizing ancestor LCPs for optimization (k-compare).
Definition ZipTrie.hpp:649

◆ insert_recursive()

template<typename CHAR_T , bool MEMORY_EFFICIENT, typename RANK_T , unsigned CHAR_SIZE_BITS>
unsigned ZipTrie< CHAR_T, MEMORY_EFFICIENT, RANK_T, CHAR_SIZE_BITS >::insert_recursive ( Bucket x,
unsigned  x_index,
unsigned  v_index,
AncestorLCPs  ancestor_lcps,
CompareFunction  compare_func 
)
protectednoexcept

Recursive helper function for inserting a new node x.

Traverses the trie based on key comparisons (compare_func) and LCPs, updating ancestor_lcps along the path. When the correct insertion position (a NULLPTR link) is found, it inserts the node x. During the backtracking phase (as the recursion unwinds), it performs 'zip' operations to maintain the max-heap property based on ranks. If the rank of the newly inserted node x (or the root of the subtree returned by the recursive call) is higher than or equal to the rank of the current node v, the higher-ranked node is promoted. The function updates node LCPs (ancestor_lcps member) during operations to reflect the structural changes.

Template Parameters
CompareFunctionA callable type for comparing keys, same signature as in search_recursive.
Parameters
xPointer to the new bucket being inserted (already present in _buckets).
x_indexIndex of the new bucket x in the _buckets vector.
v_indexIndex of the current node (v) being visited in the recursion. Starts with _root_index.
ancestor_lcpsLCPs inherited from the path above v_index. These are updated based on the traversal direction.
compare_funcThe comparison function to use.
Returns
unsigned The index of the root of the modified subtree after potential insertion and updates. This will be either v_index (if no update occurred at this level or insertion happened deeper) or x_index (if x was promoted up to become the new root of this subtree).
Note
If compare_func determines the key already exists (returns equal), the insertion is aborted for that path, and v_index is returned, leaving the subtree rooted at v unchanged.

Definition at line 766 of file ZipTrie.hpp.

767{
768 // Base case: Reached a null pointer, meaning this is the insertion spot.
769 if (v_index == NULLPTR)
770 {
771 // Set the ancestor LCPs for the new node 'x' based on the path taken.
772 x->ancestor_lcps = ancestor_lcps;
773 // Return the index of the new node 'x', indicating it's the root of this (now non-empty) subtree.
774 return x_index;
775 }
776
777 // Get a reference to the current node 'v'.
778 Bucket& v_node = _buckets[v_index];
779
780 // Compare the new key 'x->key' with the current node's key 'v_node.key'.
781 auto [ comparison, lcp ] = compare_func(x->key, v_node, ancestor_lcps);
782
783 // If keys are equal, the key already exists. Do not insert duplicates.
784 // Return the current node's index, leaving the subtree unchanged.
785 if (comparison == std::strong_ordering::equal)
786 {
787 // Note: Could potentially update value associated with key here if storing values.
788 // Since we only store keys, we just return.
789 // Also, need to handle the bucket pushed back in `insert`. A robust implementation
790 // might check for existence *before* adding to _buckets or remove the unused bucket here.
791 // Current implementation leaves the unused bucket if key exists.
792 // We should pop the unused bucket here.
793 if (x_index == _buckets.size() - 1) { // Check if it's the last one added
794 _buckets.pop_back(); // Remove the unused bucket
795 } // Else, if not the last, it's harder to remove without messing up indices.
796
797 return v_index;
798 }
799
800 // Navigate left or right based on the comparison result.
801 if (comparison == std::strong_ordering::less)
802 {
803 // Go left. Recursively insert into the left subtree.
804 // Update the ancestor LCPs for the recursive call: predecessor stays the same,
805 // successor becomes the max of the current path's successor and the LCP just computed.
806 unsigned subroot_index = insert_recursive(x, x_index, v_node.left, { ancestor_lcps.predecessor, std::max(ancestor_lcps.successor, lcp) }, compare_func);
807
808 // If the recursive call returned an index other than the original left child,
809 // it means the structure below changed.
810 if (subroot_index == x_index && x->rank >= v_node.rank)
811 {
812 v_node.left = x->right; // v adopts x's right child as its left child.
813 x->right = v_index; // x takes v as its right child.
814 // Update LCPs stored *in the nodes* due to the structural change.
815 // v's predecessor LCP is now the LCP computed between x and v.
816 v_node.ancestor_lcps.predecessor = lcp;
817 // x's ancestor LCPs become those passed into this level (ancestor_lcps).
818 x->ancestor_lcps = ancestor_lcps;
819 // Return x_index as the new root of this subtree.
820 return x_index;
821 }
822 else
823 {
824 // Simply update v's left child pointer to the returned subtree root.
825 v_node.left = subroot_index;
826 }
827 // If subroot_index == v_node.left, the left subtree structure didn't change relative to v.
828 }
829 else // comparison == std::strong_ordering::greater
830 {
831 // Go right. Recursively insert into the right subtree.
832 // Update ancestor LCPs: successor stays same, predecessor updates.
833 unsigned subroot_index = insert_recursive(x, x_index, v_node.right, { std::max(ancestor_lcps.predecessor, lcp), ancestor_lcps.successor }, compare_func);
834
835 // If the recursive call returned an index other than the original right child.
836 if (subroot_index == x_index && x->rank >= v_node.rank)
837 {
838 v_node.right = x->left; // v adopts x's left child as its right child.
839 x->left = v_index; // x takes v as its left child.
840 // Update LCPs stored in the nodes.
841 v_node.ancestor_lcps.successor = lcp; // v's successor LCP is now the computed LCP.
842 x->ancestor_lcps = ancestor_lcps; // x inherits parent's LCPs.
843 // Return x_index as the new root.
844 return x_index;
845 }
846 else
847 {
848 v_node.right = subroot_index;
849 }
850 // If subroot_index == v_node.right, the right subtree structure didn't change relative to v.
851 }
852
853 return v_index;
854}

◆ k_compare()

template<typename CHAR_T , bool MEMORY_EFFICIENT, typename RANK_T , unsigned CHAR_SIZE_BITS>
ZipTrie< CHAR_T, MEMORY_EFFICIENT, RANK_T, CHAR_SIZE_BITS >::ComparisonResult ZipTrie< CHAR_T, MEMORY_EFFICIENT, RANK_T, CHAR_SIZE_BITS >::k_compare ( const KEY_T x,
const Bucket v,
const AncestorLCPs ancestor_lcps 
) const
inlineprivatenoexcept

Compares a key x with the key in a node v, utilizing ancestor LCPs for optimization (k-compare).

This is the primary comparison function used for navigating the Zip Trie during search and insertion. It first calls k_compare_prefix_check to attempt to determine the comparison result solely based on the LCP information stored along the ancestor paths. If k_compare_prefix_check returns std::nullopt (meaning the LCP check was inconclusive), this function proceeds to perform a direct comparison of the key data (x->key vs v.key) using BitString::seq_k_compare. Crucially, the BitString comparison starts from the bit/character offset (out_start_lcp_val) determined by the prefix check, avoiding redundant comparisons of the already-matched prefix.

Parameters
xPointer to the key being searched for or inserted.
vConstant reference to the current node (Bucket) being compared against.
ancestor_lcpsThe LCPs computed along the path from the root down to v's parent.
Returns
ComparisonResult A struct containing the relative ordering (comparison) of x vs v and the calculated Longest Common Prefix (lcp) between them in the context of this comparison (converted to LCP_T format).
See also
k_compare_prefix_check
BitString::seq_k_compare
convert_lcp

Definition at line 649 of file ZipTrie.hpp.

650{
651 size_t start_lcp_val = 0; // Initialize starting bit offset for comparison.
652
653 // Attempt to determine the result based on ancestor LCPs first.
654 if (auto prefix_result = k_compare_prefix_check(v, ancestor_lcps, start_lcp_val))
655 {
656 // If k_compare_prefix_check returned a result, use it directly.
657 return *prefix_result;
658 }
659
660 // If prefix check didn't yield a result, perform sequential key comparison
661 // starting from the calculated bit offset 'start_lcp_val'.
662 // Assumes KEY_T has a method `seq_k_compare(const KEY_T& other, size_t start_offset)`
663 // that returns a struct/pair like {std::strong_ordering, size_t actual_lcp}.
664 auto [comparison, actual_lcp_val] = x->seq_k_compare(*v.key, start_lcp_val);
665
666 // Convert the resulting exact LCP length to the appropriate LCP_T format.
667 return { comparison, convert_lcp(actual_lcp_val) };
668}
std::optional< ComparisonResult > k_compare_prefix_check(const Bucket &v, const AncestorLCPs &ancestor_lcps, size_t &out_start_lcp_val) const noexcept
Performs an initial check based on ancestor LCPs before full key comparison (k-compare optimization).
Definition ZipTrie.hpp:583
LCP_T convert_lcp(size_t val) const noexcept
Converts an exact LCP length (e.g., number of matching characters/bits) to the LCP_T type.
Definition ZipTrie.hpp:636

◆ k_compare_prefix_check()

template<typename CHAR_T , bool MEMORY_EFFICIENT, typename RANK_T , unsigned CHAR_SIZE_BITS>
std::optional< typename ZipTrie< CHAR_T, MEMORY_EFFICIENT, RANK_T, CHAR_SIZE_BITS >::ComparisonResult > ZipTrie< CHAR_T, MEMORY_EFFICIENT, RANK_T, CHAR_SIZE_BITS >::k_compare_prefix_check ( const Bucket v,
const AncestorLCPs ancestor_lcps,
size_t out_start_lcp_val 
) const
protectednoexcept

Performs an initial check based on ancestor LCPs before full key comparison (k-compare optimization).

This is a core optimization in zip trie navigation. It compares the LCPs accumulated along the current search path (ancestor_lcps) with the LCPs stored in the current node v (v.ancestor_lcps, which were the path LCPs when v was inserted). If these path LCPs differ significantly (x_max_lcp != corr_v_lcp), the relative order of the search key x and node v can often be determined without comparing the actual key data (x->key vs v.key). This check exploits the property that if the LCP context differs, the branching decision made when v was inserted must differ from the decision needed for x.

Parameters
vThe current node (Bucket) being considered.
ancestor_lcpsThe LCPs accumulated along the current search/insert path down to v's parent.
[out]out_start_lcp_valIf a full key comparison is needed (returns std::nullopt), this parameter is populated with the length (number of characters/bits) from which the actual key comparison (BitString::seq_k_compare) should start. This avoids re-comparing the prefix already known to match based on the LCP check.
Returns
std::optional<ComparisonResult>
  • If the comparison can be decided based on LCPs alone: returns a ComparisonResult containing the determined ordering (less or greater) and the minimum of the differing LCPs (std::min(x_max_lcp, corr_v_lcp)).
  • If a full key comparison is required (because x_max_lcp == corr_v_lcp): returns std::nullopt.
See also
k_compare

Definition at line 583 of file ZipTrie.hpp.

587{
588 // Get the LCPs from the current search path.
589 auto predecessor_lcp = ancestor_lcps.predecessor;
590 auto successor_lcp = ancestor_lcps.successor;
591
592 // Determine the maximum LCP encountered on the current search path.
593 auto x_max_lcp = std::max(predecessor_lcp, successor_lcp);
594 // Determine the corresponding LCP stored in node 'v' (from the path when 'v' was inserted).
595 // If the search path's max LCP came from the predecessor side, use v's predecessor LCP, otherwise use v's successor LCP.
596 auto corr_v_lcp = (predecessor_lcp > successor_lcp) ? v.ancestor_lcps.predecessor : v.ancestor_lcps.successor;
597
598 // If the maximum LCP on the current path (x_max_lcp) differs from the
599 // corresponding LCP recorded when node 'v' was inserted (corr_v_lcp)...
600 if (x_max_lcp != corr_v_lcp)
601 {
602 // ...we can determine the order based on which LCP is larger and which path it came from.
603 // The logic determines if x should be less or greater than v.
604 // `(x_max_lcp > corr_v_lcp)` is true if the current path's LCP is longer.
605 // `(predecessor_lcp > successor_lcp)` is true if the current path's max LCP came from the predecessor side.
606 // If these booleans match, x is less; otherwise, x is greater.
607 std::strong_ordering comparison_result =
608 ((x_max_lcp > corr_v_lcp) == (predecessor_lcp > successor_lcp)) ? std::strong_ordering::less : std::strong_ordering::greater;
609
610 // The resulting LCP for this comparison is the minimum of the two differing LCPs.
612
613 // Return the determined comparison result and LCP. No need for full key comparison.
614 return ComparisonResult{ comparison_result, result_lcp };
615 }
616
617 // If the LCPs matched (x_max_lcp == corr_v_lcp), we cannot decide the order yet.
618 // We need to perform a full key comparison, but we know the keys match up to `x_max_lcp`.
619 // Set the starting offset for the key comparison.
620 if constexpr (MEMORY_EFFICIENT)
621 {
622 // If using approximate LCPs, get the actual value.
624 }
625 else
626 {
627 // If using exact LCPs (unsigned), the value is directly usable.
629 }
630
631 // Indicate that a full key comparison should proceed by returning nullopt.
632 return std::nullopt;
633}

◆ lcp()

template<typename CHAR_T , bool MEMORY_EFFICIENT, typename RANK_T , unsigned CHAR_SIZE_BITS>
ZipTrie< CHAR_T, MEMORY_EFFICIENT, RANK_T, CHAR_SIZE_BITS >::LCP_T ZipTrie< CHAR_T, MEMORY_EFFICIENT, RANK_T, CHAR_SIZE_BITS >::lcp ( const KEY_T key) const
noexcept

Finds the Longest Common Prefix (LCP) between the search key and the path taken in the trie.

If the key is found (search(key).contains == true), this returns the LCP between the search key and the found key (which is typically the key's full length, assuming k_compare calculates full LCP on equality). If the key is not found (search(key).contains == false), it returns the LCP between the search key and the path leading to the point where the search terminated (i.e., the maximum LCP computed along the search path).

Parameters
keyPointer to the key to search for.
Returns
LCP_T The calculated LCP value, potentially approximate if MEMORY_EFFICIENT is true.
See also
search

Definition at line 576 of file ZipTrie.hpp.

577{
578 // Delegate the actual search logic to the search method.
579 return search(key).max_lcp;
580}
LCP_T max_lcp
The Longest Common Prefix (LCP) calculated during the search.
Definition ZipTrie.hpp:279

◆ search()

template<typename CHAR_T , bool MEMORY_EFFICIENT, typename RANK_T , unsigned CHAR_SIZE_BITS>
ZipTrie< CHAR_T, MEMORY_EFFICIENT, RANK_T, CHAR_SIZE_BITS >::SearchResults ZipTrie< CHAR_T, MEMORY_EFFICIENT, RANK_T, CHAR_SIZE_BITS >::search ( const KEY_T key) const
virtualnoexcept

Performs a search for a key within the ZipTrie.

Traverses the trie based on key comparisons (k_compare), updating ancestor LCPs along the path. The k_compare function utilizes stored LCP information to potentially avoid full key comparisons.

Parameters
keyPointer to the key to search for.
Returns
SearchResults A struct containing the outcome: whether the key was found (contains), the calculated maximum LCP along the path (max_lcp), and the depth if found (depth).
See also
search_recursive
k_compare

Reimplemented in ParallelZipTrie< CHAR_T, MEMORY_EFFICIENT, RANK_T, CHAR_SIZE_BITS >.

Definition at line 671 of file ZipTrie.hpp.

672{
673 // Define a lambda function that encapsulates the call to the standard k_compare method.
674 // This lambda matches the signature required by search_recursive.
675 auto sequential_compare = [&](const KEY_T* k, const Bucket& v, const AncestorLCPs& ancestor_lcps)
676 {
677 // Call the member function k_compare for the actual comparison logic.
678 return this->k_compare(k, v, ancestor_lcps);
679 };
680
681 // Call the recursive search helper, passing the key and the comparison lambda.
683}
SearchResults search_recursive(const KEY_T *key, CompareFunction compare_func) const noexcept
Recursive helper function for the public search method.
Definition ZipTrie.hpp:687

◆ search_recursive()

template<typename CHAR_T , bool MEMORY_EFFICIENT, typename RANK_T , unsigned CHAR_SIZE_BITS>
ZipTrie< CHAR_T, MEMORY_EFFICIENT, RANK_T, CHAR_SIZE_BITS >::SearchResults ZipTrie< CHAR_T, MEMORY_EFFICIENT, RANK_T, CHAR_SIZE_BITS >::search_recursive ( const KEY_T key,
CompareFunction  compare_func 
) const
protectednoexcept

Recursive helper function for the public search method.

Implements the core traversal logic iteratively (using a while loop) based on the results of the provided comparison function. It tracks the depth and updates ancestor LCPs along the path.

Template Parameters
CompareFunctionA callable type (e.g., lambda, function pointer) that takes (const KEY_T* key, const Bucket& v_node, const AncestorLCPs& ancestor_lcps) and returns a ComparisonResult. This allows customizing the comparison logic if needed, although typically it wraps k_compare.
Parameters
keyThe key being searched for.
compare_funcThe comparison function to use for navigating the trie.
Returns
SearchResults The result of the search operation, including found status, max LCP, and depth.

Definition at line 687 of file ZipTrie.hpp.

688{
689 // Handle empty trie case.
690 if (_root_index == NULLPTR)
691 {
692 // Return not found, default LCP, depth -1.
693 return { false, {}, -1 };
694 }
695
696 unsigned v_index = _root_index; // Start traversal at the root.
697 AncestorLCPs ancestor_lcps = {}; // Initialize ancestor LCPs to zero/default at the root.
698 int depth = 0; // Initialize depth counter.
699
700 // Iterate down the trie until a null pointer is reached or the key is found.
701 while (v_index != NULLPTR)
702 {
703 // Get a reference to the current node.
704 const Bucket& v_node = _buckets[v_index];
705
706 // Perform the comparison using the provided comparison function.
707 auto [ comparison, lcp ] = compare_func(key, v_node, ancestor_lcps);
708
709 // Check if the key is an exact match.
710 if (comparison == std::strong_ordering::equal)
711 {
712 // Key found. Return true, the final LCP, and the current depth.
713 return { true, lcp, depth };
714 }
715
716 // If not equal, decide whether to go left or right.
717 if (comparison == std::strong_ordering::less)
718 {
719 // Go left. Update the successor LCP for the path: it's the maximum of the
720 // current successor LCP and the LCP computed in this step.
721 ancestor_lcps.successor = std::max(ancestor_lcps.successor, lcp);
722 // Move to the left child.
723 v_index = v_node.left;
724 }
725 else // comparison == std::strong_ordering::greater
726 {
727 // Go right. Update the predecessor LCP similarly.
728 ancestor_lcps.predecessor = std::max(ancestor_lcps.predecessor, lcp);
729 // Move to the right child.
730 v_index = v_node.right;
731 }
732
733 // Increment depth for the next level.
734 ++depth;
735 }
736
737 // Key not found after traversing the path.
738 // Return false, the maximum LCP encountered along the path, and depth -1.
739 return { false, std::max(ancestor_lcps.predecessor, ancestor_lcps.successor), -1 };
740}

◆ set()

template<typename CHAR_T , bool MEMORY_EFFICIENT, typename RANK_T , unsigned CHAR_SIZE_BITS>
void ZipTrie< CHAR_T, MEMORY_EFFICIENT, RANK_T, CHAR_SIZE_BITS >::set ( const KEY_T key,
RANK_T  rank,
unsigned  left,
unsigned  right,
LCP_T  predecessor_lcp,
LCP_T  successor_lcp 
)
noexcept

Directly adds a pre-constructed bucket to the internal storage vector.

Warning
Testing/Internal Use Only: Intended for testing or specific initialization scenarios. This method bypasses the standard insertion logic (insert), including rank generation and maintaining the zip trie properties (key order, rank heap property). Using it improperly will likely corrupt the trie structure.
Parameters
keyPointer to the key for the new bucket.
rankThe rank to assign to the new bucket.
leftIndex of the left child (use NULLPTR for none).
rightIndex of the right child (use NULLPTR for none).
predecessor_lcpThe ancestor LCP with the predecessor path for this bucket.
successor_lcpThe ancestor LCP with the successor path for this bucket.

Definition at line 1039 of file ZipTrie.hpp.

1040{
1041 _buckets.push_back({ key, rank, left, right, {predecessor_lcp, successor_lcp} });
1042}

◆ set_root_index()

template<typename CHAR_T , bool MEMORY_EFFICIENT, typename RANK_T , unsigned CHAR_SIZE_BITS>
void ZipTrie< CHAR_T, MEMORY_EFFICIENT, RANK_T, CHAR_SIZE_BITS >::set_root_index ( unsigned  root_index)
noexcept

Directly sets the root index of the trie.

Warning
Testing/Internal Use Only: Intended for testing or specific initialization scenarios. This method bypasses the standard insertion logic. Using it improperly can lead to an invalid zip trie state (e.g., pointing to an invalid index or violating zip trie properties). Ensure the root_index points to a valid, intended root node within _buckets.
Parameters
root_indexThe index in _buckets to set as the new root.

Definition at line 1045 of file ZipTrie.hpp.

1046{
1048}

◆ size()

template<typename CHAR_T , bool MEMORY_EFFICIENT, typename RANK_T , unsigned CHAR_SIZE_BITS>
unsigned ZipTrie< CHAR_T, MEMORY_EFFICIENT, RANK_T, CHAR_SIZE_BITS >::size ( ) const
noexcept

Returns the number of nodes (keys) currently stored in the trie.

Returns
unsigned The number of nodes.
Note
If duplicate keys are inserted, the current implementation might add a bucket but not link it, potentially inflating this count slightly compared to the number of unique, reachable keys.

Definition at line 857 of file ZipTrie.hpp.

858{
859 // Simply return the current size of the underlying vector storing the nodes.
860 // Note: This assumes no gaps or marked-deleted nodes if removal were implemented differently.
861 // If duplicate keys caused buckets to be added but not used in `insert_recursive`,
862 // this size might be slightly inflated compared to unique keys. Consider adjusting if duplicates are handled differently.
863 return _buckets.size();
864}

◆ to_dot()

template<typename CHAR_T , bool MEMORY_EFFICIENT, typename RANK_T , unsigned CHAR_SIZE_BITS>
void ZipTrie< CHAR_T, MEMORY_EFFICIENT, RANK_T, CHAR_SIZE_BITS >::to_dot ( const std::string &  file_path) const
noexcept

Generates a DOT language representation of the trie structure for visualization.

Writes the DOT output to the specified file. This file can then be processed by tools like Graphviz (e.g., dot -Tpng input.dot -o output.png) to create a visual diagram of the trie, showing nodes (with keys, ranks, LCPs) and their parent-child relationships.

Parameters
file_pathThe path (including filename) where the output DOT file should be written.
Note
If the file cannot be opened, an error message is printed to std::cerr.
See also
to_dot_recursive

Definition at line 946 of file ZipTrie.hpp.

947{
948 // Attempt to open the specified file path for writing.
949 std::ofstream os(file_path);
950 if (!os) {
951 // If the file cannot be opened, print an error message to standard error.
952 std::cerr << "Error: Could not open file " << file_path << " for writing." << std::endl;
953 return; // Exit the function if file opening failed.
954 }
955 // Write the standard DOT graph header.
956 os << "digraph G {\n";
957 // Define default node attributes (shape=record allows complex labels).
958 os << "\tnode [shape=record, fontname=\"Arial\"];\n";
959 // Define default edge attributes.
960 os << "\tedge [fontname=\"Arial\"];\n";
961 // Call the recursive helper function to write the nodes and edges, starting from the root.
962 if (_root_index != NULLPTR) {
964 } else {
965 os << "\tlabel=\"Empty Trie\";\n"; // Indicate if the trie is empty
966 }
967 // Write the closing brace for the DOT graph definition.
968 os << "}\n";
969 // File stream `os` is automatically closed when it goes out of scope.
970}
void to_dot_recursive(std::ofstream &os, unsigned node_index) const noexcept
Recursive helper function to generate the DOT language representation for a subtree.
Definition ZipTrie.hpp:973

◆ to_dot_recursive()

template<typename CHAR_T , bool MEMORY_EFFICIENT, typename RANK_T , unsigned CHAR_SIZE_BITS>
void ZipTrie< CHAR_T, MEMORY_EFFICIENT, RANK_T, CHAR_SIZE_BITS >::to_dot_recursive ( std::ofstream &  os,
unsigned  node_index 
) const
privatenoexcept

Recursive helper function to generate the DOT language representation for a subtree.

Called by the public to_dot method. It defines the current node and its edges to its children in the DOT format and then recursively calls itself for the children. Includes node index, key, rank, and stored ancestor LCPs in the node label.

Parameters
osThe output file stream (std::ofstream) to write the DOT commands to.
node_indexIndex of the root of the subtree to output.
See also
to_dot()

Definition at line 973 of file ZipTrie.hpp.

974{
975 // Base case: If the node index represents a null pointer, do nothing further down this path.
976 if (node_index == NULLPTR)
977 {
978 return;
979 }
980
981 // Get a constant reference to the current node's data.
982 const Bucket& node = _buckets[node_index];
983
984 // Define the DOT representation for the current node.
985 // Use a unique ID "node_<index>" for each node.
986 // The label uses DOT's record shape syntax: { field1 | field2 | ... }
987 // Display the Index, Key, Rank (geometric, uniform), and stored Ancestor LCPs (predecessor, successor).
988 // Note: Assumes KEY_T and LCP_T have overloaded operator<< for printing.
989 // Note: Explicitly cast uint8_t ranks to unsigned for printing numeric values.
990 os << "\tnode_" << node_index << " [label=\"{Idx: " << node_index
991 << "|Key: " << *node.key // Assumes KEY_T is printable
992 << "|Rank: (" << static_cast<unsigned>(node.rank.geometric_rank) << "," << static_cast<unsigned>(node.rank.uniform_rank) << ")"
993 << "|LCPs: (" << node.ancestor_lcps.predecessor << ", " << node.ancestor_lcps.successor << ")}\"];\n";
994
995 // Process the left child.
996 if (node.left != NULLPTR)
997 {
998 // Draw an edge from the current node to the left child node.
999 // Label the edge " L" for clarity.
1000 os << "\tnode_" << node_index << " -> node_" << node.left << " [label=\" L\"];\n";
1001 // Recursively call the function for the left child.
1002 to_dot_recursive(os, node.left);
1003 }
1004 else
1005 {
1006 // Optional: Explicitly draw null pointers for visualization.
1007 // Create a small, invisible point node for the null left child.
1008 // os << "\tnull_left_" << node_index << " [shape=point, style=invis];\n";
1009 // Draw an edge to the null point.
1010 // os << "\tnode_" << node_index << " -> null_left_" << node_index << " [label=\" L\", style=dashed];\n";
1011 }
1012
1013 // Process the right child.
1014 if (node.right != NULLPTR)
1015 {
1016 // Draw an edge from the current node to the right child node.
1017 // Label the edge " R".
1018 os << "\tnode_" << node_index << " -> node_" << node.right << " [label=\" R\"];\n";
1019 // Recursively call the function for the right child.
1020 to_dot_recursive(os, node.right);
1021 }
1022 else
1023 {
1024 // Optional: Explicitly draw null pointers for visualization.
1025 // os << "\tnull_right_" << node_index << " [shape=point, style=invis];\n";
1026 // os << "\tnode_" << node_index << " -> null_right_" << node_index << " [label=\" R\", style=dashed];\n";
1027 }
1028}

Member Data Documentation

◆ _buckets

template<typename CHAR_T , bool MEMORY_EFFICIENT, typename RANK_T = GeometricRank, unsigned CHAR_SIZE_BITS = sizeof(CHAR_T) * 8>
std::vector<Bucket> ZipTrie< CHAR_T, MEMORY_EFFICIENT, RANK_T, CHAR_SIZE_BITS >::_buckets
protected

A contiguous vector storing all the nodes (Buckets) of the trie.

Node relationships (parent/child) are managed via indices (_root_index, left, right) within this vector, forming the tree structure implicitly.

Definition at line 379 of file ZipTrie.hpp.

◆ _log_max_size

template<typename CHAR_T , bool MEMORY_EFFICIENT, typename RANK_T = GeometricRank, unsigned CHAR_SIZE_BITS = sizeof(CHAR_T) * 8>
uint8_t ZipTrie< CHAR_T, MEMORY_EFFICIENT, RANK_T, CHAR_SIZE_BITS >::_log_max_size
protected

Stores the approximate log base 2 of the max_size provided during construction.

Note
Used for scaling LCP values when MEMORY_EFFICIENT is true.
See also
get_memory_efficient_lcp

Definition at line 323 of file ZipTrie.hpp.

◆ _max_lcp_length

template<typename CHAR_T , bool MEMORY_EFFICIENT, typename RANK_T = GeometricRank, unsigned CHAR_SIZE_BITS = sizeof(CHAR_T) * 8>
unsigned ZipTrie< CHAR_T, MEMORY_EFFICIENT, RANK_T, CHAR_SIZE_BITS >::_max_lcp_length
protected

Stores the maximum possible LCP length provided during construction.

Note
Primarily used for scaling calculations if MEMORY_EFFICIENT is true.

Definition at line 317 of file ZipTrie.hpp.

◆ _root_index

template<typename CHAR_T , bool MEMORY_EFFICIENT, typename RANK_T = GeometricRank, unsigned CHAR_SIZE_BITS = sizeof(CHAR_T) * 8>
unsigned ZipTrie< CHAR_T, MEMORY_EFFICIENT, RANK_T, CHAR_SIZE_BITS >::_root_index
protected

Index of the root node within the _buckets vector. Initialized to NULLPTR for an empty trie.

Definition at line 312 of file ZipTrie.hpp.

◆ NULLPTR

template<typename CHAR_T , bool MEMORY_EFFICIENT, typename RANK_T = GeometricRank, unsigned CHAR_SIZE_BITS = sizeof(CHAR_T) * 8>
constexpr unsigned ZipTrie< CHAR_T, MEMORY_EFFICIENT, RANK_T, CHAR_SIZE_BITS >::NULLPTR = std::numeric_limits<unsigned>::max()
staticconstexprprotected

Represents a null child pointer index using the maximum value of unsigned.

Definition at line 326 of file ZipTrie.hpp.


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