src/cx/linked_list.h

Sun, 23 Nov 2025 13:15:19 +0100

author
Mike Becker <universe@uap-core.de>
date
Sun, 23 Nov 2025 13:15:19 +0100
changeset 1508
dfc0ddd9571e
parent 1426
3a89b31f0724
permissions
-rw-r--r--

optimize sorted insertion by using the infimum instead of the supremum

The reason is that the supremum returns the equal element with the smallest index, and we want the largest.
Therefore, we use the infimum, which already gives us the largest index when there are equal elements, and increase the index by one. The infimum is also guaranteed to exist in that case.

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2021 Mike Becker, Olaf Wintermann 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 HOLDER 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 linked_list.h
 * @brief Linked list implementation.
 * @author Mike Becker
 * @author Olaf Wintermann
 * @copyright 2-Clause BSD License
 */

#ifndef UCX_LINKED_LIST_H
#define UCX_LINKED_LIST_H

#include "common.h"
#include "list.h"

#ifdef __cplusplus
extern "C" {
#endif

/**
 * Metadata for a linked list.
 */
typedef struct cx_linked_list_s {
    /** Base members. */
    struct cx_list_s base;
    /**
     * Location of the prev pointer (mandatory).
     */
    off_t loc_prev;
    /**
     * Location of the next pointer (mandatory).
     */
    off_t loc_next;
    /**
     * Location of the payload (mandatory).
     */
    off_t loc_data;
    /**
     * Additional bytes to allocate @em behind the payload (e.g. for metadata).
     */
    size_t extra_data_len;
    /**
     * Pointer to the first node.
     */
    void *begin;
    /**
     * Pointer to the last node.
     */
    void *end;
} cx_linked_list;

/**
 * Allocates a linked list for storing elements with @p elem_size bytes each.
 *
 * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of
 * copies of the added elements, and the compare function will be automatically set
 * to cx_cmp_ptr() if none is given.
 *
 * @param allocator the allocator for allocating the list nodes
 * (if @c NULL, the cxDefaultAllocator will be used)
 * @param comparator the comparator for the elements
 * (if @c NULL, and the list is not storing pointers, sort and find
 * functions will not work)
 * @param elem_size the size of each element in bytes
 * @return the created list
 */
cx_attr_nodiscard cx_attr_malloc cx_attr_dealloc(cxListFree, 1)
CX_EXPORT CxList *cxLinkedListCreate(const CxAllocator *allocator,
        cx_compare_func comparator, size_t elem_size);

/**
 * Allocates a linked list for storing elements with @p elem_size bytes each.
 *
 * The list will use cxDefaultAllocator and no comparator function. If you want
 * to call functions that need a comparator, you must either set one immediately
 * after list creation or use cxLinkedListCreate().
 *
 * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of
 * copies of the added elements, and the compare function will be automatically set
 * to cx_cmp_ptr().
 *
 * @param elem_size (@c size_t) the size of each element in bytes
 * @return (@c CxList*) the created list
 */
#define cxLinkedListCreateSimple(elem_size) \
        cxLinkedListCreate(NULL, NULL, elem_size)

/**
 * Finds the node at a certain index.
 *
 * This function can be used to start at an arbitrary position within the list.
 * If the search index is larger than the start index, @p loc_advance must denote
 * the location of a @c next pointer (i.e., a pointer to the next node).
 * But it is also possible that the search index is smaller than the start index
 * (e.g., in cases where traversing a list backwards is faster).
 * In that case @p loc_advance must denote the location of a @c prev pointer
 * (i.e., a pointer to the previous node).
 *
 * @param start a pointer to the start node
 * @param start_index the start index
 * @param loc_advance the location of the pointer to advance
 * @param index the search index
 * @return the node found at the specified index
 */
cx_attr_nonnull cx_attr_nodiscard
CX_EXPORT void *cx_linked_list_at(const void *start,size_t start_index,
        ptrdiff_t loc_advance, size_t index);

/**
 * Finds the node containing an element within a linked list.
 *
 * @param start a pointer to the start node
 * @param loc_advance the location of the pointer to advance
 * @param loc_data the location of the @c data pointer within your node struct
 * @param cmp_func a compare function to compare @p elem against the node data
 * @param elem a pointer to the element to find
 * @param found_index an optional pointer where the index of the found node
 * (given that @p start has index 0) is stored
 * @return a pointer to the found node or @c NULL if no matching node was found
 */
cx_attr_nonnull_arg(1, 4, 5)
CX_EXPORT void *cx_linked_list_find(const void *start, ptrdiff_t loc_advance,
        ptrdiff_t loc_data, cx_compare_func cmp_func, const void *elem,
        size_t *found_index);

/**
 * Finds the first node in a linked list.
 *
 * The function starts with the pointer denoted by @p node and traverses the list
 * along a prev pointer whose location within the node struct is
 * denoted by @p loc_prev.
 *
 * @param node a pointer to a node in the list
 * @param loc_prev the location of the @c prev pointer
 * @return a pointer to the first node
 */
cx_attr_nonnull cx_attr_returns_nonnull
CX_EXPORT void *cx_linked_list_first(const void *node, ptrdiff_t loc_prev);

/**
 * Finds the last node in a linked list.
 *
 * The function starts with the pointer denoted by @p node and traverses the list
 * along a next pointer whose location within the node struct is
 * denoted by @p loc_next.
 *
 * @param node a pointer to a node in the list
 * @param loc_next the location of the @c next pointer
 * @return a pointer to the last node
 */
cx_attr_nonnull cx_attr_returns_nonnull
CX_EXPORT void *cx_linked_list_last(const void *node, ptrdiff_t loc_next);

/**
 * Finds the predecessor of a node in case it is not linked.
 *
 * @remark If @p node is not contained in the list starting with @p begin, the behavior is undefined.
 *
 * @param begin the node where to start the search
 * @param loc_next the location of the @c next pointer
 * @param node the successor of the node to find
 * @return the node or @c NULL if @p node has no predecessor
 */
cx_attr_nonnull
CX_EXPORT void *cx_linked_list_prev(const void *begin, ptrdiff_t loc_next, const void *node);

/**
 * Adds a new node to a linked list.
 * The node must not be part of any list yet.
 *
 * @remark One of the pointers @p begin or @p end may be @c NULL, but not both.
 *
 * @param begin a pointer to the beginning node pointer (if your list has one)
 * @param end a pointer to the end node pointer (if your list has one)
 * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one)
 * @param loc_next the location of a @c next pointer within your node struct (required)
 * @param new_node a pointer to the node that shall be appended
 */
cx_attr_nonnull_arg(5)
CX_EXPORT void cx_linked_list_add(void **begin, void **end, ptrdiff_t loc_prev, ptrdiff_t loc_next, void *new_node);

/**
 * Prepends a new node to a linked list.
 * The node must not be part of any list yet.
 *
 * @remark One of the pointers @p begin or @p end may be @c NULL, but not both.
 *
 * @param begin a pointer to the beginning node pointer (if your list has one)
 * @param end a pointer to the end node pointer (if your list has one)
 * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one)
 * @param loc_next the location of a @c next pointer within your node struct (required)
 * @param new_node a pointer to the node that shall be prepended
 */
cx_attr_nonnull_arg(5)
CX_EXPORT void cx_linked_list_prepend(void **begin, void **end, ptrdiff_t loc_prev, ptrdiff_t loc_next, void *new_node);

/**
 * Links two nodes.
 *
 * @param left the new predecessor of @p right
 * @param right the new successor of @p left
 * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one)
 * @param loc_next the location of a @c next pointer within your node struct (required)
 */
cx_attr_nonnull
CX_EXPORT void cx_linked_list_link(void *left, void *right, ptrdiff_t loc_prev, ptrdiff_t loc_next);

/**
 * Unlinks two nodes.
 *
 * If right is not the successor of left, the behavior is undefined.
 *
 * @param left the predecessor of @p right
 * @param right the successor of @p left
 * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one)
 * @param loc_next the location of a @c next pointer within your node struct (required)
 */
cx_attr_nonnull
CX_EXPORT void cx_linked_list_unlink(void *left, void *right, ptrdiff_t loc_prev, ptrdiff_t loc_next);

/**
 * Inserts a new node after a given node of a linked list.
 * The new node must not be part of any list yet.
 *
 * @note If you specify @c NULL as the @p node to insert after, this function needs either the @p begin or
 * the @p end pointer to determine the start of the list. Then the new node will be prepended to the list.
 *
 * @param begin a pointer to the beginning node pointer (if your list has one)
 * @param end a pointer to the end node pointer (if your list has one)
 * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one)
 * @param loc_next the location of a @c next pointer within your node struct (required)
 * @param node the node after which to insert (@c NULL if you want to prepend the node to the list)
 * @param new_node a pointer to the node that shall be inserted
 */
cx_attr_nonnull_arg(6)
CX_EXPORT void cx_linked_list_insert(void **begin, void **end,
        ptrdiff_t loc_prev, ptrdiff_t loc_next, void *node, void *new_node);

/**
 * Inserts a chain of nodes after a given node of a linked list.
 * The chain must not be part of any list yet.
 *
 * If you do not explicitly specify the end of the chain, it will be determined by traversing
 * the @c next pointer.
 *
 * @note If you specify @c NULL as the @p node to insert after, this function needs either the @p begin or
 * the @p end pointer to determine the start of the list. If only the @p end pointer is specified, you also need
 * to provide a valid @p loc_prev location.
 * Then the chain will be prepended to the list.
 *
 * @param begin a pointer to the beginning node pointer (if your list has one)
 * @param end a pointer to the end node pointer (if your list has one)
 * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one)
 * @param loc_next the location of a @c next pointer within your node struct (required)
 * @param node the node after which to insert (@c NULL to prepend the chain to the list)
 * @param insert_begin a pointer to the first node of the chain that shall be inserted
 * @param insert_end a pointer to the last node of the chain (or NULL if the last node shall be determined)
 */
cx_attr_nonnull_arg(6)
CX_EXPORT void cx_linked_list_insert_chain(void **begin, void **end,
        ptrdiff_t loc_prev, ptrdiff_t loc_next, void *node, void *insert_begin, void *insert_end);

/**
 * Inserts a node into a sorted linked list.
 * The new node must not be part of any list yet.
 *
 * If the list starting with the node pointed to by @p begin is not sorted
 * already, the behavior is undefined.
 *
 * @param begin a pointer to the beginning node pointer (required)
 * @param end a pointer to the end node pointer (if your list has one)
 * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one)
 * @param loc_next the location of a @c next pointer within your node struct (required)
 * @param new_node a pointer to the node that shall be inserted
 * @param cmp_func a compare function that will receive the node pointers
 */
cx_attr_nonnull_arg(1, 5, 6)
CX_EXPORT void cx_linked_list_insert_sorted(void **begin, void **end,
        ptrdiff_t loc_prev, ptrdiff_t loc_next, void *new_node, cx_compare_func cmp_func);

/**
 * Inserts a chain of nodes into a sorted linked list.
 * The chain must not be part of any list yet.
 *
 * If either the list starting with the node pointed to by @p begin or the list
 * starting with @p insert_begin is not sorted, the behavior is undefined.
 *
 * @attention In contrast to cx_linked_list_insert_chain(), the source chain
 * will be broken and inserted into the target list so that the resulting list
 * will be sorted according to @p cmp_func. That means each node in the source
 * chain may be re-linked with nodes from the target list.
 *
 * @param begin a pointer to the beginning node pointer (required)
 * @param end a pointer to the end node pointer (if your list has one)
 * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one)
 * @param loc_next the location of a @c next pointer within your node struct (required)
 * @param insert_begin a pointer to the first node of the chain that shall be inserted
 * @param cmp_func a compare function that will receive the node pointers
 */
cx_attr_nonnull_arg(1, 5, 6)
CX_EXPORT void cx_linked_list_insert_sorted_chain(void **begin, void **end,
        ptrdiff_t loc_prev, ptrdiff_t loc_next, void *insert_begin, cx_compare_func cmp_func);

/**
 * Inserts a node into a sorted linked list if no other node with the same value already exists.
 * The new node must not be part of any list yet.
 *
 * If the list starting with the node pointed to by @p begin is not sorted
 * already, the behavior is undefined.
 *
 * @param begin a pointer to the beginning node pointer (required)
 * @param end a pointer to the end node pointer (if your list has one)
 * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one)
 * @param loc_next the location of a @c next pointer within your node struct (required)
 * @param new_node a pointer to the node that shall be inserted
 * @param cmp_func a compare function that will receive the node pointers
 * @retval zero when the node was inserted
 * @retval non-zero when a node with the same value already exists
 */
cx_attr_nonnull_arg(1, 5, 6)
CX_EXPORT int cx_linked_list_insert_unique(void **begin, void **end,
        ptrdiff_t loc_prev, ptrdiff_t loc_next, void *new_node, cx_compare_func cmp_func);

/**
 * Inserts a chain of nodes into a sorted linked list, avoiding duplicates.
 * The chain must not be part of any list yet.
 *
 * If either the list starting with the node pointed to by @p begin or the list
 * starting with @p insert_begin is not sorted, the behavior is undefined.
 *
 * @attention In contrast to cx_linked_list_insert_sorted(), not all nodes of the
 * chain might be added. This function returns a new chain consisting of all the duplicates.
 *
 * @param begin a pointer to the beginning node pointer (required)
 * @param end a pointer to the end node pointer (if your list has one)
 * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one)
 * @param loc_next the location of a @c next pointer within your node struct (required)
 * @param insert_begin a pointer to the first node of the chain that shall be inserted
 * @param cmp_func a compare function that will receive the node pointers
 * @return a pointer to a new chain with all duplicates that were not inserted (or @c NULL when there were no duplicates)
 */
cx_attr_nonnull_arg(1, 5, 6) cx_attr_nodiscard
CX_EXPORT void *cx_linked_list_insert_unique_chain(void **begin, void **end,
        ptrdiff_t loc_prev, ptrdiff_t loc_next, void *insert_begin, cx_compare_func cmp_func);

/**
 * Removes a chain of nodes from the linked list.
 *
 * If one of the nodes to remove is the beginning (resp. end) node of the list, and if @p begin (resp. @p end)
 * addresses are provided, the pointers are adjusted accordingly.
 *
 * The following combinations of arguments are valid (more arguments are optional):
 * @li @p loc_next and @p loc_prev (ancestor node is determined by using the prev pointer, overall O(1) performance)
 * @li @p loc_next and @p begin (ancestor node is determined by list traversal, overall O(n) performance)
 *
 * @remark The @c next and @c prev pointers of the removed chain are not cleared by this function and may still be used
 * to traverse to a former adjacent node in the list, or within the chain.
 *
 * @param begin a pointer to the beginning node pointer (optional)
 * @param end a pointer to the end node pointer (optional)
 * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one)
 * @param loc_next the location of a @c next pointer within your node struct (required)
 * @param node the start node of the chain
 * @param num the number of nodes to remove
 * @return the actual number of nodes that were removed (can be less when the list did not have enough nodes)
 */
cx_attr_nonnull_arg(5)
CX_EXPORT size_t cx_linked_list_remove_chain(void **begin, void **end,
        ptrdiff_t loc_prev, ptrdiff_t loc_next, void *node, size_t num);

/**
 * Removes a node from the linked list.
 *
 * If the node to remove is the beginning (resp. end) node of the list, and if @p begin (resp. @p end)
 * addresses are provided, the pointers are adjusted accordingly.
 *
 * The following combinations of arguments are valid (more arguments are optional):
 * @li @p loc_next and @p loc_prev (ancestor node is determined by using the prev pointer, overall O(1) performance)
 * @li @p loc_next and @p begin (ancestor node is determined by list traversal, overall O(n) performance)
 *
 * @remark The @c next and @c prev pointers of the removed node are not cleared by this function and may still be used
 * to traverse to a former adjacent node in the list.
 *
 * @param begin a pointer to the beginning node pointer (optional)
 * @param end a pointer to the end node pointer (optional)
 * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one)
 * @param loc_next the location of a @c next pointer within your node struct (required)
 * @param node the node to remove
 */
cx_attr_nonnull_arg(5)
CX_EXPORT void cx_linked_list_remove(void **begin, void **end,
        ptrdiff_t loc_prev, ptrdiff_t loc_next, void *node);

/**
 * Determines the size of a linked list starting with @p node.
 *
 * @param node the first node
 * @param loc_next the location of the @c next pointer within the node struct
 * @return the size of the list or zero if @p node is @c NULL
 */
cx_attr_nodiscard
CX_EXPORT size_t cx_linked_list_size(const void *node, ptrdiff_t loc_next);

/**
 * Sorts a linked list based on a comparison function.
 *
 * This function can work with linked lists of the following structure:
 * @code
 * typedef struct node node;
 * struct node {
 *   node* prev;
 *   node* next;
 *   my_payload data;
 * }
 * @endcode
 *
 * @note This is a recursive function with at most logarithmic recursion depth.
 *
 * @param begin a pointer to the beginning node pointer (required)
 * @param end a pointer to the end node pointer (optional)
 * @param loc_prev the location of a @c prev pointer within your node struct (negative if not present)
 * @param loc_next the location of a @c next pointer within your node struct (required)
 * @param loc_data the location of the @c data pointer within your node struct
 * @param cmp_func the compare function defining the sort order
 */
cx_attr_nonnull_arg(1, 6)
CX_EXPORT void cx_linked_list_sort(void **begin, void **end,
        ptrdiff_t loc_prev, ptrdiff_t loc_next, ptrdiff_t loc_data, cx_compare_func cmp_func);


/**
 * Compares two lists element wise.
 *
 * @attention Both lists must have the same structure.
 *
 * @param begin_left the beginning of the left list (@c NULL denotes an empty list)
 * @param begin_right the beginning of the right list  (@c NULL denotes an empty list)
 * @param loc_advance the location of the pointer to advance
 * @param loc_data the location of the @c data pointer within your node struct
 * @param cmp_func the function to compare the elements
 * @return the first non-zero result of invoking @p cmp_func or: negative if the left list is smaller than the
 * right list, positive if the left list is larger than the right list, zero if both lists are equal.
 */
cx_attr_nonnull_arg(5)
CX_EXPORT int cx_linked_list_compare(const void *begin_left, const void *begin_right,
        ptrdiff_t loc_advance, ptrdiff_t loc_data, cx_compare_func cmp_func);

/**
 * Reverses the order of the nodes in a linked list.
 *
 * @param begin a pointer to the beginning node pointer (required)
 * @param end a pointer to the end node pointer (optional)
 * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one)
 * @param loc_next the location of a @c next pointer within your node struct (required)
 */
cx_attr_nonnull_arg(1)
CX_EXPORT void cx_linked_list_reverse(void **begin, void **end, ptrdiff_t loc_prev, ptrdiff_t loc_next);

#ifdef __cplusplus
} // extern "C"
#endif

#endif // UCX_LINKED_LIST_H

mercurial