123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647 |
- /* License Notice:
- **
- ** This program is free software: you can redistribute it and/or modify
- ** it under the terms of the GNU General Public License as published by
- ** the Free Software Foundation, either version 3 of the License, or
- ** (at your option) any later version.
- ** This program is distributed in the hope that it will be useful,
- ** but WITHOUT ANY WARRANTY; without even the implied warranty of
- ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- ** GNU General Public License for more details.
- ** You should have received a copy of the GNU General Public License
- ** along with this program. If not, see <https://www.gnu.org/licenses/>.
- */
- /**
- * @file linked_list_simple.hpp
- * @author TooOld2Rock'nRoll
- * @date jan/2025
- * @version 0.0.2
- *
- * @attention "make CFLAGS:=-DUNIT_TESTING" to run unit testing on the library.
- *
- * @see Cormen, Thomas H. (...) - Introduction to Algorithms (3ºed)
- * @see https://cpputest.github.io/manual.html
- *
- * @todo To be truly simple, we should have a fixed number of leafs for the branches (with a array of leafs).
- * @todo maybe add a sort function!
- *
- * @brief Header file for the <i>Simple</i> version N-ary Tree data structure.
- */
- #ifndef NTREE_S_HPP
- #define NTREE_S_HPP
- /*---- Includes ----*/
- #include "linked_list_simple.hpp"
- #ifdef UNIT_TESTING
- #include <CppUTest/CommandLineTestRunner.h>
- #include <CppUTest/MemoryLeakDetectorNewMacros.h>
- #endif
- /*---- Defines ----*/
- // #define NTSI_ENABLE_BENCHMARK
- /*---- Enumerations ----*/
- /*---- Typedefs ----*/
- /*---- Class Declaration ----*/
- /**
- * @brief Simple N-ary Tree data structure.
- * @see https://en.wikipedia.org/wiki/M-ary_tree
- *
- * A N-ary Tree (or K Tree) is a dynamic data structure that is (usually) ordered and (traditionally) used to store and
- * validade chains of data, like strings from a dictionary. Each node would represent a letter and traversing the tree
- * successfully till a leaf would confirm the dictionary world is valid.<br>
- *
- * In practice, nothing more than a list of lists...<br>
- *
- * This algorithm is simples in the sense that it covers the most basic requirements to be considered a functional n-ary
- * tree, it is <b>not</b> thread safe (traversing the list in parallel would break the reading head logic).<br>
- * This algorithm is also fast by only saving references to user data removing a good overhead of consistency checks,
- * but this is not safe and difficult to debug in case of errors. Consider using the Safe version if such safeguards
- * are necessary in your project.<br>
- *
- * The first call to insert() will create the root node, subsequent inserts will add leafs to the branch currently under
- * the reading head.<br>
- * The next() function moves the read head forward into the list of leafs.<br>
- * The down() function moves the reading head to the selected leaf, moving the reading head to a different branch.<br>
- * The other functions are just helpers that make some jobs easier.<br>
- * There is no remove() method as N-ary Trees have no use for such, it would break any search logic applied to it after.<br>
- *
- * Example:
- * <code>
- * int *buff = nullptr;
- * NTree<int> TheTree;
- *
- * //create root as 10
- * TheTree.insert (new int (10));
- *
- * //leaf is 20
- * TheTree.insert (new int (20));
- * //leaf is 30
- * TheTree.insert (new int (30));
- *
- * TheTree.next (); //read head points to first leaf (30)
- * TheTree.next (); //read head points to second leaf (20)
- * TheTree.down (); //we are at branch 20
- *
- * //leaf is 400
- * TheTree.insert (new int (400));
- *
- * buff = TheTree.search (20);
- * //buff will be 20 and readhread wil be at second leaf of the root branch
- *
- * ...
- *
- * //all user packages ()allocated memory) will be deleted at the destructor
- * //a report may be printed
- * </code>
- *
- * -Statistics:<br>
- * N-ary Tree Statistics Report:<br>
- * Unsorted 10 leafs per level.<br>
- * Total Used Time: 103.1830s<br>
- * Insert<br>
- * -was called 50000 times.<br>
- * -total used time: 0.018000s<br>
- * -Average Delay: 0.000360ms<br>
- * Clean<br>
- * -was called 1 times.<br>
- * -total used time: 0.028000s<br>
- * -Average Delay: 28.000000ms<br>
- * Search<br>
- * -was called 50000 times.<br>
- * -total used time: 103.137000s<br>
- * -Average Delay: 2.062740ms<br>
- * -Machine Specs:<br>
- * Intel Core i7-7700HQ CPU @ 2.80GHz<br>
- * 16Gb RAM<br>
- *
- * @tparam T - data type to be stored in the tree.
- */
- template <typename T>
- class NTree
- {
- public:
- /**
- * @brief Optional user callback to compare tree packages with search data.
- *
- * A barebones search callback would look like this:
- * <code>
- * bool myPackageSearchCB (T &node_package, const T *key)
- * { return (*pkgKey == *key); }
- * </code>
- *
- * And it would be used like this:
- * <code> thePackage = myList->search (&key2find, (LinkedList_Si::SearchNodeCallback)myPackageSearchCB); </code>
- *
- * @param node_package - the node package to compare.
- * @param user_data - pointer to what ever the user needs to find the correct node.
- *
- * @return Callback must return <b>true</b> to stop search when package is found or <b>false</b> to continue.
- */
- typedef bool (*SearchNodeCallback) (T &package, const void *user_data);
- private:
- //the node that builds the data structure
- struct node_s
- {
- std::size_t _depth = 0;//the distance (number of nodes) from the root
- T *tp_package = nullptr; //user data
- LinkedList_Si<node_s> *ll_leafs = nullptr; //this branch's list of leafs
- };//end node
- NTree::node_s *p_root = nullptr; //first node of the tree or null if empty
- NTree::node_s *p_readhead = nullptr; //current node being read or null if empty
- //we need to translate the NTree structure to what the user expects when making a custom search!
- const void *vp_search_key = nullptr;
- NTree::SearchNodeCallback _user_search_cb = nullptr;
- protected:
- static void cleanSubTree_r (NTree::node_s *node, bool del_packages);
- static NTree::node_s * searchSubTree_r (NTree::node_s *node, const T &data);
- static NTree::node_s * searchSubTree_r (NTree::node_s *node, const void *data, SearchNodeCallback search_cb);
- /** @brief To be used by LL to compares the contents of two leafs. */
- static bool compareLeafs_cb (NTree::node_s &node, const void *key)
- { return (*node.tp_package == *((T *)key)); }
- /** @brief We can't directly compare packages when using a custom search callback, so we need a intermediate step. */
- static bool customSearchLeafs_cb (NTree::node_s &node, const void *obj_this)
- { return ((NTree *)obj_this)->_user_search_cb (*node.tp_package, ((NTree *)obj_this)->vp_search_key); }
- public:
- /** @brief Start a new Tree. */
- // NTree ();
- ~NTree ();
- void reset ();
- void root ();
- void insert (T *package);
- T * read () const;
- /** @brief Returns the depth int he tree of the current branch under the reading head (0 if empty). */
- size_t depth () const { return (this->p_readhead ? this->p_readhead->_depth : 0); }
- /** @brief Returns the number of leafs in the branch under the reading head. */
- size_t leafs () const { return (this->p_readhead ? this->p_readhead->ll_leafs->size () : 0); }
- T * next ();
- T * down ();
- T * swap (T *new_package);
- T * search (const T &key);
- T * search (const void *data, NTree::SearchNodeCallback search_cb);
- T * searchInBranch (const T &key);
- T * searchInBranch (const void *data, NTree::SearchNodeCallback search_cb);
- void clean (bool del_packages);
- /** @brief Returns <b>true</b> if tree is empty, <b>false</b> otherwise. */
- bool empty () const { return (!this->p_root); }
- };//END NTree
- //it is truly a monstrosity to implement an entire library in a header file!
- //but using templates "requires" it....
- /*---- Methods Definition ----*/
- /**
- * @brief Delete all nodes and free all data in the Tree.
- * @warning If the tree is not empty, all the stored user packages will be freed to avoid memory leaks!
- */
- template <typename T>
- NTree<T>::~NTree ()
- {
- this->clean (true);
- }//End Destructor
- /**
- * @brief Adds a new leaf with the user data to the current branch at the readhead position.
- *
- * @remark If the tree is empty, this will be the tree root.
- * @warning The data on the new package will <b>NOT</b> be copied, only the reference saved!
- *
- * @param package - data pointer to be saved.
- *
- * @throws std::invalid_argument - if the new package is null (it makes no sense!).
- */
- template <typename T>
- void NTree<T>::insert (T *package)
- {
- //New node to put in this
- NTree::node_s *new_node = nullptr;
- //check parameters
- if (!package)
- throw std::invalid_argument (fmt::format ("{0}:{1} - new package is NULL!", __LINE__, __FILE__));
- //Save information passed to function
- new_node = new NTree<T>::node_s ();
- new_node->tp_package = package;
- new_node->ll_leafs = new LinkedList_Si<NTree::node_s> ();
- if (this->p_readhead)
- {
- new_node->_depth = this->p_readhead->_depth + 1;
- this->p_readhead->ll_leafs->insert (new_node);
- }//end if (this->p_readhead)
- else //if (!this->p_root)
- {
- this->p_root = new_node;
- this->p_readhead = this->p_root;
- }//end else if (!this->p_root)
- }//End insert ()
- /**
- * @brief Resets the reading head to the first leaf in the branch.
- */
- template <typename T>
- void NTree<T>::reset ()
- {
- if (!this->p_root)
- {
- // _debug ("INFO, tree is empty!");
- return;
- }//end if (!this->p_root)
- this->p_readhead->ll_leafs->reset ();
- }//End reset ()
- /**
- * @brief Takes the reading head back to the tree root.
- */
- template <typename T>
- void NTree<T>::root ()
- {
- this->p_readhead = this->p_root;
- if (this->p_root)
- this->p_root->ll_leafs->reset ();
- }//End root ()
- /**
- * @brief Reads the package under the read head (the current branch package).
- *
- * @return Pointer to the package under the read head, null if tree is empty.
- */
- template <typename T>
- T * NTree<T>::read () const
- {
- //check parameters
- if (!this->p_root)
- {
- // _debug ("INFO: tree is empty, nothing to read.");
- return nullptr;
- }//end if (!this->_head)
- return this->p_readhead->tp_package;
- }//End read ()
- /**
- * @brief Reads the next leaf in the branch.
- * @remark null will be returned <i>once</i> to mark the end of the list of leafs, the next call will reset to the first node.
- *
- * @return Pointer to the next package on the list of leafs, null if at the end of the list or if list is empty.
- */
- template <typename T>
- T * NTree<T>::next ()
- {
- NTree<T>::node_s *leaf = nullptr;
- //check parameters
- if (!this->p_root)
- {
- // _debug ("INFO: tree is empty...");
- return nullptr;
- }//end if (!this->_head)
- leaf = this->p_readhead->ll_leafs->next ();
- return (leaf ? leaf->tp_package : nullptr);
- }//End next ()
- /**
- * @brief Move the reading head to the current leaf being read in the branch.
- *
- * @return Pointer to the leafs package or null if there is no leaf to read.
- */
- template <typename T>
- T * NTree<T>::down ()
- {
- //check parameters
- if (!this->p_root)
- {
- // _debug ("INFO: tree is empty...");
- return nullptr;
- }//end if (!this->_head)
- else if (!this->p_readhead->ll_leafs->read ())
- {
- // _debug ("WARNING: end of leaf's list or empty, can't go down.");
- return nullptr;
- }//end else if (!)
- this->p_readhead = this->p_readhead->ll_leafs->read ();
- this->p_readhead->ll_leafs->reset ();
- return this->p_readhead->tp_package;
- }//End down ()
- /**
- * @brief Swaps the package in the branch under the read head for a new one.
- * @remark Client is responsible for freeing the returned package pointer!
- *
- * @param new_package - data pointer to be saved in place of the current one.
- *
- * @return Pointer to the old package under read head, null if tree is empty.
- *
- * @throws std::invalid_argument - if the new package is null (it makes no sense!).
- */
- template <typename T>
- T * NTree<T>::swap (T *new_package)
- {
- T *old_package = nullptr;
- //check parameters
- if (!new_package)
- throw std::invalid_argument (fmt::format ("{0}:{1} - new package is null!", __LINE__, __FILE__));
- else if (!this->p_root)
- {
- // _debug ("WARNING, Tree is empty, nothing to be done.");
- return nullptr;
- }//end else if (!this->_head)
- old_package = this->p_readhead->tp_package;
- this->p_readhead->tp_package = new_package;
- return old_package;
- }//End swap ()
- /** @brief Recursively traverse the tree (bottom/left first). */
- template <typename T>
- typename NTree<T>::node_s * NTree<T>::searchSubTree_r (NTree::node_s *node, const T &key)
- {
- NTree::node_s *aux = nullptr,
- *save = nullptr;
- aux = node->ll_leafs->reset ();
- while (aux)
- {
- save = NTree::searchSubTree_r (aux, key);
- if (save)
- return save;
- aux = node->ll_leafs->next ();
- }//end while ((aux = (NTree::node_s *)node->ll_leafs->remove ()))
- return (*node->tp_package == key ? node : nullptr);
- }//end searchSubTree_r ()
- /** @brief Recursively traverse the tree (bottom/left first) using a custom callback. */
- template <typename T>
- typename NTree<T>::node_s * NTree<T>::searchSubTree_r (NTree::node_s *node, const void *data, SearchNodeCallback search_cb)
- {
- NTree::node_s *aux = nullptr,
- *save = nullptr;
- aux = node->ll_leafs->reset ();
- while (aux)
- {
- save = NTree::searchSubTree_r (aux, data, search_cb);
- if (save)
- return save;
- aux = node->ll_leafs->next ();
- }//end while ((aux = (NTree::node_s *)node->ll_leafs->remove ()))
- return (search_cb (*node->tp_package, data) ? node : nullptr);
- }//end searchSubTree_r ()
- /**
- * @brief Search for a package in the tree using a custom callback.
- *
- * The search will begging from the read head current position till the end of the tree (remember to call reset before
- * starting the search).<br>
- * The read head will stay at the position it stopped when the method returns, so you may repeat the search from that
- * point, maybe to find more nodes with the same pattern (just remember to increment the reading head position or
- * search will be stuck at the same node).
- *
- * @param key - key to search the tree packages to (compare operator will be used).
- *
- * @return Pointer to the matched package or null if no match is found.
- *
- * @throws std::invalid_argument - if the new search callback is null (it makes no sense!).
- */
- template <typename T>
- T * NTree<T>::search (const T &key)
- {
- NTree::node_s *save = nullptr;
- //check parameters
- if (!this->p_root)
- {
- // _debug ("INFO, Tree is empty, nothing to be done.");
- return nullptr;
- }//end else if (!this->_head)
- save = NTree::searchSubTree_r (this->p_readhead, key);
- if (save)
- {
- this->p_readhead = save;
- return save->tp_package;
- }//end if (save)
- else
- {
- this->p_readhead = this->p_root;
- return nullptr;
- }//end else
- }//End search (operator==)
- /**
- * @brief Search for a package in the tree using a custom callback.
- *
- * The search will begging from the read head current position till the end of the tree (remember to call reset before
- * starting the search).<br>
- * The read head will stay at the position it stopped when the method returns, so you may repeat the search from that
- * point, maybe to find more nodes with the same pattern (just remember to increment the reading head position or
- * search will be stuck at the same node).
- *
- * @param data - client data to be passed to search the callback.
- * @param search_cb - the client search callback to compare the searched data with the packages.
- *
- * @return Pointer to the matched package or null if no match is found.
- *
- * @throws std::invalid_argument - if the new search callback is null (it makes no sense!).
- */
- template <typename T>
- T * NTree<T>::search (const void *data, SearchNodeCallback search_cb)
- {
- NTree::node_s *save = nullptr;
- //check parameters
- if (!search_cb)
- throw std::invalid_argument (fmt::format ("{0}:{1} - search callback is null!", __LINE__, __FILE__));
- else if (!this->p_root)
- {
- // _debug ("INFO, Tree is empty, nothing to be done.");
- return nullptr;
- }//end else if (!this->_head)
- save = NTree::searchSubTree_r (this->p_readhead, data, search_cb);
- if (save)
- {
- this->p_readhead = save;
- return save->tp_package;
- }//end if (save)
- else
- {
- this->p_readhead = this->p_root;
- return nullptr;
- }//end else
- }//End search (callback)
- /**
- * @brief Search the current branch leafs for a package.
- *
- * The search will always begging from the firs leaf in the branch.
- *
- * @param key - key to search the packages in the list of leafs (compare operator will be used).
- *
- * @return Pointer to the matched package or null if no match is found.
- */
- template <typename T>
- T * NTree<T>::searchInBranch (const T &key)
- {
- NTree::node_s *aux = nullptr;
- //check parameters
- if (!this->p_root)
- {
- // _debug ("INFO, Tree is empty, nothing to be done.");
- return nullptr;
- }//end else if (!this->_head)
- this->p_readhead->ll_leafs->reset ();
- aux = this->p_readhead->ll_leafs->search ((void *)&key, NTree::compareLeafs_cb);
- return (aux ? aux->tp_package : nullptr);
- }//End searchInBranch ()
- /**
- * @brief Search the current branch leafs for a package.
- *
- * The search will always begging from the firs leaf in the branch.
- *
- * @param data - client data to be passed to search the callback.
- * @param search_cb - the client search callback to compare the searched data with the packages.
- *
- * @return Pointer to the matched package or null if no match is found.
- *
- * @throws std::invalid_argument - if the new search callback is null (it makes no sense!).
- */
- template <typename T>
- T * NTree<T>::searchInBranch (const void *data, NTree::SearchNodeCallback search_cb)
- {
- NTree::node_s *aux = nullptr;
- //check parameters
- if (!search_cb)
- throw std::invalid_argument (fmt::format ("{0}:{1} - search callback is null!", __LINE__, __FILE__));
- else if (!this->p_root)
- {
- // _debug ("INFO, Tree is empty, nothing to be done.");
- return nullptr;
- }//end else if (!this->_head)
- this->vp_search_key = data;
- this->_user_search_cb = search_cb;
- this->p_readhead->ll_leafs->reset ();
- aux = this->p_readhead->ll_leafs->search ((void *)this, NTree::customSearchLeafs_cb);
- return (aux ? aux->tp_package : nullptr);
- }//End searchInBranch (callback)
- /**
- * @brief Recursively traverse the tree removing all the nodes.
- */
- template <typename T>
- void NTree<T>::cleanSubTree_r (NTree::node_s *node, bool del_packages)
- {
- NTree::node_s *aux = nullptr;
- if (!node)
- {
- // _debug ("ERROR, received an null node!");
- return;
- }//end if (!node)
- if (!node->ll_leafs)
- {
- // _debug ("ERROR, received an null list of leafs node!");
- return;
- }//end if (!node)
- //if branch has no leafs, it will not enter the while loop
- node->ll_leafs->reset ();
- while ((aux = node->ll_leafs->remove ()))
- {
- NTree::cleanSubTree_r (aux, del_packages);
- if (del_packages)
- delete (aux->tp_package);
- aux->tp_package = nullptr;
- delete (aux);
- }//end while ((aux = (NTree::node_s *)node->ll_leafs->remove ()))
- delete (node->ll_leafs);
- node->ll_leafs = nullptr;
- }//end cleanSubTree_r ()
- /**
- * @brief Remove all nodes from the tree.
- */
- template <typename T>
- void NTree<T>::clean (bool del_packages)
- {
- //check parameters
- if (!this->p_root)
- {
- // _debug ("INFO, Tree is empty, nothing to clean.");
- return;
- }//end else if (!this->_head)
- NTree::cleanSubTree_r (this->p_root, del_packages);
- if (del_packages)
- delete (this->p_root->tp_package);
- delete (this->p_root);
- this->p_root = nullptr;
- this->p_readhead = nullptr;
- }//End clean ()
- #endif //NTREE_S_HPP
|