diff --git a/hhds/testing.cpp b/hhds/testing.cpp new file mode 100644 index 0000000..b4bf41e --- /dev/null +++ b/hhds/testing.cpp @@ -0,0 +1,25 @@ +#include + +class MyClass { +private: + void privateFunction() { + std::cout << "Private function called" << std::endl; + // Calling a public member function from a private member function + publicFunction(); + } +public: + void publicFunction() { + std::cout << "Public function called" << std::endl; + } + + void callPrivateFunction() { + privateFunction(); + } + +}; + +int main() { + MyClass obj; + obj.callPrivateFunction(); // This will call the private function indirectly + return 0; +} diff --git a/hhds/tree.cpp b/hhds/tree.cpp new file mode 100644 index 0000000..9d6c0b7 --- /dev/null +++ b/hhds/tree.cpp @@ -0,0 +1,116 @@ +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +using namespace std; + +using Tree_pos = uint64_t; + +static constexpr Tree_pos INVALID = 0; // ROOT ID +static constexpr Tree_pos ROOT = 0; // ROOT ID +static constexpr short CHUNK_BITS = 43; // Number of chunks in a tree node +static constexpr short SHORT_DELTA = 21; // Amount of short delta allowed +static constexpr short MAX_OFFSET = 3; // The number of bits in a chunk offset +static constexpr short CHUNK_SIZE = 1 << MAX_OFFSET; // Size of a chunk in bits +static constexpr short CHUNK_MASK = CHUNK_SIZE - 1; // Mask for chunk offset +static constexpr uint64_t MAX_TREE_SIZE = 1LL << CHUNK_BITS; // Maximum number of nodes in the tree + +struct __attribute__((packed)) Tree_pointers { + // We only store the exact ID of parent + Tree_pos parent : CHUNK_BITS + MAX_OFFSET; + Tree_pos next_sibling : CHUNK_BITS; + Tree_pos prev_sibling : CHUNK_BITS; + + // Long child pointers + Tree_pos first_child_l : CHUNK_BITS; + Tree_pos last_child_l : CHUNK_BITS; + + // Short (delta) child pointers + struct __attribute__((packed)) short_child { + Tree_pos delta : SHORT_DELTA; + } first_child_s[CHUNK_SIZE - 1], last_child_s[CHUNK_SIZE - 1]; + + // Gives 70 bits + // struct __attribute__((packed)) short_child { + // Tree_pos delta : SHORT_DELTA; + // } first_child_s[CHUNK_SIZE - 1], last_child_s[CHUNK_SIZE - 1]; + + // Short deltas + // These give 64 bit alignment + // Tree_pos first_child_s_0 : SHORT_DELTA; + // Tree_pos first_child_s_1 : SHORT_DELTA; + // Tree_pos first_child_s_2 : SHORT_DELTA; + // Tree_pos first_child_s_3 : SHORT_DELTA; + // Tree_pos first_child_s_4 : SHORT_DELTA; + // Tree_pos first_child_s_5 : SHORT_DELTA; + // Tree_pos first_child_s_6 : SHORT_DELTA; + + // Tree_pos last_child_s_0 : SHORT_DELTA; + // Tree_pos last_child_s_1 : SHORT_DELTA; + // Tree_pos last_child_s_2 : SHORT_DELTA; + // Tree_pos last_child_s_3 : SHORT_DELTA; + // Tree_pos last_child_s_4 : SHORT_DELTA; + // Tree_pos last_child_s_5 : SHORT_DELTA; + // Tree_pos last_child_s_6 : SHORT_DELTA; + + // Gives 70 bytes, 3 for each arr + // std::array first_child_s; + // std::array last_child_s; + + // Gives + // uint_32t first_child_s[CHUNK_MASK] : SHORT_DELTA; + // uint_32t last_child_s[CHUNK_MASK] : SHORT_DELTA; + + Tree_pointers() + : parent(MAX_TREE_SIZE), next_sibling(0), prev_sibling(0), + first_child_l(0), last_child_l(0) { + + // Fill first_child_s and last_child_s + // for (int i = 0; i < CHUNK_SIZE - 1; i++) { + // first_child_s[i].delta = 0; + // last_child_s[i].delta = 0; + // } + + // std::fill(first_child_s.begin(), first_child_s.end(), short_child{0}); + // std::fill(last_child_s.begin(), last_child_s.end(), short_child{0}); + + // first_child_s_0 = 0; + // first_child_s_1 = 0; + // first_child_s_2 = 0; + // first_child_s_3 = 0; + // first_child_s_4 = 0; + // first_child_s_5 = 0; + // first_child_s_6 = 0; + + // last_child_s_0 = 0; + // last_child_s_1 = 0; + // last_child_s_2 = 0; + // last_child_s_3 = 0; + // last_child_s_4 = 0; + // last_child_s_5 = 0; + // last_child_s_6 = 0; + + // for (int i = 0; i < CHUNK_MASK; i++) { + // first_child_s[i] = 0; + // last_child_s[i] = 0; + // } + } +}; + +int main() { + + Tree_pointers test; + cout << sizeof(test) << endl; + // cout << sizeof(test.first_child_s[0]) << endl; + + return 0; +} \ No newline at end of file diff --git a/hhds/tree.hpp b/hhds/tree.hpp index e2cf4d7..fdae368 100644 --- a/hhds/tree.hpp +++ b/hhds/tree.hpp @@ -11,206 +11,713 @@ #include #include #include -#include -#include +#include +#include +#include #include namespace hhds { - using Tree_index = std::int32_t; - - class Tree_Node { - public: - /* Default constructor */ - constexpr Tree_Node() noexcept - : firstChildId(NOT_INIT), lastChildId(NOT_INIT), parentId(NOT_INIT) {} - - /* Parameterized constructor */ - constexpr Tree_Node(Tree_index firstChildId, Tree_index lastChildId, Tree_index parentId) noexcept - : firstChildId(firstChildId), lastChildId(lastChildId), parentId(parentId) {} - - /* Copy constructor */ - constexpr Tree_Node(const Tree_Node &other) noexcept - : firstChildId(other.firstChildId), lastChildId(other.lastChildId), parentId(other.parentId) {} - - /* Copy assignment */ - constexpr Tree_Node &operator=(const Tree_Node &other) noexcept { - firstChildId = other.firstChildId; - lastChildId = other.lastChildId; - parentId = other.parentId; - return *this; - } - - // Operators - constexpr bool operator==(const Tree_Node &other) const { - return firstChildId == other.firstChildId - && lastChildId == other.lastChildId - && parentId == other.parentId; - } - constexpr bool operator!=(const Tree_Node &other) const { - return firstChildId != other.firstChildId - || lastChildId != other.lastChildId - || parentId != other.parentId; - } - - // Accessor methods for encapsulation - constexpr Tree_index getFirstChildId() const noexcept { return firstChildId; } - constexpr Tree_index getLastChildId() const noexcept { return lastChildId; } - constexpr Tree_index getParentId() const noexcept { return parentId; } - - constexpr void setFirstChildId(Tree_index id) noexcept { firstChildId = id; } - constexpr void setLastChildId(Tree_index id) noexcept { lastChildId = id; } - constexpr void setParentId(Tree_index id) noexcept { parentId = id; } - constexpr void invalidate() noexcept { - firstChildId = INVALID; - lastChildId = INVALID; - parentId = INVALID; - } - - // Checkers - [[nodiscard]] constexpr bool is_empty() const noexcept { return firstChildId == NOT_INIT; } - [[nodiscard]] constexpr bool is_root() const noexcept { return parentId == INVALID; } - [[nodiscard]] constexpr bool is_invalid() const noexcept { - return firstChildId == INVALID - || lastChildId == INVALID - || parentId == INVALID; - } - - // Special Indices for readability - enum class SpecialIndex : Tree_index { - NOT_INIT = -3, - IN_HASHMAP = -2, - INVALID = -1 - }; - - // : public - - protected: - // Member variables - Tree_index firstChildId; - Tree_index lastChildId; - Tree_index parentId; - - // Enum constants for ease of use - static constexpr Tree_index NOT_INIT = static_cast(SpecialIndex::NOT_INIT); - static constexpr Tree_index IN_HASHMAP = static_cast(SpecialIndex::IN_HASHMAP); - static constexpr Tree_index INVALID = static_cast(SpecialIndex::INVALID); - - // : protected - - }; // Tree_Node class - - template - class Tree { - public: - // Checks - [[nodiscard]] bool is_empty(const Tree_index &idx) const; - [[nodiscard]] bool is_first_child(const Tree_index &idx) const; - [[nodiscard]] bool is_last_child(const Tree_index &idx) const; - - // Gets - [[nodiscard]] Tree_index get_last_child(const Tree_index &par_idx) const; - [[nodiscard]] Tree_index get_first_child(const Tree_index &par_idx) const; - /* DOUBT */ [[nodiscard]] Tree_index get_tree_width(const std::int32_t level) const; - [[nodiscard]] Tree_index get_sibling_prev(const Tree_index &sib_idx) const; - - // Modifiers - void add_child(const Tree_index &par_idx, const T &data); - void delete_leaf(const Tree_index &chld_idx); - void delete_subtree(const Tree_index &chld_idx); - void append_sibling(const Tree_index &sib_idx, const T &data); - void insert_next_sibling(const Tree_index &sib_idx, const T &data); - - //: public - - private: - std::vector nodes; // The "flattened" tree - std::vector data; // The data stored in the tree - std::vector level; /* DOUBT : SHOULD WE PUT THE LEVEL value IN THE TREE_NODE? */ - std::vector count_at_level; /* DOUBT : SHOULD WE PUT THE LEVEL value IN THE TREE_NODE? */ - [[nodiscard]] bool _check_idx_exists(const Tree_index &idx) const noexcept { - return idx >= 0 && idx < nodes.size(); - } - - //: private - - }; // Tree class - - /** - * @brief Checks if the node at the given index is empty - * - * @param idx The index of the node to check - * @return true if the node is empty - */ - template - bool Tree::is_empty(const Tree_index &idx) const { - assert(_check_idx_exists(idx)); - return nodes[idx].is_empty(); - } - - /** - * @brief Gets the last child of the node at the given index - * - * @param par_idx The index of the parent node (given index) - * @return The index of the last child of the parent node - * - * @todo Add exception handling for Child in hashmap - * @todo Add that handling to all - * @todo How are we handling invalid accesses? - */ - template - Tree_index Tree::get_last_child(const Tree_index &par_idx) const { - if (!_check_idx_exists(par_idx)) { - throw std::out_of_range("Parent index out of range"); - } - return nodes[par_idx].getLastChildId(); - } - - /** - * @brief Gets the first child of the node at the given index - * - * @param par_idx The index of the parent node (given index) - * @return The index of the first child of the parent node - */ - template - Tree_index Tree::get_first_child(const Tree_index &par_idx) const { - if (!_check_idx_exists(par_idx)) { - throw std::out_of_range("Parent index out of range"); - } - return nodes[par_idx].getFirstChildId(); - } - - /** - * @brief Checks if the node at the given index is the first child of its parent - * - * @param idx The index of the node to check - * @return true if the node is the first child of its parent - */ - template - bool Tree::is_first_child(const Tree_index &idx) const { - if (!_check_idx_exists(idx)) { - throw std::out_of_range("Parent index out of range"); - } else if (nodes[idx].is_root()) { - return false; - } - - return nodes[nodes[idx].getParentId()].getFirstChildId() == idx; - } - - /** - * @brief Checks if the node at the given index is the last child of its parent - * - * @param idx The index of the node to check - * @return true if the node is the last child of its parent - */ - template - bool Tree::is_last_child(const Tree_index &idx) const { - if (!_check_idx_exists(idx)) { - throw std::out_of_range("Parent index out of range"); - } else if (nodes[idx].is_root()) { - return false; - } - - return nodes[nodes[idx].getParentId()].getLastChildId() == idx; - } - -} // HHDS namespace \ No newline at end of file +/** NOTES for future contributors: + * Realize that the total number of bits for the + * each entry of Tree_pointers is 3 + 5*CHUNK_BITS + * + 2*7*SHORT_DELTA = 3 + 5*43 + 2*7*21 = 512 bits. + * + * The number of bits for each entry of Tree_pointers is + * exactly one cache line. If at any point there is bookkeeping + * to be added, please add it to the Tree_pointers class after + * adjusting the values of CHUNK_BITS and SHORT_DELTA. + * + * Other values for reference (CHUNK_BITS, SHORT_DELTA, TOTAL_BITS) + * 40 22 -> 511 + 43 21 -> 512 + 45 20 -> 508 + 48 19 -> 509 + * + * NEVER let it exceed 512 bits. +*/ + +using Tree_pos = uint64_t; +using Short_delta = int32_t; + +static constexpr Tree_pos INVALID = 0; // This is invalid for all pointers other than parent +static constexpr Tree_pos ROOT = 0; // ROOT ID + +static constexpr short CHUNK_BITS = 43; // Number of chunks in a tree node +static constexpr short SHORT_DELTA = 21; // Amount of short delta allowed + +static constexpr short CHUNK_SHIFT = 3; // The number of bits in a chunk offset +static constexpr short CHUNK_SIZE = 1 << CHUNK_SHIFT; // Size of a chunk in bits +static constexpr short CHUNK_MASK = CHUNK_SIZE - 1; // Mask for chunk offset +static constexpr short NUM_SHORT_DEL = CHUNK_MASK; // Number of short delta children in eachc tree_ptr + +static constexpr uint64_t MAX_TREE_SIZE = 1LL << CHUNK_BITS; // Maximum number of nodes in the tree + +static constexpr Short_delta MIN_SHORT_DELTA = -(1 << SHORT_DELTA); // Minimum value for short delta +static constexpr Short_delta MAX_SHORT_DELTA = (1 << SHORT_DELTA) - 1; // Maximum value for short delta + +class __attribute__((packed, aligned(64))) Tree_pointers { +private: + // We only store the exact ID of parent + Tree_pos parent : CHUNK_BITS + CHUNK_SHIFT; + Tree_pos next_sibling : CHUNK_BITS; + Tree_pos prev_sibling : CHUNK_BITS; + + // Long child pointers + Tree_pos first_child_l : CHUNK_BITS; + Tree_pos last_child_l : CHUNK_BITS; + + // Short (delta) child pointers + Short_delta first_child_s_0 : SHORT_DELTA; + Short_delta first_child_s_1 : SHORT_DELTA; + Short_delta first_child_s_2 : SHORT_DELTA; + Short_delta first_child_s_3 : SHORT_DELTA; + Short_delta first_child_s_4 : SHORT_DELTA; + Short_delta first_child_s_5 : SHORT_DELTA; + Short_delta first_child_s_6 : SHORT_DELTA; + + Short_delta last_child_s_0 : SHORT_DELTA; + Short_delta last_child_s_1 : SHORT_DELTA; + Short_delta last_child_s_2 : SHORT_DELTA; + Short_delta last_child_s_3 : SHORT_DELTA; + Short_delta last_child_s_4 : SHORT_DELTA; + Short_delta last_child_s_5 : SHORT_DELTA; + Short_delta last_child_s_6 : SHORT_DELTA; + + // Helper functions to get and set first child pointers by index + Short_delta _get_first_child_s(short index) const { + switch (index) { + case 0: return first_child_s_0; + case 1: return first_child_s_1; + case 2: return first_child_s_2; + case 3: return first_child_s_3; + case 4: return first_child_s_4; + case 5: return first_child_s_5; + case 6: return first_child_s_6; + + default: + throw std::out_of_range("Invalid index for first_child_s"); + } + } + + void _set_first_child_s(short index, Short_delta value) { + switch (index) { + case 0: first_child_s_0 = value; break; + case 1: first_child_s_1 = value; break; + case 2: first_child_s_2 = value; break; + case 3: first_child_s_3 = value; break; + case 4: first_child_s_4 = value; break; + case 5: first_child_s_5 = value; break; + case 6: first_child_s_6 = value; break; + + default: + throw std::out_of_range("Invalid index for first_child_s"); + } + } + + // Helper functions to get and set last child pointers by index + Short_delta _get_last_child_s(short index) const { + switch (index) { + case 0: return last_child_s_0; + case 1: return last_child_s_1; + case 2: return last_child_s_2; + case 3: return last_child_s_3; + case 4: return last_child_s_4; + case 5: return last_child_s_5; + case 6: return last_child_s_6; + + default: + throw std::out_of_range("Invalid index for last_child_s"); + } + } + + void _set_last_child_s(short index, Short_delta value) { + switch (index) { + case 0: last_child_s_0 = value; break; + case 1: last_child_s_1 = value; break; + case 2: last_child_s_2 = value; break; + case 3: last_child_s_3 = value; break; + case 4: last_child_s_4 = value; break; + case 5: last_child_s_5 = value; break; + case 6: last_child_s_6 = value; break; + + default: + throw std::out_of_range("Invalid index for last_child_s"); + } + } + +// :private + +public: + /* DEFAULT CONSTRUCTOR */ + // Parent can be = 0 for someone, but it can never be MAX_TREE_SIZE + // That is why the best way to invalidate is to set it to MAX_TREE_SIZE + // for every other entry INVALID is the best choice + Tree_pointers() + : parent(MAX_TREE_SIZE), next_sibling(INVALID), prev_sibling(INVALID), + first_child_l(INVALID), last_child_l(INVALID) { + for (short i = 0; i < NUM_SHORT_DEL; i++) { + _set_first_child_s(i, INVALID); + _set_last_child_s(i, INVALID); + } + } + + /* PARAM CONSTRUCTOR */ + Tree_pointers(Tree_pos p) + : parent(p), + next_sibling(INVALID), prev_sibling(INVALID), + first_child_l(INVALID), last_child_l(INVALID) { + for (short i = 0; i < NUM_SHORT_DEL; i++) { + _set_first_child_s(i, INVALID); + _set_last_child_s(i, INVALID); + } + } + + // Getters + Tree_pos get_parent() const { return parent; } + Tree_pos get_next_sibling() const { return next_sibling; } + Tree_pos get_prev_sibling() const { return prev_sibling; } + Tree_pos get_first_child_l() const { return first_child_l; } + Tree_pos get_last_child_l() const { return last_child_l; } + + // Getters for short child pointers + Short_delta get_first_child_s_at(short index) const { + return _get_first_child_s(index); + } + Short_delta get_last_child_s_at(short index) const { + return _get_last_child_s(index); + } + + // Setters + void set_parent(Tree_pos p) { parent = p; } + void set_next_sibling(Tree_pos ns) { next_sibling = ns; } + void set_prev_sibling(Tree_pos ps) { prev_sibling = ps; } + void set_first_child_l(Tree_pos fcl) { first_child_l = fcl; } + void set_last_child_l(Tree_pos lcl) { last_child_l = lcl; } + + // Setters for short child pointers + void set_first_child_s_at(short index, Short_delta fcs) { + _set_first_child_s(index, fcs); + } + void set_last_child_s_at(short index, Short_delta lcs) { + _set_last_child_s(index, lcs); + } + + // Operators + constexpr bool operator==(const Tree_pointers& other) const { + for (short i = 0; i < 7; i++) { + if (_get_first_child_s(i) != other._get_first_child_s(i) || + _get_last_child_s(i) != other._get_last_child_s(i)) { + return false; + } + } + return parent == other.parent && next_sibling == other.next_sibling && + prev_sibling == other.prev_sibling && first_child_l == other.first_child_l && + last_child_l == other.last_child_l; + } + + constexpr bool operator!=(const Tree_pointers& other) const { return !(*this == other); } + void invalidate() { parent = MAX_TREE_SIZE; } + + // Checkers + [[nodiscard]] constexpr bool is_invalid() const { return parent == MAX_TREE_SIZE; } + [[nodiscard]] constexpr bool is_valid() const { return parent != MAX_TREE_SIZE; } +// :public + +}; // Tree_pointers class + +template +class tree { +private: + /* The tree pointers and data stored separately */ + std::vector pointers_stack; + std::vector data_stack; + + /* Special functions for sanity */ + [[nodiscard]] bool _check_idx_exists(const Tree_pos &idx) const noexcept { + return idx >= 0 && idx < static_cast(pointers_stack.size()); + } + [[nodiscard]] bool _contains_data(const Tree_pos &idx) const noexcept { + /* CHANGE THE SECOND CONDITION + CAN USE STD::OPTIONAL WRAPPING AROUND THE + TEMPLATE OF X */ + return (idx < data_stack.size() && data_stack[idx]); + } + + /* Function to add an entry to the pointers and data stack (typically for add/append)*/ + [[nodiscard]] Tree_pos _create_space(const Tree_pos &parent_index, const X& data) { + if (pointers_stack.size() >= MAX_TREE_SIZE) { + throw std::out_of_range("Tree size exceeded"); + } else if (!_check_idx_exists(parent_index)) { + throw std::out_of_range("Parent index out of range"); + } + + // Make space for CHUNK_SIZE number of entries at the end + data_stack.emplace_back(data); + for (int i = 0; i < CHUNK_OFFSET; i++) { + data_stack.emplace_back(); + } + + // Add the single pointer node for all CHUNK_SIZE entries + pointers_stack.emplace_back(parent_index); + + return (data_stack.size() - CHUNK_SIZE) >> CHUNK_SHIFT; + } + + /* Function to insert a new chunk in between (typically for handling add/append corner cases)*/ + [[nodiscard]] Tree_pos _insert_chunk_after(const Tree_pos curr) { + if (pointers_stack.size() >= MAX_TREE_SIZE) { + throw std::out_of_range("Tree size exceeded"); + } else if (!_check_idx_exists(curr)) { + throw std::out_of_range("Current index out of range"); + } + + // Allot new chunk at the end + const auto new_chunk_id = _create_space(pointers_stack[curr].get_parent(), X()); + + // Update bookkeeping -> This is basically inserting inside of a doubly linked list + pointers_stack[new_chunk_id].set_prev_sibling(curr); + pointers_stack[new_chunk_id].set_next_sibling(pointers_stack[curr].get_next_sibling()); + pointers_stack[curr].set_next_sibling(new_chunk_id); + + if (pointers_stack[new_chunk_id].get_next_sibling() != INVALID) { + pointers_stack[pointers_stack[new_chunk_id].get_next_sibling()].set_prev_sibling(new_chunk_id); + } + + return new_chunk_id; + } + + /* Helper function to check if we can fit something in the short delta*/ + [[nodiscard]] bool _fits_in_short_del(const Tree_pos parent_id, const Tree_pos child_id) { + const auto delta = child_id - parent_id; + return abs(delta) <= MAX_SHORT_DELTA; + } + + /* Helper function to update the parent pointer of all sibling chunks*/ + void _update_parent_pointer(const Tree_pos first_child, const Tree_pos new_parent_id) { + auto curr_chunk_id = (first_child >> CHUNK_SHIFT); + + while (curr_chunk_id != INVALID) { + pointers_stack[curr_chunk_id].set_parent(new_parent_id); + curr_chunk_id = pointers_stack[curr_chunk_id].get_next_sibling(); + } + } + + /* Helper function to break the chunk starting at a a given offset inside it*/ + [[nodiscard]] Tree_pos _break_chunk_from(const Tree_pos abs_id) { + const auto old_chunk_id = (abs_id >> CHUNK_SHIFT); + const auto old_chunk_offset = (abs_id & CHUNK_MASK); + + // Insert a blank chunk after the current chunk + bool requires_new_chunk = true; + bool retval_set = false; + short new_chunk_offset = 0; + Tree_pos new_chunk_id, retval; /* To store the chunk that is being populated */ + + for (short offset = old_chunk_offset; offset < NUM_SHORT_DEL; offset++) { + const auto curr_id = (old_chunk_id << CHUNK_SHIFT) + offset; + + // Get first and last child abs id + const auto fc = get_first_child(curr_id); + const auto lc = get_last_child(curr_id); + + if (!requires_new_chunk) { + // Try fitting first and last child in the short delta + if (_fits_in_short_del(curr_id, fc) and _fits_in_short_del(curr_id, lc)) { + pointers_stack[old_chunk_id].set_first_child_s_at(new_chunk_offset, fc - curr_id); + pointers_stack[old_chunk_id].set_last_child_s_at(new_chunk_offset, lc - curr_id); + new_chunk_offset++; + + // Copy the data to the new chunk + data_stack[(new_chunk_id << CHUNK_SHIFT) + new_chunk_offset] = data_stack[curr_id]; + + // Update the parent pointer of the children + _update_parent_pointer(fc, (new_chunk_id << CHUNK_SHIFT) + new_chunk_offset); + + continue; + } + + requires_new_chunk = true; + } + + // Make new chunk since required + new_chunk_id = _insert_chunk_after(old_chunk_id); + requires_new_chunk = false; + + // Copy the data to the new chunk + data_stack[new_chunk_id << CHUNK_SHIFT] = data_stack[curr_id]; + + // Set long pointers + pointers_stack[new_chunk_id].set_first_child_l(fc >> CHUNK_SHIFT); + pointers_stack[new_chunk_id].set_last_child_l(lc >> CHUNK_SHIFT); + + // Update all the children with the new parent id + _update_parent_pointer(fc, new_chunk_id); + new_chunk_offset = 1; + + if (!retval_set) { + retval = new_chunk_id << CHUNK_SHIFT; + retval_set = true; + } + } + + // Clear old chunk if all children are moved + for (short offset = old_chunk_offset; offset < NUM_SHORT_DEL; offset++) { + data_stack[(old_chunk_id << CHUNK_SHIFT) + offset] = X(); + } + for (short offset = old_chunk_offset; offset < NUM_SHORT_DEL; offset++) { + pointers_stack[old_chunk_id].set_first_child_s_at(offset, INVALID); + pointers_stack[old_chunk_id].set_last_child_s_at(offset, INVALID); + } + + return retval; + } +// :private + +public: + /** + * Query based API (no updates) + */ + Tree_pos get_last_child(const Tree_pos& parent_index); + Tree_pos get_first_child(const Tree_pos& parent_index); + bool is_last_child(const Tree_pos& self_index); + bool is_first_child(const Tree_pos& self_index); + Tree_pos get_sibling_next(const Tree_pos& sibling_id); + Tree_pos get_sibling_prev(const Tree_pos& sibling_id); + int get_tree_width(const int& level); + + + /** + * Update based API (Adds and Deletes from the tree) + */ + Tree_pos append_sibling(const Tree_pos& sibling_id, const X& data); + Tree_pos add_child(const Tree_pos& parent_index, const X& data); +// :public + +}; // tree class + +// ---------------------------------- TEMPLATE IMPLEMENTATION ---------------------------------- // +/** + * @brief Get absolute ID of the last child of a node. + * + * @param parent_index The absolute ID of the parent node. + * @return Tree_pos The absolute ID of the last child, INVALID if none + * + * @throws std::out_of_range If the parent index is out of range +*/ +template +Tree_pos tree::get_last_child(const Tree_pos& parent_index) { + if (!_check_idx_exists(parent_index)) { + throw std::out_of_range("Parent index out of range"); + } + + const auto chunk_id = (parent_index >> CHUNK_SHIFT); + const auto chunk_offset = (parent_index & CHUNK_MASK); + const auto last_child_s_i = pointers_stack[chunk_id].get_last_child_s_at(chunk_offset); + + Tree_pos child_chunk_id = INVALID; + if (chunk_offset and (last_child_s_i != INVALID)) { + // If the short delta contains a value, go to this nearby chunk + child_chunk_id = chunk_id + last_child_s_i; + } else { + // The first entry will always have the chunk id of the child + child_chunk_id = pointers_stack[chunk_id].get_last_child_l(); + } + + // Iterate in reverse to find the last occupied in child chunk + if (child_chunk_id != INVALID) { + for (short offset = NUM_SHORT_DEL - 1; offset >= 0; offset--) { + if (_contains_data((child_chunk_id << CHUNK_SHIFT) + offset)) { + return static_cast((child_chunk_id << CHUNK_SHIFT) + offset); + } + } + } + + return static_cast(INVALID); +} + +/** + * @brief Get absolute ID of the first child of a node. + * + * @param parent_index The absolute ID of the parent node. + * @return Tree_pos The absolute ID of the first child, INVALID if none + * + * @throws std::out_of_range If the parent index is out of range +*/ +template +Tree_pos tree::get_first_child(const Tree_pos& parent_index) { + if (!_check_idx_exists(parent_index)) { + throw std::out_of_range("Parent index out of range"); + } + + const auto chunk_id = (parent_index >> CHUNK_SHIFT); + const auto chunk_offset = (parent_index & CHUNK_MASK); + const auto last_child_s_i = pointers_stack[chunk_id].get_last_child_s_at(chunk_offset); + + Tree_pos child_chunk_id = INVALID; + if (chunk_offset and (last_child_s_i != INVALID)) { + // If the short delta contains a value, go to this nearby chunk + child_chunk_id = chunk_id + last_child_s_i; + } else { + // The first entry will always have the chunk id of the child + child_chunk_id = pointers_stack[chunk_id].get_last_child_l(); + } + + // Iterate in reverse to find the last occupied in child chunk + if (child_chunk_id != INVALID) { + for (short offset = NUM_SHORT_DEL - 1; offset >= 0; offset--) { + if (_contains_data((child_chunk_id << CHUNK_SHIFT) + offset)) { + return static_cast((child_chunk_id << CHUNK_SHIFT) + offset); + } + } + } + + return static_cast(INVALID); +} + +/** + * @brief Check if a node is the last child of its parent. + * + * @param self_index The absolute ID of the node. + * @return true If the node is the last child of its parent. + * + * @throws std::out_of_range If the query index is out of range +*/ +template +bool tree::is_last_child(const Tree_pos& self_index) { + if (!_check_idx_exists(self_index)) { + throw std::out_of_range("Index out of range"); + } + + const auto self_chunk_id = (self_index >> CHUNK_SHIFT); + const auto self_chunk_offset = (self_index & CHUNK_MASK); + + // If this chunk has a next_sibling pointer, certainly not the last child + if (pointers_stack[self_chunk_id].get_next_sibling() != INVALID) { + return false; + } + + // Now, to be the last child, all entries after this should be invalid + for (short offset = self_chunk_offset; offset < NUM_SHORT_DEL; offset++) { + const auto last_child_s_i = pointers_stack[self_chunk_id].get_last_child_s_at(offset); + if (_contains_data((self_chunk_id << CHUNK_SHIFT) + offset)) { + return false; + } + } + + return true; +} + +/** + * @brief Check if a node is the first child of its parent. + * + * @param self_index The absolute ID of the node. + * @return true If the node is the first child of its parent. + * + * @throws std::out_of_range If the query index is out of range +*/ +template +bool tree::is_first_child(const Tree_pos& self_index) { + if (!_check_idx_exists(self_index)) { + throw std::out_of_range("Index out of range"); + } + + const auto self_chunk_id = (self_index >> CHUNK_SHIFT); + const auto self_chunk_offset = (self_index & CHUNK_MASK); + + // If this chunk has a prev_sibling pointer, certainly not the last child + if (pointers_stack[self_chunk_id].get_prev_sibling() != INVALID) { + return false; + } + + // Now, to be the first child, it must have no offset + if (self_chunk_offset) { + return false; + } + + return true; +} + +/** + * @brief Get the next sibling of a node. + * + * @param sibling_id The absolute ID of the sibling node. + * @return Tree_pos The absolute ID of the next sibling, INVALID if none + * + * @throws std::out_of_range If the sibling index is out of range +*/ +template +Tree_pos tree::get_sibling_next(const Tree_pos& sibling_id) { + if (!_check_idx_exists(sibling_id)) { + throw std::out_of_range("Sibling index out of range"); + } + + // If this is the last child, no next sibling + if (is_last_child(sibling_id)) { + return INVALID; + } + + // Check if the next sibling is within the same chunk, at idx + 1 + const auto curr_chunk_id = (sibling_id >> CHUNK_SHIFT); + const auto curr_chunk_offset = (sibling_id & CHUNK_MASK); + if (curr_chunk_offset < CHUNK_MASK and _contains_data(curr_chunk_id + curr_chunk_offset + 1)) { + return static_cast(curr_chunk_id + curr_chunk_offset + 1); + } + + // Just jump to the next sibling chunk, or returns invalid + return pointers_stack[sibling_id].get_next_sibling(); // default is INVALID +} + +/** + * @brief Get the prev sibling of a node. + * + * @param sibling_id The absolute ID of the sibling node. + * @return Tree_pos The absolute ID of the prev sibling, INVALID if none + * + * @throws std::out_of_range If the sibling index is out of range +*/ +template +Tree_pos tree::get_sibling_prev(const Tree_pos& sibling_id) { + if (!_check_idx_exists(sibling_id)) { + throw std::out_of_range("Sibling index out of range"); + } + + // If this is the first child, no prev sibling + if (is_first_child(sibling_id)) { + return INVALID; + } + + // Check if the prev sibling is within the same chunk, at idx + 1 + const auto curr_chunk_id = (sibling_id >> CHUNK_SHIFT); + const auto curr_chunk_offset = (sibling_id & CHUNK_MASK); + if (curr_chunk_offset > 0 and _contains_data(curr_chunk_id + curr_chunk_offset - 1)) { + return static_cast(curr_chunk_id + curr_chunk_offset - 1); + } + + // Just jump to the next sibling chunk, or returns invalid + const auto prev_sibling = pointers_stack[sibling_id].get_prev_sibling(); + + // Find the last occupied in the prev sibling chunk + if (prev_sibling != INVALID) { + for (short offset = NUM_SHORT_DEL - 1; offset >= 0; offset--) { + if (_contains_data(prev_sibling + offset)) { + return static_cast(prev_sibling + offset); + } + } + } + + return INVALID; +} + +template +int tree::get_tree_width(const int& level) { + // Placeholder: Implement the actual width calculation using BFS or additional bookkeeping + return 0; +} + +/** + * @brief Append a sibling to a node. + * + * @param sibling_id The absolute ID of the sibling node. + * @param data The data to be stored in the new sibling. + * + * @return Tree_pos The absolute ID of the new sibling. + * @throws std::out_of_range If the sibling index is out of range + */ +template +Tree_pos tree::append_sibling(const Tree_pos& sibling_id, const X& data) { + if (!_check_idx_exists(sibling_id)) { + throw std::out_of_range("Sibling index out of range"); + } + + const auto sibling_chunk_id = (sibling_id >> CHUNK_SHIFT); + const auto sibling_parent_id = pointers_stack[sibling_chunk_id].get_parent(); + const auto sib_parent_chunk_id = (sibling_parent_id >> CHUNK_SHIFT); + const auto last_sib_chunk_id = get_last_child(sibling_parent_id); + + // Can fit the sibling in the same chunk + for (short offset = 0; offset < NUM_SHORT_DEL; offset++) { + if (!_contains_data((last_sib_chunk_id << CHUNK_SHIFT) + offset)) { + // Put the data here and update bookkeeping + data_stack[(last_sib_chunk_id << CHUNK_SHIFT) + offset] = data; + return static_cast((last_sib_chunk_id << CHUNK_SHIFT) + offset); + } + } + + // Create a new chunk for the sibling and fill data there + const auto new_sib_chunk_id = _create_space(sibling_parent_id, data); + pointers_stack[new_sib_chunk_id].set_prev_sibling(last_sib_chunk_id); + pointers_stack[last_sib_chunk_id].set_next_sibling(new_sib_chunk_id); + + // Update the last child pointer of the parent + if ((sibling_parent_id & CHUNK_MASK) == 0) { + // It is the first in the cluster, so we can fit the chunk id directly + pointers_stack[sib_parent_chunk_id].set_last_child_l(new_sib_chunk_id); + } else { + // Try to fit the delta in the short delta pointers + if (_fits_in_short_del(sibling_parent_id, new_sib_chunk_id)) { + pointers_stack[sib_parent_chunk_id].set_last_child_s_at((sibling_parent_id & CHUNK_MASK) - 1, + static_cast(delta)); + } else { + // Break the parent chunk from the parent id + const auto new_parent_chunk_id = _break_chunk_from(sibling_parent_id); + + // Set the last child pointer of the parent to the new parent chunk + pointers_stack[new_parent_chunk_id].set_last_child_l(new_sib_chunk_id); + } + } + + return new_sib_chunk_id << CHUNK_SHIFT; +} + +/** + * @brief Add a chlid to a node at the end of all it's children. + * + * @param parent_index The absolute ID of the parent node. + * @param data The data to be stored in the new sibling. + * + * @return Tree_pos The absolute ID of the new child. + * @throws std::out_of_range If the parent index is out of range + */ +template +Tree_pos tree::add_child(const Tree_pos& parent_index, const X& data) { + if (!_check_idx_exists(parent_index)) { + throw std::out_of_range("Parent index out of range"); + } + + const auto last_child_id = get_last_child(parent_index); + + // This is not the first child being added + if (last_child_id != INVALID) { + return append_sibling(last_child_id, data); + } + + const auto child_chunk_id = _create_space(parent_index, data); + const auto parent_chunk_id = (parent_index >> CHUNK_SHIFT); + const auto parent_chunk_offset = (parent_index & CHUNK_MASK); + + /* Update the parent's first and last child pointers to this new child */ + if (parent_chunk_offset == 0) { + // The offset is 0, we can fit the chunk id of the child directly + pointers_stack[parent_chunk_id].set_first_child_l(child_chunk_id); + pointers_stack[parent_chunk_id].set_last_child_l(child_chunk_id); + } else { + // Try to fit the delta in the short delta pointers + const auto delta = child_chunk_id - parent_chunk_id; + if (_fits_in_short_del(parent_index, child_chunk_id)) { + pointers_stack[parent_chunk_id].set_first_child_s_at(parent_chunk_offset - 1, + static_cast(delta)); + pointers_stack[parent_chunk_id].set_last_child_s_at(parent_chunk_offset - 1, + static_cast(delta)); + } else { + // Break the parent chunk from the parent id + const auto new_parent_chunk_id = _break_chunk_from(parent_index); + + // Set the first and last child pointer of the parent to the new parent chunk + pointers_stack[new_parent_chunk_id].set_first_child_l(child_chunk_id); + pointers_stack[new_parent_chunk_id].set_last_child_l(child_chunk_id); + } + } + + return child_chunk_id << CHUNK_SHIFT; +} +} // hhds namespace \ No newline at end of file