LCOV - code coverage report
Current view: top level - tommyds - tommylist.h (source / functions) Hit Total Coverage
Test: lcov.info Lines: 72 72 100.0 %
Date: 2026-04-29 15:04:44 Functions: 12 12 100.0 %

          Line data    Source code
       1             : // SPDX-License-Identifier: BSD-2-Clause
       2             : // Copyright (C) 2010 Andrea Mazzoleni
       3             : 
       4             : /** \file
       5             :  * Double linked list for collisions into hashtables.
       6             :  *
       7             :  * This list is a **doubly** linked list mainly targeted for handling collisions
       8             :  * into an hashtable, but **usable** also as a generic list.
       9             :  *
      10             :  * The main feature of this list is to require only one pointer to represent the
      11             :  * list, compared to a classic implementation requiring a head **and** a tail pointers.
      12             :  * This reduces the memory usage in hashtables.
      13             :  *
      14             :  * Another feature is to support the insertion at the end of the list. This **allows** to store
      15             :  * collisions in a stable order. Where for stable order we mean that equal elements keep
      16             :  * their insertion order.
      17             :  *
      18             :  * To initialize the list, you have to call tommy_list_init(), or to simply assign
      19             :  * to it NULL, as an empty list is represented by the NULL value.
      20             :  *
      21             :  * \code
      22             :  * tommy_list list;
      23             :  *
      24             :  * tommy_list_init(&list); // initializes the list
      25             :  * \endcode
      26             :  *
      27             :  * To insert elements in the list you have to call tommy_list_insert_tail()
      28             :  * or tommy_list_insert_head() for each element.
      29             :  * In the insertion call you have to specify the address of the node and the
      30             :  * address of the object.
      31             :  * The address of the object is used to initialize the tommy_node::data field
      32             :  * of the node.
      33             :  *
      34             :  * \code
      35             :  * struct object {
      36             :  *     int value;
      37             :  *     // other fields
      38             :  *     tommy_node node;
      39             :  * };
      40             :  *
      41             :  * struct object* obj = malloc(sizeof(struct object)); // creates the object
      42             :  *
      43             :  * obj->value = ...; // initializes the object
      44             :  *
      45             :  * tommy_list_insert_tail(&list, &obj->node, obj); // inserts the object
      46             :  * \endcode
      47             :  *
      48             :  * To iterate over all the elements in the list you have to call
      49             :  * tommy_list_head() to get the head of the list and follow the
      50             :  * tommy_node::next pointer until NULL.
      51             :  *
      52             :  * \code
      53             :  * tommy_node* i = tommy_list_head(&list);
      54             :  * while (i) {
      55             :  *     struct object* obj = i->data; // gets the object pointer
      56             :  *
      57             :  *     printf("%d\n", obj->value); // process the object
      58             :  *
      59             :  *     i = i->next; // go to the next element
      60             :  * }
      61             :  * \endcode
      62             :  *
      63             :  * To destroy the list you have to remove all the elements,
      64             :  * as the list is completely inplace and it doesn't allocate memory.
      65             :  * This can be done with the tommy_list_foreach() function.
      66             :  *
      67             :  * \code
      68             :  * // deallocates all the objects iterating the list
      69             :  * tommy_list_foreach(&list, free);
      70             :  * \endcode
      71             :  */
      72             : 
      73             : #ifndef __TOMMYLIST_H
      74             : #define __TOMMYLIST_H
      75             : 
      76             : #include "tommytypes.h"
      77             : 
      78             : /******************************************************************************/
      79             : /* list */
      80             : 
      81             : /**
      82             :  * Doubly linked list type.
      83             :  */
      84             : typedef tommy_node* tommy_list;
      85             : 
      86             : /**
      87             :  * Initializes the list.
      88             :  * The list is completely inplace, so it doesn't need to be deinitialized.
      89             :  */
      90       23403 : tommy_inline void tommy_list_init(tommy_list* list)
      91             : {
      92       23403 :         *list = 0;
      93       23403 : }
      94             : 
      95             : /**
      96             :  * Gets the head of the list.
      97             :  * \return The head node. For empty lists 0 is returned.
      98             :  */
      99    28869531 : tommy_inline tommy_node* tommy_list_head(tommy_list* list)
     100             : {
     101    28869531 :         return *list;
     102             : }
     103             : 
     104             : /**
     105             :  * Gets the tail of the list.
     106             :  * \return The tail node. For empty lists 0 is returned.
     107             :  */
     108           2 : tommy_inline tommy_node* tommy_list_tail(tommy_list* list)
     109             : {
     110           2 :         tommy_node* head = tommy_list_head(list);
     111             : 
     112           2 :         if (!head)
     113           1 :                 return 0;
     114             : 
     115           1 :         return head->prev;
     116             : }
     117             : 
     118             : /** \internal
     119             :  * Creates a new list with a single element.
     120             :  * \param list The list to initialize.
     121             :  * \param node The node to insert.
     122             :  */
     123    25182043 : tommy_inline void tommy_list_insert_first(tommy_list* list, tommy_node* node)
     124             : {
     125             :         /* one element "circular" prev list */
     126    25182043 :         node->prev = node;
     127             : 
     128             :         /* one element "0 terminated" next list */
     129    25182043 :         node->next = 0;
     130             : 
     131    25182043 :         *list = node;
     132    25182043 : }
     133             : 
     134             : /** \internal
     135             :  * Inserts an element at the head of a non-empty list.
     136             :  * The element is inserted at the head of the list. The list cannot be empty.
     137             :  * \param list The list. The list cannot be empty.
     138             :  * \param node The node to insert.
     139             :  */
     140             : tommy_inline void tommy_list_insert_head_not_empty(tommy_list* list, tommy_node* node)
     141             : {
     142             :         tommy_node* head = tommy_list_head(list);
     143             : 
     144             :         /* insert in the "circular" prev list */
     145             :         node->prev = head->prev;
     146             :         head->prev = node;
     147             : 
     148             :         /* insert in the "0 terminated" next list */
     149             :         node->next = head;
     150             : 
     151             :         *list = node;
     152             : }
     153             : 
     154             : /** \internal
     155             :  * Inserts an element at the tail of a non-empty list.
     156             :  * The element is inserted at the tail of the list. The list cannot be empty.
     157             :  * \param head The node at the list head. It cannot be 0.
     158             :  * \param node The node to insert.
     159             :  */
     160    12207710 : tommy_inline void tommy_list_insert_tail_not_empty(tommy_node* head, tommy_node* node)
     161             : {
     162             :         /* insert in the "circular" prev list */
     163    12207710 :         node->prev = head->prev;
     164    12207710 :         head->prev = node;
     165             : 
     166             :         /* insert in the "0 terminated" next list */
     167    12207710 :         node->next = 0;
     168    12207710 :         node->prev->next = node;
     169    12207710 : }
     170             : 
     171             : /**
     172             :  * Inserts an element at the head of a list.
     173             :  * \param list The list.
     174             :  * \param node The node to insert.
     175             :  * \param data The object containing the node. It's used to set the tommy_node::data field of the node.
     176             :  */
     177             : tommy_inline void tommy_list_insert_head(tommy_list* list, tommy_node* node, void* data)
     178             : {
     179             :         tommy_node* head = tommy_list_head(list);
     180             : 
     181             :         if (head)
     182             :                 tommy_list_insert_head_not_empty(list, node);
     183             :         else
     184             :                 tommy_list_insert_first(list, node);
     185             : 
     186             :         node->data = data;
     187             : }
     188             : 
     189             : /**
     190             :  * Inserts an element at the tail of a list.
     191             :  * \param list The list.
     192             :  * \param node The node to insert.
     193             :  * \param data The object containing the node. It's used to set the tommy_node::data field of the node.
     194             :  */
     195    20056001 : tommy_inline void tommy_list_insert_tail(tommy_list* list, tommy_node* node, void* data)
     196             : {
     197    20056001 :         tommy_node* head = tommy_list_head(list);
     198             : 
     199    20056001 :         if (head)
     200    10139409 :                 tommy_list_insert_tail_not_empty(head, node);
     201             :         else
     202     9916592 :                 tommy_list_insert_first(list, node);
     203             : 
     204    20056001 :         node->data = data;
     205    20056001 : }
     206             : 
     207             : /**
     208             :  * Inserts an element before the specified reference node.
     209             :  * \param list The list.
     210             :  * \param reference The reference node. The new node will be inserted before this node. The reference node must be in the list.
     211             :  * \param node The node to insert.
     212             :  * \param data The object containing the node. It's used to set the tommy_node::data field of the node.
     213             :  */
     214             : tommy_inline void tommy_list_insert_before(tommy_list* list, tommy_node* reference, tommy_node* node, void* data)
     215             : {
     216             :         tommy_node* head = tommy_list_head(list);
     217             : 
     218             :         /* if inserting before the head, update the list pointer */
     219             :         if (head == reference) {
     220             :                 tommy_list_insert_head_not_empty(list, node);
     221             :         } else {
     222             :                 /* insert in the "circular" prev list */
     223             :                 node->prev = reference->prev;
     224             :                 reference->prev = node;
     225             : 
     226             :                 /* insert in the "0 terminated" next list */
     227             :                 node->next = reference;
     228             :                 node->prev->next = node;
     229             :         }
     230             : 
     231             :         node->data = data;
     232             : }
     233             : 
     234             : /**
     235             :  * Inserts an element after the specified reference node.
     236             :  * \param list The list.
     237             :  * \param reference The reference node. The new node will be inserted after this node. The reference node must be in the list.
     238             :  * \param node The node to insert.
     239             :  * \param data The object containing the node. It's used to set the tommy_node::data field of the node.
     240             :  */
     241             : tommy_inline void tommy_list_insert_after(tommy_list* list, tommy_node* reference, tommy_node* node, void* data)
     242             : {
     243             :         tommy_node* head = tommy_list_head(list);
     244             : 
     245             :         /* if inserting after the tail (reference->next == 0), handle specially */
     246             :         if (reference->next == 0) {
     247             :                 tommy_list_insert_tail_not_empty(head, node);
     248             :         } else {
     249             :                 /* insert in the "circular" prev list */
     250             :                 node->prev = reference;
     251             :                 reference->next->prev = node;
     252             : 
     253             :                 /* insert in the "0 terminated" next list */
     254             :                 node->next = reference->next;
     255             :                 reference->next = node;
     256             :         }
     257             : 
     258             :         node->data = data;
     259             : }
     260             : 
     261             : /**
     262             :  * Removes an element from the list.
     263             :  * You must already have the address of the element to remove.
     264             :  * \note The node content is left unchanged, including the tommy_node::next
     265             :  * and tommy_node::prev fields that still contain pointers **in** the list.
     266             :  * \param list The list.
     267             :  * \param node The node to remove. The node must be in the list.
     268             :  * \return The tommy_node::data field of the node removed.
     269             :  */
     270      365000 : tommy_inline void* tommy_list_remove_existing(tommy_list* list, tommy_node* node)
     271             : {
     272      365000 :         tommy_node* head = tommy_list_head(list);
     273             : 
     274             :         /* remove from the "circular" prev list */
     275      365000 :         if (node->next)
     276      123294 :                 node->next->prev = node->prev;
     277             :         else
     278      241706 :                 head->prev = node->prev; /* the last */
     279             : 
     280             :         /* remove from the "0 terminated" next list */
     281      365000 :         if (head == node)
     282      260765 :                 *list = node->next; /* the new head, in case 0 */
     283             :         else
     284      104235 :                 node->prev->next = node->next;
     285             : 
     286      365000 :         return node->data;
     287             : }
     288             : 
     289             : /**
     290             :  * Concats two lists.
     291             :  * The second list is concatenated at the first list.
     292             :  * \param first The first list.
     293             :  * \param second The second list. After this call the list content is undefined,
     294             :  * and you should not use it anymore.
     295             :  */
     296       79280 : tommy_inline void tommy_list_concat(tommy_list* first, tommy_list* second)
     297             : {
     298             :         tommy_node* first_head;
     299             :         tommy_node* first_tail;
     300             :         tommy_node* second_head;
     301             : 
     302             :         /* if the second is empty, nothing to do */
     303       79280 :         second_head = tommy_list_head(second);
     304       79280 :         if (second_head == 0)
     305       70191 :                 return;
     306             : 
     307             :         /* if the first is empty, copy the second */
     308        9089 :         first_head = tommy_list_head(first);
     309        9089 :         if (first_head == 0) {
     310        8045 :                 *first = *second;
     311        8045 :                 return;
     312             :         }
     313             : 
     314             :         /* tail of the first list */
     315        1044 :         first_tail = first_head->prev;
     316             : 
     317             :         /* set the "circular" prev list */
     318        1044 :         first_head->prev = second_head->prev;
     319        1044 :         second_head->prev = first_tail;
     320             : 
     321             :         /* set the "0 terminated" next list */
     322        1044 :         first_tail->next = second_head;
     323             : }
     324             : 
     325             : /**
     326             :  * Sorts a list.
     327             :  * It's a stable merge sort with O(N*log(N)) worst complexity.
     328             :  * It's faster on degenerated cases like partially ordered lists.
     329             :  * \param cmp Compare function called with two elements.
     330             :  * The function should return <0 if the first element is less than the second, ==0 if equal, and >0 if greater.
     331             :  */
     332             : TOMMY_API void tommy_list_sort(tommy_list* list, tommy_compare_func* cmp);
     333             : 
     334             : /**
     335             :  * Checks if empty.
     336             :  * \return If the list is empty.
     337             :  */
     338        9685 : tommy_inline tommy_bool_t tommy_list_empty(tommy_list* list)
     339             : {
     340        9685 :         return tommy_list_head(list) == 0;
     341             : }
     342             : 
     343             : /**
     344             :  * Gets the number of elements.
     345             :  * \note This operation is O(n).
     346             :  */
     347         491 : tommy_inline tommy_size_t tommy_list_count(tommy_list* list)
     348             : {
     349         491 :         tommy_size_t count = 0;
     350         491 :         tommy_node* i = tommy_list_head(list);
     351             : 
     352       72005 :         while (i) {
     353       71514 :                 ++count;
     354       71514 :                 i = i->next;
     355             :         }
     356             : 
     357         491 :         return count;
     358             : }
     359             : 
     360             : /**
     361             :  * Calls the specified function for each element in the list.
     362             :  *
     363             :  * You cannot add or remove elements from the inside of the callback,
     364             :  * but can use it to deallocate them.
     365             :  *
     366             :  * \code
     367             :  * tommy_list list;
     368             :  *
     369             :  * // initializes the list
     370             :  * tommy_list_init(&list);
     371             :  *
     372             :  * ...
     373             :  *
     374             :  * // creates an object
     375             :  * struct object* obj = malloc(sizeof(struct object));
     376             :  *
     377             :  * ...
     378             :  *
     379             :  * // insert it in the list
     380             :  * tommy_list_insert_tail(&list, &obj->node, obj);
     381             :  *
     382             :  * ...
     383             :  *
     384             :  * // deallocates all the objects iterating the list
     385             :  * tommy_list_foreach(&list, free);
     386             :  * \endcode
     387             :  */
     388       18348 : tommy_inline void tommy_list_foreach(tommy_list* list, tommy_foreach_func* func)
     389             : {
     390       18348 :         tommy_node* node = tommy_list_head(list);
     391             : 
     392     4146369 :         while (node) {
     393     4128021 :                 void* data = node->data;
     394     4128021 :                 node = node->next;
     395     4128021 :                 func(data);
     396             :         }
     397       18348 : }
     398             : 
     399             : /**
     400             :  * Calls the specified function with an argument for each element in the list.
     401             :  */
     402           5 : tommy_inline void tommy_list_foreach_arg(tommy_list* list, tommy_foreach_arg_func* func, void* arg)
     403             : {
     404           5 :         tommy_node* node = tommy_list_head(list);
     405             : 
     406       11272 :         while (node) {
     407       11267 :                 void* data = node->data;
     408       11267 :                 node = node->next;
     409       11267 :                 func(arg, data);
     410             :         }
     411           5 : }
     412             : 
     413             : #endif

Generated by: LCOV version 1.0