From 24037023eb380e9d85563b98c5c325dfdccb35d7 Mon Sep 17 00:00:00 2001 From: Ujjwal Shekhar Date: Mon, 27 May 2024 17:27:16 +0530 Subject: [PATCH 01/15] Debugging `add_child()`. Handwriting tests for now. - Minor changes to the tree class. - Currently debugging the `add_child()` brute method. - Major target currently, is to fix the update to the bookkeeping. - To add, private `_add_child_brute()` and `_add_child_hashmap()` calls. Which will be called by the public `add_child()`. - To add, random tree generator to test flp. --- hhds/tree.cpp | 49 ++++++++++++ hhds/tree.hpp | 206 ++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 242 insertions(+), 13 deletions(-) create mode 100644 hhds/tree.cpp diff --git a/hhds/tree.cpp b/hhds/tree.cpp new file mode 100644 index 0000000..e905719 --- /dev/null +++ b/hhds/tree.cpp @@ -0,0 +1,49 @@ +// test_tree.cpp +#include "tree.hpp" +#include + +using namespace hhds; + +// Function to print the status of a node +template +void print_node_status(const Tree& tree, Tree_index idx) { + std::cout << "Node " << idx << " status:\n"; + std::cout << " is_empty: " << tree.is_empty(idx) << "\n"; + std::cout << " is_first_child: " << tree.is_first_child(idx) << "\n"; + std::cout << " is_last_child: " << tree.is_last_child(idx) << "\n"; + std::cout << " First child: " << tree.get_first_child(idx) << "\n"; + std::cout << " Last child: " << tree.get_last_child(idx) << "\n"; + std::cout << " Previous sibling: " << tree.get_sibling_prev(idx) << "\n"; + std::cout << " Next sibling: " << tree.get_sibling_next(idx) << "\n"; + std::cout << " Parent: " << tree.get_parent(idx) << "\n"; + std::cout << std::endl; +} + +int main() { + Tree tree; + + // Add root node + // tree.add_child(static_cast(Tree_Node::SpecialIndex::INVALID), "root"); + tree.add_root("root"); + Tree_index root_idx = 0; + + // Add child nodes + std::cout << "Adding child node 1 \n"; + tree.add_child(root_idx, "child1"); + std::cout << "Adding child node 2 \n"; + tree.add_child(root_idx, "child2"); + + // Add grandchild nodes + Tree_index child1_idx = tree.get_first_child(root_idx); + Tree_index child2_idx = tree.get_sibling_next(child1_idx); + std::cout << "Adding grandchild node 1 \n"; + tree.add_child(child1_idx, "grandchild1"); + std::cout << "Adding grandchild node 2 \n"; + tree.add_child(child1_idx, "grandchild2"); + + std::cout << "Adding greatgrandchild node 1 \n"; + child1_idx = tree.get_last_child(child1_idx); + tree.add_child(child2_idx, "greatgrandchild1"); + + return 0; +} diff --git a/hhds/tree.hpp b/hhds/tree.hpp index e2cf4d7..c3e5281 100644 --- a/hhds/tree.hpp +++ b/hhds/tree.hpp @@ -22,7 +22,7 @@ namespace hhds { public: /* Default constructor */ constexpr Tree_Node() noexcept - : firstChildId(NOT_INIT), lastChildId(NOT_INIT), parentId(NOT_INIT) {} + : firstChildId(INVALID), lastChildId(INVALID), parentId(INVALID) {} /* Parameterized constructor */ constexpr Tree_Node(Tree_index firstChildId, Tree_index lastChildId, Tree_index parentId) noexcept @@ -67,17 +67,18 @@ namespace hhds { } // Checkers - [[nodiscard]] constexpr bool is_empty() const noexcept { return firstChildId == NOT_INIT; } + [[nodiscard]] constexpr bool is_empty() const noexcept { return firstChildId == INVALID; } + [[nodiscard]] constexpr bool has_no_children() const noexcept { return firstChildId == INVALID; } + [[nodiscard]] constexpr bool is_uninitialized() const noexcept { return firstChildId == INVALID;} [[nodiscard]] constexpr bool is_root() const noexcept { return parentId == INVALID; } [[nodiscard]] constexpr bool is_invalid() const noexcept { - return firstChildId == INVALID - || lastChildId == INVALID - || parentId == INVALID; + return (firstChildId == INVALID + || lastChildId == INVALID) + && (parentId != INVALID); } // Special Indices for readability enum class SpecialIndex : Tree_index { - NOT_INIT = -3, IN_HASHMAP = -2, INVALID = -1 }; @@ -91,7 +92,6 @@ namespace hhds { 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); @@ -112,9 +112,12 @@ namespace hhds { [[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; + [[nodiscard]] Tree_index get_sibling_next(const Tree_index &sib_idx) const; + [[nodiscard]] Tree_index get_parent(const Tree_index &child_idx) const; // Modifiers void add_child(const Tree_index &par_idx, const T &data); + void add_root(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); @@ -123,13 +126,20 @@ namespace hhds { //: 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? */ + std::vector nodes; // The "flattened" tree + std::vector data_points; // 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(); + return idx >= 0 && idx < static_cast(nodes.size()); } + [[nodiscard]] bool _cannot_be_accessed(const Tree_index &idx) const noexcept { + return idx == static_cast(Tree_Node::SpecialIndex::INVALID) + || idx == static_cast(Tree_Node::SpecialIndex::INVALID); + } + + // DEBUG + void print_nodes(); //: private @@ -143,7 +153,9 @@ namespace hhds { */ template bool Tree::is_empty(const Tree_index &idx) const { - assert(_check_idx_exists(idx)); + if (!_check_idx_exists(idx)) { + throw std::out_of_range("Parent index out of range"); + } return nodes[idx].is_empty(); } @@ -176,6 +188,7 @@ namespace hhds { if (!_check_idx_exists(par_idx)) { throw std::out_of_range("Parent index out of range"); } + return nodes[par_idx].getFirstChildId(); } @@ -184,6 +197,7 @@ namespace hhds { * * @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 { @@ -191,6 +205,8 @@ namespace hhds { throw std::out_of_range("Parent index out of range"); } else if (nodes[idx].is_root()) { return false; + } else if (_cannot_be_accessed(nodes[idx].getParentId())) { + return false; } return nodes[nodes[idx].getParentId()].getFirstChildId() == idx; @@ -201,6 +217,8 @@ namespace hhds { * * @param idx The index of the node to check * @return true if the node is the last child of its parent + * + * @todo Add exception handling for Child in hashmap */ template bool Tree::is_last_child(const Tree_index &idx) const { @@ -208,9 +226,171 @@ namespace hhds { throw std::out_of_range("Parent index out of range"); } else if (nodes[idx].is_root()) { return false; + } else if (_cannot_be_accessed(nodes[idx].getParentId())) { + return false; } return nodes[nodes[idx].getParentId()].getLastChildId() == idx; } + /** + * @brief Gets the previous sibling of the node at the given index + * + * @param sib_idx The index of the sibling node (given index) + * @return The index of the previous sibling of the sibling node + * + * @todo Add exception handling for Child in hashmap + */ + template + Tree_index Tree::get_sibling_prev(const Tree_index &sib_idx) const { + if (!_check_idx_exists(sib_idx)) { + throw std::out_of_range("Sibling index out of range"); + } else if (nodes[sib_idx].is_root() || is_first_child(sib_idx)) { + return static_cast(Tree_Node::SpecialIndex::INVALID); + } + + return sib_idx - 1; // The flattening policy assigns consecutive id to sibs. + } + + /** + * @brief Gets the next sibling of the node at the given index + * + * @param sib_idx The index of the sibling node (given index) + * @return The index of the next sibling of the sibling node + * + * @todo Add exception handling for Child in hashmap + */ + template + Tree_index Tree::get_sibling_next(const Tree_index &sib_idx) const { + if (!_check_idx_exists(sib_idx)) { + throw std::out_of_range("Sibling index out of range"); + } else if (nodes[sib_idx].is_root() || is_last_child(sib_idx)) { + return static_cast(Tree_Node::SpecialIndex::INVALID); + } + + return sib_idx + 1; // The flattening policy assigns consecutive id to sibs. + } + + /** + * @brief Gets the parent of the node at the given index + * + * @param sib_idx The index of the child node (given index) + * @return The index of the parent of the child node + * + * @todo Add exception handling for Child in hashmap + */ + template + Tree_index Tree::get_parent(const Tree_index &child_idx) const { + if (!_check_idx_exists(child_idx)) { + throw std::out_of_range("Child index out of range"); + } + + return nodes[child_idx].getParentId(); + } + + /** + * @brief Adds the first node, that is, the root of the tree + * + * @param data The data to be stored in the root node + */ + template + void Tree::add_root(const T &data) { + if (!nodes.empty()) { + throw std::logic_error("Root already exists"); + } + nodes.emplace_back(); + data_points.emplace_back(data); + } + + + /** + * @brief Adds a child to the node at the given index + * + * @param par_idx The index of the parent node (given index) + * @param data The data to be stored in the child node + * + * @todo Add exception handling for Child in hashmap + * @todo Add case II + */ + template + void Tree::add_child(const Tree_index &par_idx, const T &data) { + if (nodes.empty()) { + throw std::logic_error("Tree is empty. Add a root node first."); + } else if (!_check_idx_exists(par_idx)) { + throw std::out_of_range("Parent index out of range"); + } + + std::cout << "Adding child to parent : " << par_idx << "\n"; + + /* PLEASE REMOVE THIS COMMENT ONCE THE BRUTE ADD CHILD HAS BEEN FINALIZED */ + // Case I: The add_child has been called close to the end of this array + // I will have to add the child right AFTER the par_idx.lastchild + // If the parent has no children yet, then the id will be right + // AFTER the par_idx. + // 0) last_no_upd = (par_idx.has_no_children()) ? par_idx : par_idx.lastchild + // 1) Shift everything at id > to_add to the right by 1 + // 2) Add +1 to every (fc, lc, par) id > to_add. NOTE: + // if any par <= to_add, then don't do id++. + // 3) Add the new node at to_add + // 4) Update the par_idx.lastchild to to_add + const Tree_index last_no_upd = (nodes[par_idx].has_no_children()) ? + par_idx : nodes[par_idx].getLastChildId(); + + std::cout << "Last no update : " << last_no_upd << "\n"; + + // Make space for another node at the end + Tree::print_nodes(); + + nodes.emplace_back(); + data_points.emplace_back(data); + Tree::print_nodes(); + + // Shift everything at id > last_no_upd to the right by 1 + // and add +1 wherever valid + for (Tree_index i = nodes.size() - 1; i > last_no_upd; --i) { + std::cout << "Loop starts running : " << i << "\n"; + nodes[i] = nodes[i - 1]; + data_points[i] = data_points[i - 1]; + + // Add +1 to every (fc, lc, par) id > last_no_upd + if (nodes[i].getFirstChildId() > last_no_upd) + nodes[i].setFirstChildId(nodes[i].getFirstChildId() + 1); + + if (nodes[i].getLastChildId() > last_no_upd) + nodes[i].setLastChildId(nodes[i].getLastChildId() + 1); + + if (nodes[i].getParentId() > last_no_upd) + nodes[i].setParentId(nodes[i].getParentId() + 1); + } + + // Add the new node at last_no_upd + 1 + nodes[last_no_upd + 1] = Tree_Node(static_cast(Tree_Node::SpecialIndex::INVALID), + static_cast(Tree_Node::SpecialIndex::INVALID), + par_idx); + data_points[last_no_upd + 1] = data; + + // Update bookkeeping at the parent index + if (nodes[par_idx].has_no_children()) { + nodes[par_idx].setFirstChildId(last_no_upd + 1); + } + nodes[par_idx].setLastChildId(last_no_upd + 1); + + Tree::print_nodes(); + } + + /** + * @brief Print the list nodes in the tree, for debugging purposes + * + */ + template + void Tree::print_nodes() { + std::cout << "Nodes in the tree:\n"; + for (std::size_t i = 0; i < nodes.size(); ++i) { + std::cout << "Node (" << i << ") : "; + std::cout << " [" << nodes[i].getFirstChildId() << ", "; + std::cout << nodes[i].getLastChildId() << ", "; + std::cout << nodes[i].getParentId() << "] -> "; + std::cout << data_points[i] << "\n"; + } + } } // HHDS namespace \ No newline at end of file From 08dcb321b5e4bf53d28d5db03be348d195f0d444 Mon Sep 17 00:00:00 2001 From: Ujjwal Shekhar Date: Mon, 27 May 2024 17:46:20 +0530 Subject: [PATCH 02/15] minor fix --- hhds/tree_pseudo.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hhds/tree_pseudo.hpp b/hhds/tree_pseudo.hpp index e78a20b..3ac9a5e 100644 --- a/hhds/tree_pseudo.hpp +++ b/hhds/tree_pseudo.hpp @@ -5,7 +5,7 @@ #include #include -namespace hhsd { +namespace hhds { using Tree_index = std::int32_t; From 5ab8c62341c77b83ff04943964385c18a57bc5e5 Mon Sep 17 00:00:00 2001 From: Ujjwal Shekhar Date: Mon, 27 May 2024 17:49:19 +0530 Subject: [PATCH 03/15] Save local changes before pull --- hhds/tree.hpp | 396 -------------------------------------------------- 1 file changed, 396 deletions(-) diff --git a/hhds/tree.hpp b/hhds/tree.hpp index c3e5281..e69de29 100644 --- a/hhds/tree.hpp +++ b/hhds/tree.hpp @@ -1,396 +0,0 @@ -// This file is distributed under the BSD 3-Clause License. See LICENSE for details. -#pragma once - -// tree.hpp -#include -#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(INVALID), lastChildId(INVALID), parentId(INVALID) {} - - /* 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 == INVALID; } - [[nodiscard]] constexpr bool has_no_children() const noexcept { return firstChildId == INVALID; } - [[nodiscard]] constexpr bool is_uninitialized() const noexcept { return firstChildId == INVALID;} - [[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 { - 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 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; - [[nodiscard]] Tree_index get_sibling_next(const Tree_index &sib_idx) const; - [[nodiscard]] Tree_index get_parent(const Tree_index &child_idx) const; - - // Modifiers - void add_child(const Tree_index &par_idx, const T &data); - void add_root(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_points; // 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 < static_cast(nodes.size()); - } - [[nodiscard]] bool _cannot_be_accessed(const Tree_index &idx) const noexcept { - return idx == static_cast(Tree_Node::SpecialIndex::INVALID) - || idx == static_cast(Tree_Node::SpecialIndex::INVALID); - } - - // DEBUG - void print_nodes(); - - //: 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 { - if (!_check_idx_exists(idx)) { - throw std::out_of_range("Parent index out of range"); - } - 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; - } else if (_cannot_be_accessed(nodes[idx].getParentId())) { - 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 - * - * @todo Add exception handling for Child in hashmap - */ - 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; - } else if (_cannot_be_accessed(nodes[idx].getParentId())) { - return false; - } - - return nodes[nodes[idx].getParentId()].getLastChildId() == idx; - } - - /** - * @brief Gets the previous sibling of the node at the given index - * - * @param sib_idx The index of the sibling node (given index) - * @return The index of the previous sibling of the sibling node - * - * @todo Add exception handling for Child in hashmap - */ - template - Tree_index Tree::get_sibling_prev(const Tree_index &sib_idx) const { - if (!_check_idx_exists(sib_idx)) { - throw std::out_of_range("Sibling index out of range"); - } else if (nodes[sib_idx].is_root() || is_first_child(sib_idx)) { - return static_cast(Tree_Node::SpecialIndex::INVALID); - } - - return sib_idx - 1; // The flattening policy assigns consecutive id to sibs. - } - - /** - * @brief Gets the next sibling of the node at the given index - * - * @param sib_idx The index of the sibling node (given index) - * @return The index of the next sibling of the sibling node - * - * @todo Add exception handling for Child in hashmap - */ - template - Tree_index Tree::get_sibling_next(const Tree_index &sib_idx) const { - if (!_check_idx_exists(sib_idx)) { - throw std::out_of_range("Sibling index out of range"); - } else if (nodes[sib_idx].is_root() || is_last_child(sib_idx)) { - return static_cast(Tree_Node::SpecialIndex::INVALID); - } - - return sib_idx + 1; // The flattening policy assigns consecutive id to sibs. - } - - /** - * @brief Gets the parent of the node at the given index - * - * @param sib_idx The index of the child node (given index) - * @return The index of the parent of the child node - * - * @todo Add exception handling for Child in hashmap - */ - template - Tree_index Tree::get_parent(const Tree_index &child_idx) const { - if (!_check_idx_exists(child_idx)) { - throw std::out_of_range("Child index out of range"); - } - - return nodes[child_idx].getParentId(); - } - - /** - * @brief Adds the first node, that is, the root of the tree - * - * @param data The data to be stored in the root node - */ - template - void Tree::add_root(const T &data) { - if (!nodes.empty()) { - throw std::logic_error("Root already exists"); - } - nodes.emplace_back(); - data_points.emplace_back(data); - } - - - /** - * @brief Adds a child to the node at the given index - * - * @param par_idx The index of the parent node (given index) - * @param data The data to be stored in the child node - * - * @todo Add exception handling for Child in hashmap - * @todo Add case II - */ - template - void Tree::add_child(const Tree_index &par_idx, const T &data) { - if (nodes.empty()) { - throw std::logic_error("Tree is empty. Add a root node first."); - } else if (!_check_idx_exists(par_idx)) { - throw std::out_of_range("Parent index out of range"); - } - - std::cout << "Adding child to parent : " << par_idx << "\n"; - - /* PLEASE REMOVE THIS COMMENT ONCE THE BRUTE ADD CHILD HAS BEEN FINALIZED */ - // Case I: The add_child has been called close to the end of this array - // I will have to add the child right AFTER the par_idx.lastchild - // If the parent has no children yet, then the id will be right - // AFTER the par_idx. - // 0) last_no_upd = (par_idx.has_no_children()) ? par_idx : par_idx.lastchild - // 1) Shift everything at id > to_add to the right by 1 - // 2) Add +1 to every (fc, lc, par) id > to_add. NOTE: - // if any par <= to_add, then don't do id++. - // 3) Add the new node at to_add - // 4) Update the par_idx.lastchild to to_add - const Tree_index last_no_upd = (nodes[par_idx].has_no_children()) ? - par_idx : nodes[par_idx].getLastChildId(); - - std::cout << "Last no update : " << last_no_upd << "\n"; - - // Make space for another node at the end - Tree::print_nodes(); - - nodes.emplace_back(); - data_points.emplace_back(data); - Tree::print_nodes(); - - // Shift everything at id > last_no_upd to the right by 1 - // and add +1 wherever valid - for (Tree_index i = nodes.size() - 1; i > last_no_upd; --i) { - std::cout << "Loop starts running : " << i << "\n"; - nodes[i] = nodes[i - 1]; - data_points[i] = data_points[i - 1]; - - // Add +1 to every (fc, lc, par) id > last_no_upd - if (nodes[i].getFirstChildId() > last_no_upd) - nodes[i].setFirstChildId(nodes[i].getFirstChildId() + 1); - - if (nodes[i].getLastChildId() > last_no_upd) - nodes[i].setLastChildId(nodes[i].getLastChildId() + 1); - - if (nodes[i].getParentId() > last_no_upd) - nodes[i].setParentId(nodes[i].getParentId() + 1); - } - - // Add the new node at last_no_upd + 1 - nodes[last_no_upd + 1] = Tree_Node(static_cast(Tree_Node::SpecialIndex::INVALID), - static_cast(Tree_Node::SpecialIndex::INVALID), - par_idx); - data_points[last_no_upd + 1] = data; - - // Update bookkeeping at the parent index - if (nodes[par_idx].has_no_children()) { - nodes[par_idx].setFirstChildId(last_no_upd + 1); - } - nodes[par_idx].setLastChildId(last_no_upd + 1); - - Tree::print_nodes(); - } - - /** - * @brief Print the list nodes in the tree, for debugging purposes - * - */ - template - void Tree::print_nodes() { - std::cout << "Nodes in the tree:\n"; - for (std::size_t i = 0; i < nodes.size(); ++i) { - std::cout << "Node (" << i << ") : "; - std::cout << " [" << nodes[i].getFirstChildId() << ", "; - std::cout << nodes[i].getLastChildId() << ", "; - std::cout << nodes[i].getParentId() << "] -> "; - std::cout << data_points[i] << "\n"; - } - } -} // HHDS namespace \ No newline at end of file From e08d850b68ef23b08ea0aa4db56b6da46e905239 Mon Sep 17 00:00:00 2001 From: Ujjwal Shekhar Date: Sat, 8 Jun 2024 17:41:32 +0530 Subject: [PATCH 04/15] Started with the new tree representation. - The invalidity of a tree_pointer is now reduced to checking if the parent is MAX_VALUE. Reason: The last guy can never be a parent. - Stub implementations added for now, need to add tests and complete the implementation. ## Todos for now: -[] Complete all query based API (other than `get_tree_width`). -[] Complete `add_child` and `append_sibling`. -[] Add pathological tests. --- hhds/tree.hpp | 253 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 253 insertions(+) diff --git a/hhds/tree.hpp b/hhds/tree.hpp index e69de29..ddd7e34 100644 --- a/hhds/tree.hpp +++ b/hhds/tree.hpp @@ -0,0 +1,253 @@ +// This file is distributed under the BSD 3-Clause License. See LICENSE for details. +#pragma once + +// tree.hpp +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace hhds { +/** 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. + * + * NEVER let it exceed 512 bits. +*/ + +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 = 1 << CHUNK_BITS; // Maximum number of nodes in the tree + +class __attribute__((packed, aligned(512))) Tree_pointers { +private: + // 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; + }; + + std::array first_child_s; + std::array last_child_s; +// :private + +public: + /* DEFAULT CONSTRUCTOR */ + Tree_pointers() + : parent(MAX_TREE_SIZE), next_sibling(0), prev_sibling(0), + first_child_l(0), last_child_l(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}); + } + + /* COPY CONSTRUCTOR */ + Tree_pointers(const Tree_pointers& other) + : 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), + first_child_s(other.first_child_s), last_child_s(other.last_child_s) {} + + /* PARAM CONSTRUCTOR */ + Tree_pointers(Tree_pos p, Tree_pos ns, Tree_pos ps, Tree_pos fcl, Tree_pos lcl, + const std::array& fcs, const std::array& lcs) + : parent(p), next_sibling(ns), prev_sibling(ps), first_child_l(fcl), last_child_l(lcl), + first_child_s(fcs), last_child_s(lcs) {} + + // 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; } + const std::array& get_first_child_s() const { return first_child_s; } + const std::array& get_last_child_s() const { return last_child_s; } + + // 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; } + void set_first_child_s(uint32_t idx, Tree_pos fcs) { first_child_s[idx] = short_child{fcs}; } + void set_last_child_s(uint32_t idx, Tree_pos lcs) { last_child_s[idx] = short_child{lcs}; } + + // Operators + constexpr bool operator==(const Tree_pointers& other) const { + 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 && first_child_s == other.first_child_s && + last_child_s == other.last_child_s; + } + 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; } +// :public + +}; // Tree_pointers class + +template +class tree { +private: + /* The tree pointers and data stored separately */ + std::vector pointers_stack; + std::vector data_stack; +// :protected + +public: + /** + * Query based API (no updates) + /* + + /** + * @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 + */ + Tree_pos get_last_child(const Tree_pos& parent_index) { + const auto chunk_id = (parent_index >> 3); + const auto last_child_s = pointers_stack[chunk_id].get_last_child_s(); + + // Try checking if we stored it in a short delta + for (const auto& delta : last_child_s) { + if (delta.delta != 0) { + const auto child_chunk_id = chunk_id + delta.delta; + assert(pointers_stack[child_chunk_id].get_parent() == parent_index); + + // NOTE: The last_child will be the last used offset here + for (short offset = (1 << 3) - 2; offset >= 0; --offset) { + if (!pointers_stack[child_chunk_id + offset].is_invalid()) { + return child_chunk_id + offset; + } + } + } + } + + // If it’s not in the short delta, it’s in the long one. + const auto last_child_l = pointers_stack[chunk_id].get_last_child_l(); + + for (short offset = CHUNK_MASK; offset >= 0; --offset) { + if (!pointers_stack[last_child_l + offset].is_invalid()) { + return last_child_l + offset; + } + } + + return INVALID; + } + + Tree_pos get_first_child(const Tree_pos& parent_index) { + auto chunk_id = (parent_index >> 3); + auto first_child_s = pointers_stack[chunk_id].get_first_child_s(); + + // Try checking if we stored it in a short delta + for (const auto& delta : first_child_s) { + if (delta.delta != 0) { + auto child_chunk_id = chunk_id + delta.delta; + assert(pointers_stack[child_chunk_id].get_parent() == parent_index); + // NOTE: The first_child is always the first in the cluster + return child_chunk_id; + } + } + + // If it's not in the short delta, it's in the long one. + auto first_child_l = pointers_stack[chunk_id].get_first_child_l(); + + // No need to verify offset, first one has to be the first_child + return first_child_l; + } + + bool is_last_child(const Tree_pos& self_index) { + auto chunk_id = (self_index >> 3); + auto parent_abs_id = pointers_stack[chunk_id].get_parent(); + + // It has to be the parent’s last child + return get_last_child(parent_abs_id) == self_index; + } + + bool is_first_child(const Tree_pos& self_index) { + auto chunk_id = (self_index >> 3); + auto parent_abs_id = pointers_stack[chunk_id].get_parent(); + + // It has to be the parent’s first child + return get_first_child(parent_abs_id) == self_index; + } + + Tree_pos get_sibling_next(const Tree_pos& sibling_id) { + auto chunk_id = (sibling_id >> 3); + auto parent_abs_id = pointers_stack[chunk_id].get_parent(); + auto last_child_id = get_last_child(parent_abs_id); + + // If this is the last child, no next sibling + if (sibling_id == last_child_id) { + return INVALID; + } + + // If there are other options within the chunk + if ((sibling_id - chunk_id < ((1 << 3) - 1)) && !pointers_stack[sibling_id + 1].is_invalid()) { + return sibling_id + 1; + } + + // Just jump to the next sibling chunk, or returns invalid + auto next_sib_chunk = pointers_stack[sibling_id].get_next_sibling(); + return (next_sib_chunk != 0 ? next_sib_chunk : INVALID); + } + + Tree_pos get_sibling_prev(const Tree_pos& sibling_id) { + auto chunk_id = (sibling_id >> 3); + auto parent_abs_id = pointers_stack[chunk_id].get_parent(); + auto first_child_id = get_first_child(parent_abs_id); + + // If this is the first child, no previous sibling + if (sibling_id == first_child_id) { + return INVALID; + } + + // If there are other options within the chunk + if ((sibling_id - chunk_id) && !pointers_stack[sibling_id - 1].is_invalid()) { + return sibling_id - 1; + } + + // Just jump to the previous sibling chunk, or returns invalid + auto prev_sib_chunk = pointers_stack[sibling_id].get_prev_sibling(); + return (prev_sib_chunk != 0 ? prev_sib_chunk : INVALID); + } + + int get_tree_width(const int& level) { + // Placeholder: Implement the actual width calculation using BFS or additional bookkeeping + return 0; + } +} +// :public + +} // hhds namespace \ No newline at end of file From d5804a0b57ee38e66114e3341127e1a34eaa840b Mon Sep 17 00:00:00 2001 From: Ujjwal Shekhar Date: Mon, 10 Jun 2024 00:44:16 +0530 Subject: [PATCH 05/15] Fixed faulty array of bitfields --- hhds/tree.cpp | 153 ++++++++++++++++++++++++++++++++------------- hhds/tree.hpp | 168 +++++++++++++++++++++++++++++++++----------------- 2 files changed, 223 insertions(+), 98 deletions(-) diff --git a/hhds/tree.cpp b/hhds/tree.cpp index e905719..9d6c0b7 100644 --- a/hhds/tree.cpp +++ b/hhds/tree.cpp @@ -1,49 +1,116 @@ -// test_tree.cpp -#include "tree.hpp" +#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; -using namespace hhds; - -// Function to print the status of a node -template -void print_node_status(const Tree& tree, Tree_index idx) { - std::cout << "Node " << idx << " status:\n"; - std::cout << " is_empty: " << tree.is_empty(idx) << "\n"; - std::cout << " is_first_child: " << tree.is_first_child(idx) << "\n"; - std::cout << " is_last_child: " << tree.is_last_child(idx) << "\n"; - std::cout << " First child: " << tree.get_first_child(idx) << "\n"; - std::cout << " Last child: " << tree.get_last_child(idx) << "\n"; - std::cout << " Previous sibling: " << tree.get_sibling_prev(idx) << "\n"; - std::cout << " Next sibling: " << tree.get_sibling_next(idx) << "\n"; - std::cout << " Parent: " << tree.get_parent(idx) << "\n"; - std::cout << std::endl; -} + // for (int i = 0; i < CHUNK_MASK; i++) { + // first_child_s[i] = 0; + // last_child_s[i] = 0; + // } + } +}; int main() { - Tree tree; - - // Add root node - // tree.add_child(static_cast(Tree_Node::SpecialIndex::INVALID), "root"); - tree.add_root("root"); - Tree_index root_idx = 0; - - // Add child nodes - std::cout << "Adding child node 1 \n"; - tree.add_child(root_idx, "child1"); - std::cout << "Adding child node 2 \n"; - tree.add_child(root_idx, "child2"); - - // Add grandchild nodes - Tree_index child1_idx = tree.get_first_child(root_idx); - Tree_index child2_idx = tree.get_sibling_next(child1_idx); - std::cout << "Adding grandchild node 1 \n"; - tree.add_child(child1_idx, "grandchild1"); - std::cout << "Adding grandchild node 2 \n"; - tree.add_child(child1_idx, "grandchild2"); - - std::cout << "Adding greatgrandchild node 1 \n"; - child1_idx = tree.get_last_child(child1_idx); - tree.add_child(child2_idx, "greatgrandchild1"); + + 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 ddd7e34..e9ff92d 100644 --- a/hhds/tree.hpp +++ b/hhds/tree.hpp @@ -39,6 +39,7 @@ static constexpr short SHORT_DELTA = 21; // Amount of short de 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 short NUM_SHORT_DEL = CHUNK_MASK; // Mask for chunk offset static constexpr uint64_t MAX_TREE_SIZE = 1 << CHUNK_BITS; // Maximum number of nodes in the tree class __attribute__((packed, aligned(512))) Tree_pointers { @@ -53,34 +54,91 @@ class __attribute__((packed, aligned(512))) Tree_pointers { Tree_pos last_child_l : CHUNK_BITS; // Short (delta) child pointers - struct __attribute__((packed)) short_child { - Tree_pos delta : SHORT_DELTA; - }; + 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; + + // Helper function to access first child pointers by index + Tree_pos _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, Tree_pos 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 function to access last child pointers by index + Tree_pos _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, Tree_pos 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"); + } + } - std::array first_child_s; - std::array 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(0), prev_sibling(0), - first_child_l(0), last_child_l(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}); - } - - /* COPY CONSTRUCTOR */ - Tree_pointers(const Tree_pointers& other) - : 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), - first_child_s(other.first_child_s), last_child_s(other.last_child_s) {} - - /* PARAM CONSTRUCTOR */ - Tree_pointers(Tree_pos p, Tree_pos ns, Tree_pos ps, Tree_pos fcl, Tree_pos lcl, - const std::array& fcs, const std::array& lcs) - : parent(p), next_sibling(ns), prev_sibling(ps), first_child_l(fcl), last_child_l(lcl), - first_child_s(fcs), last_child_s(lcs) {} + : 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); + } + } // Getters Tree_pos get_parent() const { return parent; } @@ -88,8 +146,10 @@ class __attribute__((packed, aligned(512))) Tree_pointers { 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; } - const std::array& get_first_child_s() const { return first_child_s; } - const std::array& get_last_child_s() const { return last_child_s; } + + // Public getters for short child pointers + Tree_pos get_first_child_s_at(short index) const { return get_first_child_s(index); } + Tree_pos get_last_child_s_at(short index) const { return get_last_child_s(index); } // Setters void set_parent(Tree_pos p) { parent = p; } @@ -97,21 +157,29 @@ class __attribute__((packed, aligned(512))) Tree_pointers { 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; } - void set_first_child_s(uint32_t idx, Tree_pos fcs) { first_child_s[idx] = short_child{fcs}; } - void set_last_child_s(uint32_t idx, Tree_pos lcs) { last_child_s[idx] = short_child{lcs}; } + + // Public setters for short child pointers + void set_first_child_s_at(short index, Tree_pos fcs) { set_first_child_s(index, fcs); } + void set_last_child_s_at(short index, Tree_pos 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 && first_child_s == other.first_child_s && - last_child_s == other.last_child_s; + 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_invalid() const { return parent == INVALID; } // :public }; // Tree_pointers class @@ -122,7 +190,12 @@ class tree { /* The tree pointers and data stored separately */ std::vector pointers_stack; std::vector data_stack; -// :protected + + /* Special functions for sanity */ + [[nodiscard]] bool _check_idx_exists(const Tree_pos &idx) const noexcept { + return idx >= 0 && idx < static_cast(pointers_stack.size()); + } +// :private public: /** @@ -136,31 +209,16 @@ class tree { * @return Tree_pos The absolute ID of the last child, INVALID if none */ Tree_pos get_last_child(const Tree_pos& parent_index) { - const auto chunk_id = (parent_index >> 3); - const auto last_child_s = pointers_stack[chunk_id].get_last_child_s(); - - // Try checking if we stored it in a short delta - for (const auto& delta : last_child_s) { - if (delta.delta != 0) { - const auto child_chunk_id = chunk_id + delta.delta; - assert(pointers_stack[child_chunk_id].get_parent() == parent_index); - - // NOTE: The last_child will be the last used offset here - for (short offset = (1 << 3) - 2; offset >= 0; --offset) { - if (!pointers_stack[child_chunk_id + offset].is_invalid()) { - return child_chunk_id + offset; - } - } - } - } - - // If it’s not in the short delta, it’s in the long one. - const auto last_child_l = pointers_stack[chunk_id].get_last_child_l(); - - for (short offset = CHUNK_MASK; offset >= 0; --offset) { - if (!pointers_stack[last_child_l + offset].is_invalid()) { - return last_child_l + offset; - } + const auto chunk_id = (parent_index >> MAX_OFFSET); + const auto chunk_offset = (parent_index & CHUNK_MASK); + const auto last_child_s_i = pointers_stack[chunk_id].get_last_child_s_at( + parent_index & CHUNK_MASK + ); + + if (chunk_offset) { + + } else { + const auto last_child_l = pointers_stack[chunk_id].get_last_child_l(); } return INVALID; From 626f5f4c4fae048a9a1931ec8dc1e9627a54bbba Mon Sep 17 00:00:00 2001 From: Ujjwal Shekhar Date: Mon, 10 Jun 2024 12:09:51 +0530 Subject: [PATCH 06/15] Finished coding up - `get_first_child` - `get_last_child` - `is_first_child` - `is_last_child` > These methods still need to be tested, can't be done until `add_child()` is ready. --- hhds/tree.hpp | 155 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 120 insertions(+), 35 deletions(-) diff --git a/hhds/tree.hpp b/hhds/tree.hpp index e9ff92d..24f5dbf 100644 --- a/hhds/tree.hpp +++ b/hhds/tree.hpp @@ -36,8 +36,8 @@ 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_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; // Mask for chunk offset static constexpr uint64_t MAX_TREE_SIZE = 1 << CHUNK_BITS; // Maximum number of nodes in the tree @@ -45,7 +45,7 @@ static constexpr uint64_t MAX_TREE_SIZE = 1 << CHUNK_BITS; // Maximum number of class __attribute__((packed, aligned(512))) Tree_pointers { private: // We only store the exact ID of parent - Tree_pos parent : CHUNK_BITS + MAX_OFFSET; + Tree_pos parent : CHUNK_BITS + CHUNK_SHIFT; Tree_pos next_sibling : CHUNK_BITS; Tree_pos prev_sibling : CHUNK_BITS; @@ -179,7 +179,8 @@ class __attribute__((packed, aligned(512))) Tree_pointers { void invalidate() { parent = MAX_TREE_SIZE; } // Checkers - [[nodiscard]] constexpr bool is_invalid() const { return parent == INVALID; } + [[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 @@ -195,6 +196,12 @@ class tree { [[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] != nullptr); + } // :private public: @@ -207,58 +214,136 @@ class tree { * * @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 */ Tree_pos get_last_child(const Tree_pos& parent_index) { - const auto chunk_id = (parent_index >> MAX_OFFSET); + 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( - parent_index & CHUNK_MASK - ); + const auto last_child_s_i = pointers_stack[chunk_id].get_last_child_s_at(chunk_offset); - if (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 { - const auto last_child_l = pointers_stack[chunk_id].get_last_child_l(); + // 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 (pointers_stack[child_chunk_id + offset].is_valid()) { + return static_cast(child_chunk_id + offset); + } + } } - return INVALID; + 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 + */ Tree_pos get_first_child(const Tree_pos& parent_index) { - auto chunk_id = (parent_index >> 3); - auto first_child_s = pointers_stack[chunk_id].get_first_child_s(); - - // Try checking if we stored it in a short delta - for (const auto& delta : first_child_s) { - if (delta.delta != 0) { - auto child_chunk_id = chunk_id + delta.delta; - assert(pointers_stack[child_chunk_id].get_parent() == parent_index); - // NOTE: The first_child is always the first in the cluster - return child_chunk_id; - } + 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); - // If it's not in the short delta, it's in the long one. - auto first_child_l = pointers_stack[chunk_id].get_first_child_l(); + 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 (pointers_stack[child_chunk_id + offset].is_valid()) { + return static_cast(child_chunk_id + offset); + } + } + } - // No need to verify offset, first one has to be the first_child - return first_child_l; + 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 + */ bool is_last_child(const Tree_pos& self_index) { - auto chunk_id = (self_index >> 3); - auto parent_abs_id = pointers_stack[chunk_id].get_parent(); + 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 (pointers_stack[self_chunk_id + offset].is_valid()) { + return false; + } + } - // It has to be the parent’s last child - return get_last_child(parent_abs_id) == self_index; + 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 + */ bool is_first_child(const Tree_pos& self_index) { - auto chunk_id = (self_index >> 3); - auto parent_abs_id = pointers_stack[chunk_id].get_parent(); + 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; + } - // It has to be the parent’s first child - return get_first_child(parent_abs_id) == self_index; + return true; } Tree_pos get_sibling_next(const Tree_pos& sibling_id) { From b7f1e7380732d77a4c055a511e0f2c01d3ebc8ca Mon Sep 17 00:00:00 2001 From: Ujjwal Shekhar Date: Mon, 10 Jun 2024 12:31:41 +0530 Subject: [PATCH 07/15] Added code for `get_next_sibling()` and `get_prev_sibling()` --- hhds/tree.hpp | 75 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 52 insertions(+), 23 deletions(-) diff --git a/hhds/tree.hpp b/hhds/tree.hpp index 24f5dbf..4569d84 100644 --- a/hhds/tree.hpp +++ b/hhds/tree.hpp @@ -238,7 +238,7 @@ class tree { // 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 (pointers_stack[child_chunk_id + offset].is_valid()) { + if (_contains_data(child_chunk_id + offset)) { return static_cast(child_chunk_id + offset); } } @@ -276,7 +276,7 @@ class tree { // 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 (pointers_stack[child_chunk_id + offset].is_valid()) { + if (_contains_data(child_chunk_id + offset)) { return static_cast(child_chunk_id + offset); } } @@ -309,7 +309,7 @@ class tree { // 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 (pointers_stack[self_chunk_id + offset].is_valid()) { + if (_contains_data(self_chunk_id + offset)) { return false; } } @@ -346,44 +346,73 @@ class tree { 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 + */ Tree_pos get_sibling_next(const Tree_pos& sibling_id) { - auto chunk_id = (sibling_id >> 3); - auto parent_abs_id = pointers_stack[chunk_id].get_parent(); - auto last_child_id = get_last_child(parent_abs_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 (sibling_id == last_child_id) { + if (is_last_child(sibling_id)) { return INVALID; } - // If there are other options within the chunk - if ((sibling_id - chunk_id < ((1 << 3) - 1)) && !pointers_stack[sibling_id + 1].is_invalid()) { - return sibling_id + 1; + // 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 - auto next_sib_chunk = pointers_stack[sibling_id].get_next_sibling(); - return (next_sib_chunk != 0 ? next_sib_chunk : 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 + */ Tree_pos get_sibling_prev(const Tree_pos& sibling_id) { - auto chunk_id = (sibling_id >> 3); - auto parent_abs_id = pointers_stack[chunk_id].get_parent(); - auto first_child_id = get_first_child(parent_abs_id); + if (!_check_idx_exists(sibling_id)) { + throw std::out_of_range("Sibling index out of range"); + } - // If this is the first child, no previous sibling - if (sibling_id == first_child_id) { + // If this is the first child, no prev sibling + if (is_first_child(sibling_id)) { return INVALID; } - // If there are other options within the chunk - if ((sibling_id - chunk_id) && !pointers_stack[sibling_id - 1].is_invalid()) { - return sibling_id - 1; + // 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); + } + } } - // Just jump to the previous sibling chunk, or returns invalid - auto prev_sib_chunk = pointers_stack[sibling_id].get_prev_sibling(); - return (prev_sib_chunk != 0 ? prev_sib_chunk : INVALID); + return INVALID; } int get_tree_width(const int& level) { From 3e8531cb3a817b407e625c7a94bf60d3497f8ace Mon Sep 17 00:00:00 2001 From: Ujjwal Shekhar Date: Mon, 10 Jun 2024 15:30:17 +0530 Subject: [PATCH 08/15] Testing file created. Minor bugs fixed. --- hhds/testing.cpp | 52 ++++++++++++++++++++++++++++++++++++++++++++++++ hhds/tree.hpp | 41 ++++++++++++++++++++------------------ 2 files changed, 74 insertions(+), 19 deletions(-) create mode 100644 hhds/testing.cpp diff --git a/hhds/testing.cpp b/hhds/testing.cpp new file mode 100644 index 0000000..e66cb88 --- /dev/null +++ b/hhds/testing.cpp @@ -0,0 +1,52 @@ +// main.cpp +// This file is distributed under the BSD 3-Clause License. See LICENSE for details. + +#include +#include "tree.hpp" // Ensure this path is correct according to your project structure + +int main() { + using namespace hhds; + + // Creating a tree with integer data + tree my_tree; + + // Example usage (assuming the tree has been populated appropriately) + Tree_pos root_index = ROOT; + + try { + Tree_pos last_child = my_tree.get_last_child(root_index); + if (last_child != INVALID) { + std::cout << "Last child of root has index: " << last_child << std::endl; + } else { + std::cout << "Root has no children." << std::endl; + } + + Tree_pos first_child = my_tree.get_first_child(root_index); + if (first_child != INVALID) { + std::cout << "First child of root has index: " << first_child << std::endl; + } else { + std::cout << "Root has no children." << std::endl; + } + + bool is_last = my_tree.is_last_child(first_child); + std::cout << "Is the first child also the last child of root? " << (is_last ? "Yes" : "No") << std::endl; + + Tree_pos next_sibling = my_tree.get_sibling_next(first_child); + if (next_sibling != INVALID) { + std::cout << "Next sibling of first child has index: " << next_sibling << std::endl; + } else { + std::cout << "First child has no next sibling." << std::endl; + } + + Tree_pos prev_sibling = my_tree.get_sibling_prev(next_sibling); + if (prev_sibling != INVALID) { + std::cout << "Previous sibling of next sibling has index: " << prev_sibling << std::endl; + } else { + std::cout << "Next sibling has no previous sibling." << std::endl; + } + } catch (const std::out_of_range& e) { + std::cerr << "Error: " << e.what() << std::endl; + } + + return 0; +} diff --git a/hhds/tree.hpp b/hhds/tree.hpp index 4569d84..ef0e9d6 100644 --- a/hhds/tree.hpp +++ b/hhds/tree.hpp @@ -32,17 +32,17 @@ namespace hhds { 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 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; // Mask for chunk offset -static constexpr uint64_t MAX_TREE_SIZE = 1 << CHUNK_BITS; // Maximum number of nodes in the tree - -class __attribute__((packed, aligned(512))) Tree_pointers { +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 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; // Mask for chunk offset +static constexpr uint64_t MAX_TREE_SIZE = 1LL << CHUNK_BITS; // Maximum number of nodes in the tree + +class __attribute__((packed, aligned(64))) Tree_pointers { private: // We only store the exact ID of parent Tree_pos parent : CHUNK_BITS + CHUNK_SHIFT; @@ -148,8 +148,8 @@ class __attribute__((packed, aligned(512))) Tree_pointers { Tree_pos get_last_child_l() const { return last_child_l; } // Public getters for short child pointers - Tree_pos get_first_child_s_at(short index) const { return get_first_child_s(index); } - Tree_pos get_last_child_s_at(short index) const { return get_last_child_s(index); } + Tree_pos get_first_child_s_at(short index) const { return _get_first_child_s(index); } + Tree_pos get_last_child_s_at(short index) const { return _get_last_child_s(index); } // Setters void set_parent(Tree_pos p) { parent = p; } @@ -159,8 +159,8 @@ class __attribute__((packed, aligned(512))) Tree_pointers { void set_last_child_l(Tree_pos lcl) { last_child_l = lcl; } // Public setters for short child pointers - void set_first_child_s_at(short index, Tree_pos fcs) { set_first_child_s(index, fcs); } - void set_last_child_s_at(short index, Tree_pos lcs) { set_last_child_s(index, lcs); } + void set_first_child_s_at(short index, Tree_pos fcs) { _set_first_child_s(index, fcs); } + void set_last_child_s_at(short index, Tree_pos lcs) { _set_last_child_s(index, lcs); } // Operators constexpr bool operator==(const Tree_pointers& other) const { @@ -200,14 +200,14 @@ class tree { /* CHANGE THE SECOND CONDITION CAN USE STD::OPTIONAL WRAPPING AROUND THE TEMPLATE OF X */ - return (idx < data_stack.size() && data_stack[idx] != nullptr); + return (idx < data_stack.size() && data_stack[idx]); } // :private public: /** * Query based API (no updates) - /* + */ /** * @brief Get absolute ID of the last child of a node. @@ -419,7 +419,10 @@ class tree { // Placeholder: Implement the actual width calculation using BFS or additional bookkeeping return 0; } -} -// :public + /** + * Update based API (Adds and Deletes from the tree) + */ + // :public + }; // tree class } // hhds namespace \ No newline at end of file From e33bf9f7a080b3dea590496399e769ae2a3bd213 Mon Sep 17 00:00:00 2001 From: Ujjwal Shekhar Date: Tue, 11 Jun 2024 12:29:26 +0530 Subject: [PATCH 09/15] Started with `add_child`. Handling a chain of updates will take a lot of time. --- hhds/tree.hpp | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/hhds/tree.hpp b/hhds/tree.hpp index ef0e9d6..2edb8e6 100644 --- a/hhds/tree.hpp +++ b/hhds/tree.hpp @@ -140,6 +140,17 @@ class __attribute__((packed, aligned(64))) Tree_pointers { } } + /* 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; } @@ -202,6 +213,26 @@ class tree { 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)*/ + 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; + } // :private public: @@ -423,6 +454,50 @@ class tree { /** * Update based API (Adds and Deletes from the tree) */ + + /** + * @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 + */ + Tree_pos 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); + + // Update the parent's first and last child pointers to this new child + // If the offset is 0, then we can always fit the chunk id of the child + // If the offset is non-zero: + // Try to fit the delta in short delta pointers + // If you can do that then all good we move on. + // If you cannot do that then break the chunk in which the parent is + // Put [parent, end) in the a new chunk + // Things easy to manage: + // Parent pointer -> remains the same as this chunk + // prev sibling -> initially same as this chunk, becomes the next after every add + // next sibling -> keep making it as createspace()>>shift + // Things NOT so easy to manage: + // after moving to the next chunk this might get messed up + // The short deltas that worked out initially might not work now (2^21 * 2 < 2*43) + // So, now we first put the first guy thta was moved, set the long pointers + // keep iterating to the right, try updating the delta, if updated then go to the next guy + // if not updated then break the chunk and move the rest to the next chunk + // repeat and update all the pointers accordingly + } + // :public }; // tree class } // hhds namespace \ No newline at end of file From 2190817a97adc6c73446aa2e8b1d56cd2b293712 Mon Sep 17 00:00:00 2001 From: Ujjwal Shekhar Date: Thu, 13 Jun 2024 16:12:07 +0530 Subject: [PATCH 10/15] Started with the chunk breaking and shifting code. Will have to test this, and clean it up too. --- hhds/tree.hpp | 73 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 54 insertions(+), 19 deletions(-) diff --git a/hhds/tree.hpp b/hhds/tree.hpp index 2edb8e6..e194fdc 100644 --- a/hhds/tree.hpp +++ b/hhds/tree.hpp @@ -231,7 +231,7 @@ class tree { // Add the single pointer node for all CHUNK_SIZE entries pointers_stack.emplace_back(parent_index); - return data_stack.size() - CHUNK_SIZE; + return (data_stack.size() - CHUNK_SIZE) >> CHUNK_SHIFT; } // :private @@ -477,25 +477,60 @@ class tree { } 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 { + const auto grand_parent_id = pointers_stack[parent_chunk_id].get_parent(); + auto new_chunk_id = create_space(grand_parent_id, X()); + + // Add bookkeeping to the fresh sibling chunk + pointers_stack[new_chunk_id].set_parent(grand_parent_id); + pointers_stack[new_chunk_id].set_prev_sibling(parent_chunk_id); + pointers_stack[new_chunk_id].set_next_sibling(pointers_stack[parent_chunk_id].get_next_sibling()); + pointers_stack[parent_chunk_id].set_next_sibling(new_chunk_id); - // Update the parent's first and last child pointers to this new child - // If the offset is 0, then we can always fit the chunk id of the child - // If the offset is non-zero: - // Try to fit the delta in short delta pointers - // If you can do that then all good we move on. - // If you cannot do that then break the chunk in which the parent is - // Put [parent, end) in the a new chunk - // Things easy to manage: - // Parent pointer -> remains the same as this chunk - // prev sibling -> initially same as this chunk, becomes the next after every add - // next sibling -> keep making it as createspace()>>shift - // Things NOT so easy to manage: - // after moving to the next chunk this might get messed up - // The short deltas that worked out initially might not work now (2^21 * 2 < 2*43) - // So, now we first put the first guy thta was moved, set the long pointers - // keep iterating to the right, try updating the delta, if updated then go to the next guy - // if not updated then break the chunk and move the rest to the next chunk - // repeat and update all the pointers accordingly + 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); + } + + pointers_stack[new_chunk_id].set_first_child_l(child_chunk_id); + pointers_stack[new_chunk_id].set_last_child_l(child_chunk_id); + + // Move entries from the parent chunk to the new chunk + for (short offset = parent_chunk_offset; offset < NUM_SHORT_DEL; ++offset) { + const auto current_child_id = parent_chunk_id + offset; + + if (_contains_data(current_child_id)) { + const auto current_child_delta = static_cast(current_child_id - new_chunk_id); + + if (current_child_delta < (1 << SHORT_DELTA)) { + // We can fit the delta in short delta pointers + pointers_stack[new_chunk_id].set_first_child_s_at(offset, current_child_delta); + pointers_stack[new_chunk_id].set_last_child_s_at(offset, current_child_delta); + } else { + // If we can't fit the delta, move the rest to a new chunk + auto next_new_chunk_id = create_space(grand_parent_id, X()); + + pointers_stack[next_new_chunk_id].set_parent(grand_parent_id); + pointers_stack[next_new_chunk_id].set_prev_sibling(new_chunk_id); + pointers_stack[new_chunk_id].set_next_sibling(next_new_chunk_id); + new_chunk_id = next_new_chunk_id; + offset = -1; // Restart the offset loop for the new chunk + } + } + } + + // Invalidate the moved entries in the parent chunk + for (short offset = parent_chunk_offset; offset < NUM_SHORT_DEL; ++offset) { + data_stack[parent_chunk_id + offset] = X(); + } + } } // :public From bd51d22ed1ef6ab3fce99a560980148bbbbb21b3 Mon Sep 17 00:00:00 2001 From: Ujjwal Shekhar Date: Thu, 13 Jun 2024 17:11:31 +0530 Subject: [PATCH 11/15] Refactored public API methods to have declarations in the class but template defs outside. --- hhds/tree.hpp | 544 +++++++++++++++++++++++++++----------------------- 1 file changed, 294 insertions(+), 250 deletions(-) diff --git a/hhds/tree.hpp b/hhds/tree.hpp index e194fdc..fa999c0 100644 --- a/hhds/tree.hpp +++ b/hhds/tree.hpp @@ -215,7 +215,7 @@ class tree { } /* Function to add an entry to the pointers and data stack (typically for add/append)*/ - Tree_pos create_space(const Tree_pos &parent_index, const X& data) { + 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)) { @@ -239,300 +239,344 @@ class tree { /** * 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); + /** - * @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 - */ - Tree_pos 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 + offset)) { - return static_cast(child_chunk_id + offset); - } - } - } + * 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 - return static_cast(INVALID); +}; // 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"); } - /** - * @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 - */ - Tree_pos 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 + offset)) { - return static_cast(child_chunk_id + offset); - } - } - } + 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(); + } - return static_cast(INVALID); + // 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 + offset)) { + return static_cast(child_chunk_id + offset); + } + } } - /** - * @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 - */ - bool is_last_child(const Tree_pos& self_index) { - if (!_check_idx_exists(self_index)) { - throw std::out_of_range("Index out of range"); - } + return static_cast(INVALID); +} - const auto self_chunk_id = (self_index >> CHUNK_SHIFT); - const auto self_chunk_offset = (self_index & CHUNK_MASK); +/** + * @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"); + } - // 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; - } + 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(); + } - // 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 + offset)) { - return false; + // 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 + offset)) { + return static_cast(child_chunk_id + offset); } - } + } + } - return true; + 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"); } - /** - * @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 - */ - bool 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); - 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; + } - // If this chunk has a prev_sibling pointer, certainly not the last child - if (pointers_stack[self_chunk_id].get_prev_sibling() != INVALID) { + // 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 + offset)) { return false; } + } - // Now, to be the first child, it must have no offset - if (self_chunk_offset) { - return false; - } + return true; +} - 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"); } - /** - * @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 - */ - Tree_pos get_sibling_next(const Tree_pos& sibling_id) { - if (!_check_idx_exists(sibling_id)) { - throw std::out_of_range("Sibling index out of range"); - } + const auto self_chunk_id = (self_index >> CHUNK_SHIFT); + const auto self_chunk_offset = (self_index & CHUNK_MASK); - // If this is the last child, no next sibling - if (is_last_child(sibling_id)) { - return INVALID; - } + // 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; + } - // 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); - } + // Now, to be the first child, it must have no offset + if (self_chunk_offset) { + return false; + } + + return true; +} - // Just jump to the next sibling chunk, or returns invalid - return pointers_stack[sibling_id].get_next_sibling(); // default is INVALID +/** + * @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"); } - /** - * @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 - */ - Tree_pos 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 last child, no next sibling + if (is_last_child(sibling_id)) { + return INVALID; + } - // If this is the first child, no prev sibling - if (is_first_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); + } - // 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 + return pointers_stack[sibling_id].get_next_sibling(); // default is INVALID +} - // 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); - } - } - } +/** + * @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; } - int get_tree_width(const int& level) { - // Placeholder: Implement the actual width calculation using BFS or additional bookkeeping - return 0; + // 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); } - /** - * Update based API (Adds and Deletes from the tree) - */ - - /** - * @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 - */ - Tree_pos 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"); + // 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); + } } + } - const auto last_child_id = get_last_child(parent_index); + return INVALID; +} - // This is not the first child being added - if (last_child_id != INVALID) { - return append_sibling(last_child_id, data); - } +template +int tree::get_tree_width(const int& level) { + // Placeholder: Implement the actual width calculation using BFS or additional bookkeeping + return 0; +} - 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 { - const auto grand_parent_id = pointers_stack[parent_chunk_id].get_parent(); - auto new_chunk_id = create_space(grand_parent_id, X()); - - // Add bookkeeping to the fresh sibling chunk - pointers_stack[new_chunk_id].set_parent(grand_parent_id); - pointers_stack[new_chunk_id].set_prev_sibling(parent_chunk_id); - pointers_stack[new_chunk_id].set_next_sibling(pointers_stack[parent_chunk_id].get_next_sibling()); - pointers_stack[parent_chunk_id].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); - } +/** + * @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_chunk_offset = (sibling_id & CHUNK_MASK); + const auto sibling_parent_id = pointers_stack[sibling_chunk_id].get_parent(); + + // TODO IMPLEMENTATION - pointers_stack[new_chunk_id].set_first_child_l(child_chunk_id); - pointers_stack[new_chunk_id].set_last_child_l(child_chunk_id); - - // Move entries from the parent chunk to the new chunk - for (short offset = parent_chunk_offset; offset < NUM_SHORT_DEL; ++offset) { - const auto current_child_id = parent_chunk_id + offset; - - if (_contains_data(current_child_id)) { - const auto current_child_delta = static_cast(current_child_id - new_chunk_id); - - if (current_child_delta < (1 << SHORT_DELTA)) { - // We can fit the delta in short delta pointers - pointers_stack[new_chunk_id].set_first_child_s_at(offset, current_child_delta); - pointers_stack[new_chunk_id].set_last_child_s_at(offset, current_child_delta); - } else { - // If we can't fit the delta, move the rest to a new chunk - auto next_new_chunk_id = create_space(grand_parent_id, X()); - - pointers_stack[next_new_chunk_id].set_parent(grand_parent_id); - pointers_stack[next_new_chunk_id].set_prev_sibling(new_chunk_id); - pointers_stack[new_chunk_id].set_next_sibling(next_new_chunk_id); - new_chunk_id = next_new_chunk_id; - offset = -1; // Restart the offset loop for the new chunk - } + return INVALID +} + +/** + * @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 { + const auto grand_parent_id = pointers_stack[parent_chunk_id].get_parent(); + auto new_chunk_id = _create_space(grand_parent_id, X()); + + // Add bookkeeping to the fresh sibling chunk + pointers_stack[new_chunk_id].set_parent(grand_parent_id); + pointers_stack[new_chunk_id].set_prev_sibling(parent_chunk_id); + pointers_stack[new_chunk_id].set_next_sibling(pointers_stack[parent_chunk_id].get_next_sibling()); + pointers_stack[parent_chunk_id].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); + } + + pointers_stack[new_chunk_id].set_first_child_l(child_chunk_id); + pointers_stack[new_chunk_id].set_last_child_l(child_chunk_id); + + // Move entries from the parent chunk to the new chunk + for (short offset = parent_chunk_offset; offset < NUM_SHORT_DEL; ++offset) { + const auto current_child_id = parent_chunk_id + offset; + + if (_contains_data(current_child_id)) { + ///// THE DELTA CAN BE NEGATIVE?????????????? + const auto current_child_delta = static_cast(current_child_id - new_chunk_id); + + if (current_child_delta < (1 << SHORT_DELTA)) { + // We can fit the delta in short delta pointers + pointers_stack[new_chunk_id].set_first_child_s_at(offset, current_child_delta); + pointers_stack[new_chunk_id].set_last_child_s_at(offset, current_child_delta); + } else { + // If we can't fit the delta, move the rest to a new chunk + auto next_new_chunk_id = _create_space(grand_parent_id, X()); + + pointers_stack[next_new_chunk_id].set_parent(grand_parent_id); + pointers_stack[next_new_chunk_id].set_prev_sibling(new_chunk_id); + pointers_stack[new_chunk_id].set_next_sibling(next_new_chunk_id); + new_chunk_id = next_new_chunk_id; + offset = -1; // Restart the offset loop for the new chunk } } + } - // Invalidate the moved entries in the parent chunk - for (short offset = parent_chunk_offset; offset < NUM_SHORT_DEL; ++offset) { - data_stack[parent_chunk_id + offset] = X(); - } + // Invalidate the moved entries in the parent chunk + for (short offset = parent_chunk_offset; offset < NUM_SHORT_DEL; ++offset) { + data_stack[parent_chunk_id + offset] = X(); } } - - // :public - }; // tree class +} } // hhds namespace \ No newline at end of file From 579ce962546a30dcb801b59127a3de8f2dcd723c Mon Sep 17 00:00:00 2001 From: Ujjwal Shekhar Date: Thu, 13 Jun 2024 21:34:14 +0530 Subject: [PATCH 12/15] Almost finished `add_child()`. Need to set the data appropriately, and cross check. --- hhds/tree.hpp | 124 +++++++++++++++++++++++++++++++------------------- 1 file changed, 78 insertions(+), 46 deletions(-) diff --git a/hhds/tree.hpp b/hhds/tree.hpp index fa999c0..31d527c 100644 --- a/hhds/tree.hpp +++ b/hhds/tree.hpp @@ -27,51 +27,64 @@ namespace hhds { * 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 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 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; // Mask for chunk offset -static constexpr uint64_t MAX_TREE_SIZE = 1LL << CHUNK_BITS; // Maximum number of nodes in the tree +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; + 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; + Tree_pos first_child_l : CHUNK_BITS; + Tree_pos last_child_l : CHUNK_BITS; // Short (delta) child pointers - 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; + 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 function to access first child pointers by index - Tree_pos _get_first_child_s(short index) const { + 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; @@ -84,7 +97,7 @@ class __attribute__((packed, aligned(64))) Tree_pointers { } } - void _set_first_child_s(short index, Tree_pos value) { + 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; @@ -98,7 +111,7 @@ class __attribute__((packed, aligned(64))) Tree_pointers { } // Helper function to access last child pointers by index - Tree_pos _get_last_child_s(short index) const { + 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; @@ -111,7 +124,7 @@ class __attribute__((packed, aligned(64))) Tree_pointers { } } - void _set_last_child_s(short index, Tree_pos value) { + 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; @@ -159,8 +172,8 @@ class __attribute__((packed, aligned(64))) Tree_pointers { Tree_pos get_last_child_l() const { return last_child_l; } // Public getters for short child pointers - Tree_pos get_first_child_s_at(short index) const { return _get_first_child_s(index); } - Tree_pos get_last_child_s_at(short index) const { return _get_last_child_s(index); } + 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; } @@ -170,8 +183,8 @@ class __attribute__((packed, aligned(64))) Tree_pointers { void set_last_child_l(Tree_pos lcl) { last_child_l = lcl; } // Public setters for short child pointers - void set_first_child_s_at(short index, Tree_pos fcs) { _set_first_child_s(index, fcs); } - void set_last_child_s_at(short index, Tree_pos lcs) { _set_last_child_s(index, lcs); } + 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 { @@ -548,27 +561,46 @@ Tree_pos tree::add_child(const Tree_pos& parent_index, const X& data) { pointers_stack[new_chunk_id].set_first_child_l(child_chunk_id); pointers_stack[new_chunk_id].set_last_child_l(child_chunk_id); + // Set the data in the new_chunk_id + data_stack[new_chunk_id] = data; + // Move entries from the parent chunk to the new chunk - for (short offset = parent_chunk_offset; offset < NUM_SHORT_DEL; ++offset) { - const auto current_child_id = parent_chunk_id + offset; + for (short offset = parent_chunk_offset + 1; offset < NUM_SHORT_DEL; ++offset) { + const auto current_move_id = parent_chunk_id + offset; - if (_contains_data(current_child_id)) { - ///// THE DELTA CAN BE NEGATIVE?????????????? - const auto current_child_delta = static_cast(current_child_id - new_chunk_id); + if (_contains_data(current_move_id)) { + ///// THE DELTA CAN BE NEGATIVE + const auto current_child_delta = current_move_id - new_chunk_id; - if (current_child_delta < (1 << SHORT_DELTA)) { + if (abs(current_child_delta) <= MAX_SHORT_DELTA) { // We can fit the delta in short delta pointers - pointers_stack[new_chunk_id].set_first_child_s_at(offset, current_child_delta); - pointers_stack[new_chunk_id].set_last_child_s_at(offset, current_child_delta); + pointers_stack[new_chunk_id].set_first_child_s_at(offset, static_cast(current_child_delta)); + pointers_stack[new_chunk_id].set_last_child_s_at(offset, static_cast(current_child_delta)); + + // Set the data in the next_new_chunk_id + // @todo + } else { // If we can't fit the delta, move the rest to a new chunk auto next_new_chunk_id = _create_space(grand_parent_id, X()); pointers_stack[next_new_chunk_id].set_parent(grand_parent_id); pointers_stack[next_new_chunk_id].set_prev_sibling(new_chunk_id); + pointers_stack[next_new_chunk_id].set_next_sibling(pointers_stack[new_chunk_id].get_next_sibling()); pointers_stack[new_chunk_id].set_next_sibling(next_new_chunk_id); + + if (pointers_stack[next_new_chunk_id].get_next_sibling() != INVALID) { + pointers_stack[pointers_stack[next_new_chunk_id].get_next_sibling()].set_prev_sibling(new_chunk_id); + } + + pointers_stack[new_chunk_id].set_first_child_l(current_move_id); + pointers_stack[new_chunk_id].set_last_child_l(current_move_id); + + // Set the data in the next_new_chunk_id + // @todo + new_chunk_id = next_new_chunk_id; - offset = -1; // Restart the offset loop for the new chunk + offset++; } } } From 84fbb83c88196b56b81e5019778fa462f6be2019 Mon Sep 17 00:00:00 2001 From: Ujjwal Shekhar Date: Fri, 14 Jun 2024 00:35:41 +0530 Subject: [PATCH 13/15] Almost done with `append_sibling()`. Major issue spotted : We will have to iterate over every child chunk and change it's parent pointer when shifting chunks. --- hhds/tree.hpp | 63 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 4 deletions(-) diff --git a/hhds/tree.hpp b/hhds/tree.hpp index 31d527c..5843fc8 100644 --- a/hhds/tree.hpp +++ b/hhds/tree.hpp @@ -505,12 +505,65 @@ Tree_pos tree::append_sibling(const Tree_pos& sibling_id, const X& data) { } const auto sibling_chunk_id = (sibling_id >> CHUNK_SHIFT); - const auto sibling_chunk_offset = (sibling_id & CHUNK_MASK); const auto sibling_parent_id = pointers_stack[sibling_chunk_id].get_parent(); + const auto last_sib_chunk_id = get_last_child(sibling_parent_id); + + // Can fit the sibling in the same chunk + for (short offset = NUM_SHORT_DEL - 1; offset >= 0; offset--) { + if (!_contains_data(last_sib_chunk_id + offset)) { + // Put the data here and update bookkeeping + data_stack[last_sib_chunk_id + offset] = data; + return static_cast(last_sib_chunk_id + offset); + } + } + + // Create a new chunk for the sibling + const auto new_chunk_id = _create_space(sibling_parent_id, data); + + // Update bookkeeping + pointers_stack[new_chunk_id].set_prev_sibling(last_sib_chunk_id); + pointers_stack[new_chunk_id].set_next_sibling(pointers_stack[last_sib_chunk_id].get_next_sibling()); + pointers_stack[last_sib_chunk_id].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); + } + + // Update the last child pointer of the parent + if (sibling_parent_id & CHUNK_MASK == 0) { + pointers_stack[sibling_parent_id].set_last_child_l(new_chunk_id); + } else { + // Try to fit the delta in the short delta pointers + const auto delta = new_chunk_id - sibling_parent_id; + if (abs(delta) <= MAX_SHORT_DELTA) { + pointers_stack[sibling_parent_id].set_last_child_s_at(sibling_parent_id & CHUNK_MASK, static_cast(delta)); + } else { + // If we can't fit the delta, move the rest to a new chunk + auto next_new_chunk_id = _create_space(sibling_parent_id, X()); + const auto sib_parent_chunk_id = (sibling_parent_id >> CHUNK_SHIFT); + + // Update bookkeeping + pointers_stack[next_new_chunk_id].set_prev_sibling(sib_parent_chunk_id); + pointers_stack[next_new_chunk_id].set_next_sibling(pointers_stack[sib_parent_chunk_id].get_next_sibling()); + pointers_stack[sib_parent_chunk_id].set_next_sibling(next_new_chunk_id); + + if (pointers_stack[next_new_chunk_id].get_next_sibling() != INVALID) { + pointers_stack[pointers_stack[next_new_chunk_id].get_next_sibling()].set_prev_sibling(next_new_chunk_id); + } + + pointers_stack[next_new_chunk_id].set_first_child_l( + pointers_stack[sibling_parent_id].get_first_child_l() + ); + pointers_stack[next_new_chunk_id].set_last_child_l(new_chunk_id); + + // Update the parent pointer now that it has moved + pointers_stack[new_chunk_id].set_parent(next_new_chunk_id); + pointers_stack[sibling_parent_id].set_last_child_l(next_new_chunk_id); - // TODO IMPLEMENTATION + /* TODO : Finish ALL the bookkeeping */ - return INVALID + } + } } /** @@ -590,7 +643,7 @@ Tree_pos tree::add_child(const Tree_pos& parent_index, const X& data) { pointers_stack[new_chunk_id].set_next_sibling(next_new_chunk_id); if (pointers_stack[next_new_chunk_id].get_next_sibling() != INVALID) { - pointers_stack[pointers_stack[next_new_chunk_id].get_next_sibling()].set_prev_sibling(new_chunk_id); + pointers_stack[pointers_stack[next_new_chunk_id].get_next_sibling()].set_prev_sibling(next_new_chunk_id); } pointers_stack[new_chunk_id].set_first_child_l(current_move_id); @@ -602,6 +655,8 @@ Tree_pos tree::add_child(const Tree_pos& parent_index, const X& data) { new_chunk_id = next_new_chunk_id; offset++; } + + /* MAJOR ISSUE : WE WILL HAVE TO ITERATE OVER ALL THE CHILDREN AND CHANGE THE PARENT ID STORED IN THE CHUNKS */ } } From d85df1443b486baf2707d20c78af4a2bc56dec06 Mon Sep 17 00:00:00 2001 From: Ujjwal Shekhar Date: Fri, 14 Jun 2024 11:58:46 +0530 Subject: [PATCH 14/15] Refactored code to make a private helper function that can create, insert and update sibling_chunk pointers. --- hhds/tree.hpp | 77 +++++++++++++++++++++------------------------------ 1 file changed, 32 insertions(+), 45 deletions(-) diff --git a/hhds/tree.hpp b/hhds/tree.hpp index 5843fc8..e9270d8 100644 --- a/hhds/tree.hpp +++ b/hhds/tree.hpp @@ -52,7 +52,7 @@ static constexpr short NUM_SHORT_DEL = CHUNK_MASK; // Numbe 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 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 { @@ -83,7 +83,7 @@ class __attribute__((packed, aligned(64))) Tree_pointers { Short_delta last_child_s_5 : SHORT_DELTA; Short_delta last_child_s_6 : SHORT_DELTA; - // Helper function to access first child pointers by index + // 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; @@ -110,7 +110,7 @@ class __attribute__((packed, aligned(64))) Tree_pointers { } } - // Helper function to access last child pointers by index + // 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; @@ -246,6 +246,30 @@ class tree { return (data_stack.size() - CHUNK_SIZE) >> CHUNK_SHIFT; } + + /* Function to insert a new chunk in between (typically for handling add/append corner cases)*/ + 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; + } // :private public: @@ -518,16 +542,7 @@ Tree_pos tree::append_sibling(const Tree_pos& sibling_id, const X& data) { } // Create a new chunk for the sibling - const auto new_chunk_id = _create_space(sibling_parent_id, data); - - // Update bookkeeping - pointers_stack[new_chunk_id].set_prev_sibling(last_sib_chunk_id); - pointers_stack[new_chunk_id].set_next_sibling(pointers_stack[last_sib_chunk_id].get_next_sibling()); - pointers_stack[last_sib_chunk_id].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); - } + const auto new_chunk_id = _insert_chunk_after(last_sib_chunk_id); // Update the last child pointer of the parent if (sibling_parent_id & CHUNK_MASK == 0) { @@ -539,17 +554,8 @@ Tree_pos tree::append_sibling(const Tree_pos& sibling_id, const X& data) { pointers_stack[sibling_parent_id].set_last_child_s_at(sibling_parent_id & CHUNK_MASK, static_cast(delta)); } else { // If we can't fit the delta, move the rest to a new chunk - auto next_new_chunk_id = _create_space(sibling_parent_id, X()); const auto sib_parent_chunk_id = (sibling_parent_id >> CHUNK_SHIFT); - - // Update bookkeeping - pointers_stack[next_new_chunk_id].set_prev_sibling(sib_parent_chunk_id); - pointers_stack[next_new_chunk_id].set_next_sibling(pointers_stack[sib_parent_chunk_id].get_next_sibling()); - pointers_stack[sib_parent_chunk_id].set_next_sibling(next_new_chunk_id); - - if (pointers_stack[next_new_chunk_id].get_next_sibling() != INVALID) { - pointers_stack[pointers_stack[next_new_chunk_id].get_next_sibling()].set_prev_sibling(next_new_chunk_id); - } + auto next_new_chunk_id = _insert_chunk_after(sib_parent_chunk_id); pointers_stack[next_new_chunk_id].set_first_child_l( pointers_stack[sibling_parent_id].get_first_child_l() @@ -599,18 +605,8 @@ Tree_pos tree::add_child(const Tree_pos& parent_index, const X& data) { pointers_stack[parent_chunk_id].set_last_child_l(child_chunk_id); } else { const auto grand_parent_id = pointers_stack[parent_chunk_id].get_parent(); - auto new_chunk_id = _create_space(grand_parent_id, X()); - - // Add bookkeeping to the fresh sibling chunk - pointers_stack[new_chunk_id].set_parent(grand_parent_id); - pointers_stack[new_chunk_id].set_prev_sibling(parent_chunk_id); - pointers_stack[new_chunk_id].set_next_sibling(pointers_stack[parent_chunk_id].get_next_sibling()); - pointers_stack[parent_chunk_id].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); - } - + auto new_chunk_id = _insert_chunk_after(parent_chunk_id); + pointers_stack[new_chunk_id].set_first_child_l(child_chunk_id); pointers_stack[new_chunk_id].set_last_child_l(child_chunk_id); @@ -635,16 +631,7 @@ Tree_pos tree::add_child(const Tree_pos& parent_index, const X& data) { } else { // If we can't fit the delta, move the rest to a new chunk - auto next_new_chunk_id = _create_space(grand_parent_id, X()); - - pointers_stack[next_new_chunk_id].set_parent(grand_parent_id); - pointers_stack[next_new_chunk_id].set_prev_sibling(new_chunk_id); - pointers_stack[next_new_chunk_id].set_next_sibling(pointers_stack[new_chunk_id].get_next_sibling()); - pointers_stack[new_chunk_id].set_next_sibling(next_new_chunk_id); - - if (pointers_stack[next_new_chunk_id].get_next_sibling() != INVALID) { - pointers_stack[pointers_stack[next_new_chunk_id].get_next_sibling()].set_prev_sibling(next_new_chunk_id); - } + auto next_new_chunk_id = _insert_chunk_after(new_chunk_id); pointers_stack[new_chunk_id].set_first_child_l(current_move_id); pointers_stack[new_chunk_id].set_last_child_l(current_move_id); From a1ea4106e4ba08aa744e236162e4042ae73b4ee4 Mon Sep 17 00:00:00 2001 From: Ujjwal Shekhar Date: Fri, 14 Jun 2024 15:49:17 +0530 Subject: [PATCH 15/15] Finished `add_child` and `append_sibling`. Made helper functions - _break_chunk_from - _insert_chunk_after - _fits_in_short_del Refactored a few things and fixed minor bugs. Will refactor, change a few constants and improve the comments, code quality before proceeding with testing/debugging. --- hhds/testing.cpp | 63 ++++-------- hhds/tree.hpp | 245 ++++++++++++++++++++++++++++++----------------- 2 files changed, 174 insertions(+), 134 deletions(-) diff --git a/hhds/testing.cpp b/hhds/testing.cpp index e66cb88..b4bf41e 100644 --- a/hhds/testing.cpp +++ b/hhds/testing.cpp @@ -1,52 +1,25 @@ -// main.cpp -// This file is distributed under the BSD 3-Clause License. See LICENSE for details. - #include -#include "tree.hpp" // Ensure this path is correct according to your project structure - -int main() { - using namespace hhds; - - // Creating a tree with integer data - tree my_tree; - - // Example usage (assuming the tree has been populated appropriately) - Tree_pos root_index = ROOT; - - try { - Tree_pos last_child = my_tree.get_last_child(root_index); - if (last_child != INVALID) { - std::cout << "Last child of root has index: " << last_child << std::endl; - } else { - std::cout << "Root has no children." << std::endl; - } - Tree_pos first_child = my_tree.get_first_child(root_index); - if (first_child != INVALID) { - std::cout << "First child of root has index: " << first_child << std::endl; - } else { - std::cout << "Root has no children." << std::endl; - } - - bool is_last = my_tree.is_last_child(first_child); - std::cout << "Is the first child also the last child of root? " << (is_last ? "Yes" : "No") << std::endl; - - Tree_pos next_sibling = my_tree.get_sibling_next(first_child); - if (next_sibling != INVALID) { - std::cout << "Next sibling of first child has index: " << next_sibling << std::endl; - } else { - std::cout << "First child has no next sibling." << std::endl; - } +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; + } - Tree_pos prev_sibling = my_tree.get_sibling_prev(next_sibling); - if (prev_sibling != INVALID) { - std::cout << "Previous sibling of next sibling has index: " << prev_sibling << std::endl; - } else { - std::cout << "Next sibling has no previous sibling." << std::endl; - } - } catch (const std::out_of_range& e) { - std::cerr << "Error: " << e.what() << 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.hpp b/hhds/tree.hpp index e9270d8..fdae368 100644 --- a/hhds/tree.hpp +++ b/hhds/tree.hpp @@ -93,7 +93,9 @@ class __attribute__((packed, aligned(64))) Tree_pointers { 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"); + + default: + throw std::out_of_range("Invalid index for first_child_s"); } } @@ -106,7 +108,9 @@ class __attribute__((packed, aligned(64))) Tree_pointers { 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"); + + default: + throw std::out_of_range("Invalid index for first_child_s"); } } @@ -120,7 +124,9 @@ class __attribute__((packed, aligned(64))) Tree_pointers { 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"); + + default: + throw std::out_of_range("Invalid index for last_child_s"); } } @@ -133,7 +139,9 @@ class __attribute__((packed, aligned(64))) Tree_pointers { 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"); + + default: + throw std::out_of_range("Invalid index for last_child_s"); } } @@ -171,9 +179,13 @@ class __attribute__((packed, aligned(64))) Tree_pointers { Tree_pos get_first_child_l() const { return first_child_l; } Tree_pos get_last_child_l() const { return last_child_l; } - // Public 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); } + // 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; } @@ -182,9 +194,13 @@ class __attribute__((packed, aligned(64))) Tree_pointers { void set_first_child_l(Tree_pos fcl) { first_child_l = fcl; } void set_last_child_l(Tree_pos lcl) { last_child_l = lcl; } - // Public 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); } + // 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 { @@ -228,7 +244,7 @@ class tree { } /* Function to add an entry to the pointers and data stack (typically for add/append)*/ - Tree_pos _create_space(const Tree_pos &parent_index, const X& data) { + [[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)) { @@ -248,7 +264,7 @@ class tree { } /* Function to insert a new chunk in between (typically for handling add/append corner cases)*/ - Tree_pos _insert_chunk_after(const Tree_pos curr) { + [[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)) { @@ -258,8 +274,7 @@ class tree { // 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 + // 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); @@ -270,6 +285,92 @@ class tree { 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: @@ -325,8 +426,8 @@ Tree_pos tree::get_last_child(const Tree_pos& parent_index) { // 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 + offset)) { - return static_cast(child_chunk_id + offset); + if (_contains_data((child_chunk_id << CHUNK_SHIFT) + offset)) { + return static_cast((child_chunk_id << CHUNK_SHIFT) + offset); } } } @@ -364,8 +465,8 @@ Tree_pos tree::get_first_child(const Tree_pos& parent_index) { // 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 + offset)) { - return static_cast(child_chunk_id + offset); + if (_contains_data((child_chunk_id << CHUNK_SHIFT) + offset)) { + return static_cast((child_chunk_id << CHUNK_SHIFT) + offset); } } } @@ -398,7 +499,7 @@ bool tree::is_last_child(const Tree_pos& self_index) { // 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 + offset)) { + if (_contains_data((self_chunk_id << CHUNK_SHIFT) + offset)) { return false; } } @@ -530,46 +631,42 @@ Tree_pos tree::append_sibling(const Tree_pos& sibling_id, const X& data) { 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 = NUM_SHORT_DEL - 1; offset >= 0; offset--) { - if (!_contains_data(last_sib_chunk_id + offset)) { + 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 + offset] = data; - return static_cast(last_sib_chunk_id + offset); + 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 - const auto new_chunk_id = _insert_chunk_after(last_sib_chunk_id); + // 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) { - pointers_stack[sibling_parent_id].set_last_child_l(new_chunk_id); + 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 - const auto delta = new_chunk_id - sibling_parent_id; - if (abs(delta) <= MAX_SHORT_DELTA) { - pointers_stack[sibling_parent_id].set_last_child_s_at(sibling_parent_id & CHUNK_MASK, static_cast(delta)); + 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 { - // If we can't fit the delta, move the rest to a new chunk - const auto sib_parent_chunk_id = (sibling_parent_id >> CHUNK_SHIFT); - auto next_new_chunk_id = _insert_chunk_after(sib_parent_chunk_id); - - pointers_stack[next_new_chunk_id].set_first_child_l( - pointers_stack[sibling_parent_id].get_first_child_l() - ); - pointers_stack[next_new_chunk_id].set_last_child_l(new_chunk_id); - - // Update the parent pointer now that it has moved - pointers_stack[new_chunk_id].set_parent(next_new_chunk_id); - pointers_stack[sibling_parent_id].set_last_child_l(next_new_chunk_id); - - /* TODO : Finish ALL the bookkeeping */ + // 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; } /** @@ -604,53 +701,23 @@ Tree_pos tree::add_child(const Tree_pos& parent_index, const X& data) { 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 { - const auto grand_parent_id = pointers_stack[parent_chunk_id].get_parent(); - auto new_chunk_id = _insert_chunk_after(parent_chunk_id); - - pointers_stack[new_chunk_id].set_first_child_l(child_chunk_id); - pointers_stack[new_chunk_id].set_last_child_l(child_chunk_id); - - // Set the data in the new_chunk_id - data_stack[new_chunk_id] = data; - - // Move entries from the parent chunk to the new chunk - for (short offset = parent_chunk_offset + 1; offset < NUM_SHORT_DEL; ++offset) { - const auto current_move_id = parent_chunk_id + offset; - - if (_contains_data(current_move_id)) { - ///// THE DELTA CAN BE NEGATIVE - const auto current_child_delta = current_move_id - new_chunk_id; - - if (abs(current_child_delta) <= MAX_SHORT_DELTA) { - // We can fit the delta in short delta pointers - pointers_stack[new_chunk_id].set_first_child_s_at(offset, static_cast(current_child_delta)); - pointers_stack[new_chunk_id].set_last_child_s_at(offset, static_cast(current_child_delta)); - - // Set the data in the next_new_chunk_id - // @todo - - } else { - // If we can't fit the delta, move the rest to a new chunk - auto next_new_chunk_id = _insert_chunk_after(new_chunk_id); - - pointers_stack[new_chunk_id].set_first_child_l(current_move_id); - pointers_stack[new_chunk_id].set_last_child_l(current_move_id); - - // Set the data in the next_new_chunk_id - // @todo - - new_chunk_id = next_new_chunk_id; - offset++; - } - - /* MAJOR ISSUE : WE WILL HAVE TO ITERATE OVER ALL THE CHILDREN AND CHANGE THE PARENT ID STORED IN THE CHUNKS */ - } - } + // 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); - // Invalidate the moved entries in the parent chunk - for (short offset = parent_chunk_offset; offset < NUM_SHORT_DEL; ++offset) { - data_stack[parent_chunk_id + offset] = X(); + // 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