nary_tree_simple.hpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647
  1. /* License Notice:
  2. **
  3. ** This program is free software: you can redistribute it and/or modify
  4. ** it under the terms of the GNU General Public License as published by
  5. ** the Free Software Foundation, either version 3 of the License, or
  6. ** (at your option) any later version.
  7. ** This program is distributed in the hope that it will be useful,
  8. ** but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. ** GNU General Public License for more details.
  11. ** You should have received a copy of the GNU General Public License
  12. ** along with this program. If not, see <https://www.gnu.org/licenses/>.
  13. */
  14. /**
  15. * @file linked_list_simple.hpp
  16. * @author TooOld2Rock'nRoll
  17. * @date jan/2025
  18. * @version 0.0.2
  19. *
  20. * @attention "make CFLAGS:=-DUNIT_TESTING" to run unit testing on the library.
  21. *
  22. * @see Cormen, Thomas H. (...) - Introduction to Algorithms (3ºed)
  23. * @see https://cpputest.github.io/manual.html
  24. *
  25. * @todo To be truly simple, we should have a fixed number of leafs for the branches (with a array of leafs).
  26. * @todo maybe add a sort function!
  27. *
  28. * @brief Header file for the <i>Simple</i> version N-ary Tree data structure.
  29. */
  30. #ifndef NTREE_S_HPP
  31. #define NTREE_S_HPP
  32. /*---- Includes ----*/
  33. #include "linked_list_simple.hpp"
  34. #ifdef UNIT_TESTING
  35. #include <CppUTest/CommandLineTestRunner.h>
  36. #include <CppUTest/MemoryLeakDetectorNewMacros.h>
  37. #endif
  38. /*---- Defines ----*/
  39. // #define NTSI_ENABLE_BENCHMARK
  40. /*---- Enumerations ----*/
  41. /*---- Typedefs ----*/
  42. /*---- Class Declaration ----*/
  43. /**
  44. * @brief Simple N-ary Tree data structure.
  45. * @see https://en.wikipedia.org/wiki/M-ary_tree
  46. *
  47. * A N-ary Tree (or K Tree) is a dynamic data structure that is (usually) ordered and (traditionally) used to store and
  48. * validade chains of data, like strings from a dictionary. Each node would represent a letter and traversing the tree
  49. * successfully till a leaf would confirm the dictionary world is valid.<br>
  50. *
  51. * In practice, nothing more than a list of lists...<br>
  52. *
  53. * This algorithm is simples in the sense that it covers the most basic requirements to be considered a functional n-ary
  54. * tree, it is <b>not</b> thread safe (traversing the list in parallel would break the reading head logic).<br>
  55. * This algorithm is also fast by only saving references to user data removing a good overhead of consistency checks,
  56. * but this is not safe and difficult to debug in case of errors. Consider using the Safe version if such safeguards
  57. * are necessary in your project.<br>
  58. *
  59. * The first call to insert() will create the root node, subsequent inserts will add leafs to the branch currently under
  60. * the reading head.<br>
  61. * The next() function moves the read head forward into the list of leafs.<br>
  62. * The down() function moves the reading head to the selected leaf, moving the reading head to a different branch.<br>
  63. * The other functions are just helpers that make some jobs easier.<br>
  64. * 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>
  65. *
  66. * Example:
  67. * <code>
  68. * int *buff = nullptr;
  69. * NTree<int> TheTree;
  70. *
  71. * //create root as 10
  72. * TheTree.insert (new int (10));
  73. *
  74. * //leaf is 20
  75. * TheTree.insert (new int (20));
  76. * //leaf is 30
  77. * TheTree.insert (new int (30));
  78. *
  79. * TheTree.next (); //read head points to first leaf (30)
  80. * TheTree.next (); //read head points to second leaf (20)
  81. * TheTree.down (); //we are at branch 20
  82. *
  83. * //leaf is 400
  84. * TheTree.insert (new int (400));
  85. *
  86. * buff = TheTree.search (20);
  87. * //buff will be 20 and readhread wil be at second leaf of the root branch
  88. *
  89. * ...
  90. *
  91. * //all user packages ()allocated memory) will be deleted at the destructor
  92. * //a report may be printed
  93. * </code>
  94. *
  95. * -Statistics:<br>
  96. * N-ary Tree Statistics Report:<br>
  97. * Unsorted 10 leafs per level.<br>
  98. * Total Used Time: 103.1830s<br>
  99. * Insert<br>
  100. * -was called 50000 times.<br>
  101. * -total used time: 0.018000s<br>
  102. * -Average Delay: 0.000360ms<br>
  103. * Clean<br>
  104. * -was called 1 times.<br>
  105. * -total used time: 0.028000s<br>
  106. * -Average Delay: 28.000000ms<br>
  107. * Search<br>
  108. * -was called 50000 times.<br>
  109. * -total used time: 103.137000s<br>
  110. * -Average Delay: 2.062740ms<br>
  111. * -Machine Specs:<br>
  112. * Intel Core i7-7700HQ CPU @ 2.80GHz<br>
  113. * 16Gb RAM<br>
  114. *
  115. * @tparam T - data type to be stored in the tree.
  116. */
  117. template <typename T>
  118. class NTree
  119. {
  120. public:
  121. /**
  122. * @brief Optional user callback to compare tree packages with search data.
  123. *
  124. * A barebones search callback would look like this:
  125. * <code>
  126. * bool myPackageSearchCB (T &node_package, const T *key)
  127. * { return (*pkgKey == *key); }
  128. * </code>
  129. *
  130. * And it would be used like this:
  131. * <code> thePackage = myList->search (&key2find, (LinkedList_Si::SearchNodeCallback)myPackageSearchCB); </code>
  132. *
  133. * @param node_package - the node package to compare.
  134. * @param user_data - pointer to what ever the user needs to find the correct node.
  135. *
  136. * @return Callback must return <b>true</b> to stop search when package is found or <b>false</b> to continue.
  137. */
  138. typedef bool (*SearchNodeCallback) (T &package, const void *user_data);
  139. private:
  140. //the node that builds the data structure
  141. struct node_s
  142. {
  143. std::size_t _depth = 0;//the distance (number of nodes) from the root
  144. T *tp_package = nullptr; //user data
  145. LinkedList_Si<node_s> *ll_leafs = nullptr; //this branch's list of leafs
  146. };//end node
  147. NTree::node_s *p_root = nullptr; //first node of the tree or null if empty
  148. NTree::node_s *p_readhead = nullptr; //current node being read or null if empty
  149. //we need to translate the NTree structure to what the user expects when making a custom search!
  150. const void *vp_search_key = nullptr;
  151. NTree::SearchNodeCallback _user_search_cb = nullptr;
  152. protected:
  153. static void cleanSubTree_r (NTree::node_s *node, bool del_packages);
  154. static NTree::node_s * searchSubTree_r (NTree::node_s *node, const T &data);
  155. static NTree::node_s * searchSubTree_r (NTree::node_s *node, const void *data, SearchNodeCallback search_cb);
  156. /** @brief To be used by LL to compares the contents of two leafs. */
  157. static bool compareLeafs_cb (NTree::node_s &node, const void *key)
  158. { return (*node.tp_package == *((T *)key)); }
  159. /** @brief We can't directly compare packages when using a custom search callback, so we need a intermediate step. */
  160. static bool customSearchLeafs_cb (NTree::node_s &node, const void *obj_this)
  161. { return ((NTree *)obj_this)->_user_search_cb (*node.tp_package, ((NTree *)obj_this)->vp_search_key); }
  162. public:
  163. /** @brief Start a new Tree. */
  164. // NTree ();
  165. ~NTree ();
  166. void reset ();
  167. void root ();
  168. void insert (T *package);
  169. T * read () const;
  170. /** @brief Returns the depth int he tree of the current branch under the reading head (0 if empty). */
  171. size_t depth () const { return (this->p_readhead ? this->p_readhead->_depth : 0); }
  172. /** @brief Returns the number of leafs in the branch under the reading head. */
  173. size_t leafs () const { return (this->p_readhead ? this->p_readhead->ll_leafs->size () : 0); }
  174. T * next ();
  175. T * down ();
  176. T * swap (T *new_package);
  177. T * search (const T &key);
  178. T * search (const void *data, NTree::SearchNodeCallback search_cb);
  179. T * searchInBranch (const T &key);
  180. T * searchInBranch (const void *data, NTree::SearchNodeCallback search_cb);
  181. void clean (bool del_packages);
  182. /** @brief Returns <b>true</b> if tree is empty, <b>false</b> otherwise. */
  183. bool empty () const { return (!this->p_root); }
  184. };//END NTree
  185. //it is truly a monstrosity to implement an entire library in a header file!
  186. //but using templates "requires" it....
  187. /*---- Methods Definition ----*/
  188. /**
  189. * @brief Delete all nodes and free all data in the Tree.
  190. * @warning If the tree is not empty, all the stored user packages will be freed to avoid memory leaks!
  191. */
  192. template <typename T>
  193. NTree<T>::~NTree ()
  194. {
  195. this->clean (true);
  196. }//End Destructor
  197. /**
  198. * @brief Adds a new leaf with the user data to the current branch at the readhead position.
  199. *
  200. * @remark If the tree is empty, this will be the tree root.
  201. * @warning The data on the new package will <b>NOT</b> be copied, only the reference saved!
  202. *
  203. * @param package - data pointer to be saved.
  204. *
  205. * @throws std::invalid_argument - if the new package is null (it makes no sense!).
  206. */
  207. template <typename T>
  208. void NTree<T>::insert (T *package)
  209. {
  210. //New node to put in this
  211. NTree::node_s *new_node = nullptr;
  212. //check parameters
  213. if (!package)
  214. throw std::invalid_argument (fmt::format ("{0}:{1} - new package is NULL!", __LINE__, __FILE__));
  215. //Save information passed to function
  216. new_node = new NTree<T>::node_s ();
  217. new_node->tp_package = package;
  218. new_node->ll_leafs = new LinkedList_Si<NTree::node_s> ();
  219. if (this->p_readhead)
  220. {
  221. new_node->_depth = this->p_readhead->_depth + 1;
  222. this->p_readhead->ll_leafs->insert (new_node);
  223. }//end if (this->p_readhead)
  224. else //if (!this->p_root)
  225. {
  226. this->p_root = new_node;
  227. this->p_readhead = this->p_root;
  228. }//end else if (!this->p_root)
  229. }//End insert ()
  230. /**
  231. * @brief Resets the reading head to the first leaf in the branch.
  232. */
  233. template <typename T>
  234. void NTree<T>::reset ()
  235. {
  236. if (!this->p_root)
  237. {
  238. // _debug ("INFO, tree is empty!");
  239. return;
  240. }//end if (!this->p_root)
  241. this->p_readhead->ll_leafs->reset ();
  242. }//End reset ()
  243. /**
  244. * @brief Takes the reading head back to the tree root.
  245. */
  246. template <typename T>
  247. void NTree<T>::root ()
  248. {
  249. this->p_readhead = this->p_root;
  250. if (this->p_root)
  251. this->p_root->ll_leafs->reset ();
  252. }//End root ()
  253. /**
  254. * @brief Reads the package under the read head (the current branch package).
  255. *
  256. * @return Pointer to the package under the read head, null if tree is empty.
  257. */
  258. template <typename T>
  259. T * NTree<T>::read () const
  260. {
  261. //check parameters
  262. if (!this->p_root)
  263. {
  264. // _debug ("INFO: tree is empty, nothing to read.");
  265. return nullptr;
  266. }//end if (!this->_head)
  267. return this->p_readhead->tp_package;
  268. }//End read ()
  269. /**
  270. * @brief Reads the next leaf in the branch.
  271. * @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.
  272. *
  273. * @return Pointer to the next package on the list of leafs, null if at the end of the list or if list is empty.
  274. */
  275. template <typename T>
  276. T * NTree<T>::next ()
  277. {
  278. NTree<T>::node_s *leaf = nullptr;
  279. //check parameters
  280. if (!this->p_root)
  281. {
  282. // _debug ("INFO: tree is empty...");
  283. return nullptr;
  284. }//end if (!this->_head)
  285. leaf = this->p_readhead->ll_leafs->next ();
  286. return (leaf ? leaf->tp_package : nullptr);
  287. }//End next ()
  288. /**
  289. * @brief Move the reading head to the current leaf being read in the branch.
  290. *
  291. * @return Pointer to the leafs package or null if there is no leaf to read.
  292. */
  293. template <typename T>
  294. T * NTree<T>::down ()
  295. {
  296. //check parameters
  297. if (!this->p_root)
  298. {
  299. // _debug ("INFO: tree is empty...");
  300. return nullptr;
  301. }//end if (!this->_head)
  302. else if (!this->p_readhead->ll_leafs->read ())
  303. {
  304. // _debug ("WARNING: end of leaf's list or empty, can't go down.");
  305. return nullptr;
  306. }//end else if (!)
  307. this->p_readhead = this->p_readhead->ll_leafs->read ();
  308. this->p_readhead->ll_leafs->reset ();
  309. return this->p_readhead->tp_package;
  310. }//End down ()
  311. /**
  312. * @brief Swaps the package in the branch under the read head for a new one.
  313. * @remark Client is responsible for freeing the returned package pointer!
  314. *
  315. * @param new_package - data pointer to be saved in place of the current one.
  316. *
  317. * @return Pointer to the old package under read head, null if tree is empty.
  318. *
  319. * @throws std::invalid_argument - if the new package is null (it makes no sense!).
  320. */
  321. template <typename T>
  322. T * NTree<T>::swap (T *new_package)
  323. {
  324. T *old_package = nullptr;
  325. //check parameters
  326. if (!new_package)
  327. throw std::invalid_argument (fmt::format ("{0}:{1} - new package is null!", __LINE__, __FILE__));
  328. else if (!this->p_root)
  329. {
  330. // _debug ("WARNING, Tree is empty, nothing to be done.");
  331. return nullptr;
  332. }//end else if (!this->_head)
  333. old_package = this->p_readhead->tp_package;
  334. this->p_readhead->tp_package = new_package;
  335. return old_package;
  336. }//End swap ()
  337. /** @brief Recursively traverse the tree (bottom/left first). */
  338. template <typename T>
  339. typename NTree<T>::node_s * NTree<T>::searchSubTree_r (NTree::node_s *node, const T &key)
  340. {
  341. NTree::node_s *aux = nullptr,
  342. *save = nullptr;
  343. aux = node->ll_leafs->reset ();
  344. while (aux)
  345. {
  346. save = NTree::searchSubTree_r (aux, key);
  347. if (save)
  348. return save;
  349. aux = node->ll_leafs->next ();
  350. }//end while ((aux = (NTree::node_s *)node->ll_leafs->remove ()))
  351. return (*node->tp_package == key ? node : nullptr);
  352. }//end searchSubTree_r ()
  353. /** @brief Recursively traverse the tree (bottom/left first) using a custom callback. */
  354. template <typename T>
  355. typename NTree<T>::node_s * NTree<T>::searchSubTree_r (NTree::node_s *node, const void *data, SearchNodeCallback search_cb)
  356. {
  357. NTree::node_s *aux = nullptr,
  358. *save = nullptr;
  359. aux = node->ll_leafs->reset ();
  360. while (aux)
  361. {
  362. save = NTree::searchSubTree_r (aux, data, search_cb);
  363. if (save)
  364. return save;
  365. aux = node->ll_leafs->next ();
  366. }//end while ((aux = (NTree::node_s *)node->ll_leafs->remove ()))
  367. return (search_cb (*node->tp_package, data) ? node : nullptr);
  368. }//end searchSubTree_r ()
  369. /**
  370. * @brief Search for a package in the tree using a custom callback.
  371. *
  372. * The search will begging from the read head current position till the end of the tree (remember to call reset before
  373. * starting the search).<br>
  374. * The read head will stay at the position it stopped when the method returns, so you may repeat the search from that
  375. * point, maybe to find more nodes with the same pattern (just remember to increment the reading head position or
  376. * search will be stuck at the same node).
  377. *
  378. * @param key - key to search the tree packages to (compare operator will be used).
  379. *
  380. * @return Pointer to the matched package or null if no match is found.
  381. *
  382. * @throws std::invalid_argument - if the new search callback is null (it makes no sense!).
  383. */
  384. template <typename T>
  385. T * NTree<T>::search (const T &key)
  386. {
  387. NTree::node_s *save = nullptr;
  388. //check parameters
  389. if (!this->p_root)
  390. {
  391. // _debug ("INFO, Tree is empty, nothing to be done.");
  392. return nullptr;
  393. }//end else if (!this->_head)
  394. save = NTree::searchSubTree_r (this->p_readhead, key);
  395. if (save)
  396. {
  397. this->p_readhead = save;
  398. return save->tp_package;
  399. }//end if (save)
  400. else
  401. {
  402. this->p_readhead = this->p_root;
  403. return nullptr;
  404. }//end else
  405. }//End search (operator==)
  406. /**
  407. * @brief Search for a package in the tree using a custom callback.
  408. *
  409. * The search will begging from the read head current position till the end of the tree (remember to call reset before
  410. * starting the search).<br>
  411. * The read head will stay at the position it stopped when the method returns, so you may repeat the search from that
  412. * point, maybe to find more nodes with the same pattern (just remember to increment the reading head position or
  413. * search will be stuck at the same node).
  414. *
  415. * @param data - client data to be passed to search the callback.
  416. * @param search_cb - the client search callback to compare the searched data with the packages.
  417. *
  418. * @return Pointer to the matched package or null if no match is found.
  419. *
  420. * @throws std::invalid_argument - if the new search callback is null (it makes no sense!).
  421. */
  422. template <typename T>
  423. T * NTree<T>::search (const void *data, SearchNodeCallback search_cb)
  424. {
  425. NTree::node_s *save = nullptr;
  426. //check parameters
  427. if (!search_cb)
  428. throw std::invalid_argument (fmt::format ("{0}:{1} - search callback is null!", __LINE__, __FILE__));
  429. else if (!this->p_root)
  430. {
  431. // _debug ("INFO, Tree is empty, nothing to be done.");
  432. return nullptr;
  433. }//end else if (!this->_head)
  434. save = NTree::searchSubTree_r (this->p_readhead, data, search_cb);
  435. if (save)
  436. {
  437. this->p_readhead = save;
  438. return save->tp_package;
  439. }//end if (save)
  440. else
  441. {
  442. this->p_readhead = this->p_root;
  443. return nullptr;
  444. }//end else
  445. }//End search (callback)
  446. /**
  447. * @brief Search the current branch leafs for a package.
  448. *
  449. * The search will always begging from the firs leaf in the branch.
  450. *
  451. * @param key - key to search the packages in the list of leafs (compare operator will be used).
  452. *
  453. * @return Pointer to the matched package or null if no match is found.
  454. */
  455. template <typename T>
  456. T * NTree<T>::searchInBranch (const T &key)
  457. {
  458. NTree::node_s *aux = nullptr;
  459. //check parameters
  460. if (!this->p_root)
  461. {
  462. // _debug ("INFO, Tree is empty, nothing to be done.");
  463. return nullptr;
  464. }//end else if (!this->_head)
  465. this->p_readhead->ll_leafs->reset ();
  466. aux = this->p_readhead->ll_leafs->search ((void *)&key, NTree::compareLeafs_cb);
  467. return (aux ? aux->tp_package : nullptr);
  468. }//End searchInBranch ()
  469. /**
  470. * @brief Search the current branch leafs for a package.
  471. *
  472. * The search will always begging from the firs leaf in the branch.
  473. *
  474. * @param data - client data to be passed to search the callback.
  475. * @param search_cb - the client search callback to compare the searched data with the packages.
  476. *
  477. * @return Pointer to the matched package or null if no match is found.
  478. *
  479. * @throws std::invalid_argument - if the new search callback is null (it makes no sense!).
  480. */
  481. template <typename T>
  482. T * NTree<T>::searchInBranch (const void *data, NTree::SearchNodeCallback search_cb)
  483. {
  484. NTree::node_s *aux = nullptr;
  485. //check parameters
  486. if (!search_cb)
  487. throw std::invalid_argument (fmt::format ("{0}:{1} - search callback is null!", __LINE__, __FILE__));
  488. else if (!this->p_root)
  489. {
  490. // _debug ("INFO, Tree is empty, nothing to be done.");
  491. return nullptr;
  492. }//end else if (!this->_head)
  493. this->vp_search_key = data;
  494. this->_user_search_cb = search_cb;
  495. this->p_readhead->ll_leafs->reset ();
  496. aux = this->p_readhead->ll_leafs->search ((void *)this, NTree::customSearchLeafs_cb);
  497. return (aux ? aux->tp_package : nullptr);
  498. }//End searchInBranch (callback)
  499. /**
  500. * @brief Recursively traverse the tree removing all the nodes.
  501. */
  502. template <typename T>
  503. void NTree<T>::cleanSubTree_r (NTree::node_s *node, bool del_packages)
  504. {
  505. NTree::node_s *aux = nullptr;
  506. if (!node)
  507. {
  508. // _debug ("ERROR, received an null node!");
  509. return;
  510. }//end if (!node)
  511. if (!node->ll_leafs)
  512. {
  513. // _debug ("ERROR, received an null list of leafs node!");
  514. return;
  515. }//end if (!node)
  516. //if branch has no leafs, it will not enter the while loop
  517. node->ll_leafs->reset ();
  518. while ((aux = node->ll_leafs->remove ()))
  519. {
  520. NTree::cleanSubTree_r (aux, del_packages);
  521. if (del_packages)
  522. delete (aux->tp_package);
  523. aux->tp_package = nullptr;
  524. delete (aux);
  525. }//end while ((aux = (NTree::node_s *)node->ll_leafs->remove ()))
  526. delete (node->ll_leafs);
  527. node->ll_leafs = nullptr;
  528. }//end cleanSubTree_r ()
  529. /**
  530. * @brief Remove all nodes from the tree.
  531. */
  532. template <typename T>
  533. void NTree<T>::clean (bool del_packages)
  534. {
  535. //check parameters
  536. if (!this->p_root)
  537. {
  538. // _debug ("INFO, Tree is empty, nothing to clean.");
  539. return;
  540. }//end else if (!this->_head)
  541. NTree::cleanSubTree_r (this->p_root, del_packages);
  542. if (del_packages)
  543. delete (this->p_root->tp_package);
  544. delete (this->p_root);
  545. this->p_root = nullptr;
  546. this->p_readhead = nullptr;
  547. }//End clean ()
  548. #endif //NTREE_S_HPP