382 lines
10 KiB
C
382 lines
10 KiB
C
/*
|
|
* Copyright (c) 2010, Andrea Mazzoleni. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
*
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
/** \file
|
|
* Double linked list for collisions into hashtables.
|
|
*
|
|
* This list is a double linked list mainly targeted for handling collisions
|
|
* into an hashtables, but useable also as a generic list.
|
|
*
|
|
* The main feature of this list is to require only one pointer to represent the
|
|
* list, compared to a classic implementation requiring a head an a tail pointers.
|
|
* This reduces the memory usage in hashtables.
|
|
*
|
|
* Another feature is to support the insertion at the end of the list. This allow to store
|
|
* collisions in a stable order. Where for stable order we mean that equal elements keep
|
|
* their insertion order.
|
|
*
|
|
* To initialize the list, you have to call tommy_list_init(), or to simply assign
|
|
* to it NULL, as an empty list is represented by the NULL value.
|
|
*
|
|
* \code
|
|
* tommy_list list;
|
|
*
|
|
* tommy_list_init(&list); // initializes the list
|
|
* \endcode
|
|
*
|
|
* To insert elements in the list you have to call tommy_list_insert_tail()
|
|
* or tommy_list_insert_head() for each element.
|
|
* In the insertion call you have to specify the address of the node and the
|
|
* address of the object.
|
|
* The address of the object is used to initialize the tommy_node::data field
|
|
* of the node.
|
|
*
|
|
* \code
|
|
* struct object {
|
|
* int value;
|
|
* // other fields
|
|
* tommy_node node;
|
|
* };
|
|
*
|
|
* struct object* obj = malloc(sizeof(struct object)); // creates the object
|
|
*
|
|
* obj->value = ...; // initializes the object
|
|
*
|
|
* tommy_list_insert_tail(&list, &obj->node, obj); // inserts the object
|
|
* \endcode
|
|
*
|
|
* To iterate over all the elements in the list you have to call
|
|
* tommy_list_head() to get the head of the list and follow the
|
|
* tommy_node::next pointer until NULL.
|
|
*
|
|
* \code
|
|
* tommy_node* i = tommy_list_head(&list);
|
|
* while (i) {
|
|
* struct object* obj = i->data; // gets the object pointer
|
|
*
|
|
* printf("%d\n", obj->value); // process the object
|
|
*
|
|
* i = i->next; // go to the next element
|
|
* }
|
|
* \endcode
|
|
*
|
|
* To destroy the list you have to remove all the elements,
|
|
* as the list is completely inplace and it doesn't allocate memory.
|
|
* This can be done with the tommy_list_foreach() function.
|
|
*
|
|
* \code
|
|
* // deallocates all the objects iterating the list
|
|
* tommy_list_foreach(&list, free);
|
|
* \endcode
|
|
*/
|
|
|
|
#ifndef __TOMMYLIST_H
|
|
#define __TOMMYLIST_H
|
|
|
|
#include "tommytypes.h"
|
|
|
|
/******************************************************************************/
|
|
/* list */
|
|
|
|
/**
|
|
* Double linked list type.
|
|
*/
|
|
typedef tommy_node* tommy_list;
|
|
|
|
/**
|
|
* Initializes the list.
|
|
* The list is completely inplace, so it doesn't need to be deinitialized.
|
|
*/
|
|
tommy_inline void tommy_list_init(tommy_list* list)
|
|
{
|
|
*list = 0;
|
|
}
|
|
|
|
/**
|
|
* Gets the head of the list.
|
|
* \return The head node. For empty lists 0 is returned.
|
|
*/
|
|
tommy_inline tommy_node* tommy_list_head(tommy_list* list)
|
|
{
|
|
return *list;
|
|
}
|
|
|
|
/**
|
|
* Gets the tail of the list.
|
|
* \return The tail node. For empty lists 0 is returned.
|
|
*/
|
|
tommy_inline tommy_node* tommy_list_tail(tommy_list* list)
|
|
{
|
|
tommy_node* head = tommy_list_head(list);
|
|
|
|
if (!head)
|
|
return 0;
|
|
|
|
return head->prev;
|
|
}
|
|
|
|
/** \internal
|
|
* Creates a new list with a single element.
|
|
* \param list The list to initialize.
|
|
* \param node The node to insert.
|
|
*/
|
|
tommy_inline void tommy_list_insert_first(tommy_list* list, tommy_node* node)
|
|
{
|
|
/* one element "circular" prev list */
|
|
node->prev = node;
|
|
|
|
/* one element "0 terminated" next list */
|
|
node->next = 0;
|
|
|
|
*list = node;
|
|
}
|
|
|
|
/** \internal
|
|
* Inserts an element at the head of a not empty list.
|
|
* The element is inserted at the head of the list. The list cannot be empty.
|
|
* \param list The list. The list cannot be empty.
|
|
* \param node The node to insert.
|
|
*/
|
|
tommy_inline void tommy_list_insert_head_not_empty(tommy_list* list, tommy_node* node)
|
|
{
|
|
tommy_node* head = tommy_list_head(list);
|
|
|
|
/* insert in the "circular" prev list */
|
|
node->prev = head->prev;
|
|
head->prev = node;
|
|
|
|
/* insert in the "0 terminated" next list */
|
|
node->next = head;
|
|
|
|
*list = node;
|
|
}
|
|
|
|
/** \internal
|
|
* Inserts an element at the tail of a not empty list.
|
|
* The element is inserted at the tail of the list. The list cannot be empty.
|
|
* \param head The node at the list head. It cannot be 0.
|
|
* \param node The node to insert.
|
|
*/
|
|
tommy_inline void tommy_list_insert_tail_not_empty(tommy_node* head, tommy_node* node)
|
|
{
|
|
/* insert in the "circular" prev list */
|
|
node->prev = head->prev;
|
|
head->prev = node;
|
|
|
|
/* insert in the "0 terminated" next list */
|
|
node->next = 0;
|
|
node->prev->next = node;
|
|
}
|
|
|
|
/**
|
|
* Inserts an element at the head of a list.
|
|
* \param node The node to insert.
|
|
* \param data The object containing the node. It's used to set the tommy_node::data field of the node.
|
|
*/
|
|
tommy_inline void tommy_list_insert_head(tommy_list* list, tommy_node* node, void* data)
|
|
{
|
|
tommy_node* head = tommy_list_head(list);
|
|
|
|
if (head)
|
|
tommy_list_insert_head_not_empty(list, node);
|
|
else
|
|
tommy_list_insert_first(list, node);
|
|
|
|
node->data = data;
|
|
}
|
|
|
|
/**
|
|
* Inserts an element at the tail of a list.
|
|
* \param node The node to insert.
|
|
* \param data The object containing the node. It's used to set the tommy_node::data field of the node.
|
|
*/
|
|
tommy_inline void tommy_list_insert_tail(tommy_list* list, tommy_node* node, void* data)
|
|
{
|
|
tommy_node* head = tommy_list_head(list);
|
|
|
|
if (head)
|
|
tommy_list_insert_tail_not_empty(head, node);
|
|
else
|
|
tommy_list_insert_first(list, node);
|
|
|
|
node->data = data;
|
|
}
|
|
|
|
/**
|
|
* Removes an element from the list.
|
|
* You must already have the address of the element to remove.
|
|
* \note The node content is left unchanged, including the tommy_node::next
|
|
* and tommy_node::prev fields that still contain pointers at the list.
|
|
* \param node The node to remove. The node must be in the list.
|
|
* \return The tommy_node::data field of the node removed.
|
|
*/
|
|
tommy_inline void* tommy_list_remove_existing(tommy_list* list, tommy_node* node)
|
|
{
|
|
tommy_node* head = tommy_list_head(list);
|
|
|
|
/* remove from the "circular" prev list */
|
|
if (node->next)
|
|
node->next->prev = node->prev;
|
|
else
|
|
head->prev = node->prev; /* the last */
|
|
|
|
/* remove from the "0 terminated" next list */
|
|
if (head == node)
|
|
*list = node->next; /* the new head, in case 0 */
|
|
else
|
|
node->prev->next = node->next;
|
|
|
|
return node->data;
|
|
}
|
|
|
|
/**
|
|
* Concats two lists.
|
|
* The second list is concatenated at the first list.
|
|
* \param first The first list.
|
|
* \param second The second list. After this call the list content is undefined,
|
|
* and you should not use it anymore.
|
|
*/
|
|
tommy_inline void tommy_list_concat(tommy_list* first, tommy_list* second)
|
|
{
|
|
tommy_node* first_head;
|
|
tommy_node* first_tail;
|
|
tommy_node* second_head;
|
|
|
|
/* if the second is empty, nothing to do */
|
|
second_head = tommy_list_head(second);
|
|
if (second_head == 0)
|
|
return;
|
|
|
|
/* if the first is empty, copy the second */
|
|
first_head = tommy_list_head(first);
|
|
if (first_head == 0) {
|
|
*first = *second;
|
|
return;
|
|
}
|
|
|
|
/* tail of the first list */
|
|
first_tail = first_head->prev;
|
|
|
|
/* set the "circular" prev list */
|
|
first_head->prev = second_head->prev;
|
|
second_head->prev = first_tail;
|
|
|
|
/* set the "0 terminated" next list */
|
|
first_tail->next = second_head;
|
|
}
|
|
|
|
/**
|
|
* Sorts a list.
|
|
* It's a stable merge sort with O(N*log(N)) worst complexity.
|
|
* It's faster on degenerated cases like partially ordered lists.
|
|
* \param cmp Compare function called with two elements.
|
|
* The function should return <0 if the first element is less than the second, ==0 if equal, and >0 if greater.
|
|
*/
|
|
void tommy_list_sort(tommy_list* list, tommy_compare_func* cmp);
|
|
|
|
/**
|
|
* Checks if empty.
|
|
* \return If the list is empty.
|
|
*/
|
|
tommy_inline tommy_bool_t tommy_list_empty(tommy_list* list)
|
|
{
|
|
return tommy_list_head(list) == 0;
|
|
}
|
|
|
|
/**
|
|
* Gets the number of elements.
|
|
* \note This operation is O(n).
|
|
*/
|
|
tommy_inline tommy_size_t tommy_list_count(tommy_list* list)
|
|
{
|
|
tommy_size_t count = 0;
|
|
tommy_node* i = tommy_list_head(list);
|
|
|
|
while (i) {
|
|
++count;
|
|
i = i->next;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
/**
|
|
* Calls the specified function for each element in the list.
|
|
*
|
|
* You cannot add or remove elements from the inside of the callback,
|
|
* but can use it to deallocate them.
|
|
*
|
|
* \code
|
|
* tommy_list list;
|
|
*
|
|
* // initializes the list
|
|
* tommy_list_init(&list);
|
|
*
|
|
* ...
|
|
*
|
|
* // creates an object
|
|
* struct object* obj = malloc(sizeof(struct object));
|
|
*
|
|
* ...
|
|
*
|
|
* // insert it in the list
|
|
* tommy_list_insert_tail(&list, &obj->node, obj);
|
|
*
|
|
* ...
|
|
*
|
|
* // deallocates all the objects iterating the list
|
|
* tommy_list_foreach(&list, free);
|
|
* \endcode
|
|
*/
|
|
tommy_inline void tommy_list_foreach(tommy_list* list, tommy_foreach_func* func)
|
|
{
|
|
tommy_node* node = tommy_list_head(list);
|
|
|
|
while (node) {
|
|
void* data = node->data;
|
|
node = node->next;
|
|
func(data);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calls the specified function with an argument for each element in the list.
|
|
*/
|
|
tommy_inline void tommy_list_foreach_arg(tommy_list* list, tommy_foreach_arg_func* func, void* arg)
|
|
{
|
|
tommy_node* node = tommy_list_head(list);
|
|
|
|
while (node) {
|
|
void* data = node->data;
|
|
node = node->next;
|
|
func(arg, data);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|