src/linked_list.c

Fri, 23 May 2025 12:44:24 +0200

author
Mike Becker <universe@uap-core.de>
date
Fri, 23 May 2025 12:44:24 +0200
changeset 1327
ed75dc1db503
parent 1319
aa1f580f8f59
permissions
-rw-r--r--

make test-compile depend on both static and shared

the shared lib is not needed for the tests,
but when run with coverage, gcov will be confused
when outdated line information is available from
a previous shared build

/*
 * 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.
 */

#include "cx/linked_list.h"
#include "cx/compare.h"
#include <string.h>
#include <assert.h>

// LOW LEVEL LINKED LIST FUNCTIONS

#define CX_LL_PTR(cur, off) (*(void**)(((char*)(cur))+(off)))
#define ll_prev(node) CX_LL_PTR(node, loc_prev)
#define ll_next(node) CX_LL_PTR(node, loc_next)
#define ll_advance(node) CX_LL_PTR(node, loc_advance)
#define ll_data(node) (((char*)(node))+loc_data)

void *cx_linked_list_at(
        const void *start,
        size_t start_index,
        ptrdiff_t loc_advance,
        size_t index
) {
    assert(start != NULL);
    assert(loc_advance >= 0);
    size_t i = start_index;
    const void *cur = start;
    while (i != index && cur != NULL) {
        cur = ll_advance(cur);
        i < index ? i++ : i--;
    }
    return (void *) cur;
}

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
) {
    assert(start != NULL);
    assert(loc_advance >= 0);
    assert(loc_data >= 0);
    assert(cmp_func);

    void *node = (void*) start;
    size_t index = 0;
    do {
        void *current = ll_data(node);
        if (cmp_func(current, elem) == 0) {
            if (found_index != NULL) {
                *found_index = index;
            }
            return node;
        }
        node = ll_advance(node);
        index++;
    } while (node != NULL);
    return NULL;
}

void *cx_linked_list_first(
        const void *node,
        ptrdiff_t loc_prev
) {
    return cx_linked_list_last(node, loc_prev);
}

void *cx_linked_list_last(
        const void *node,
        ptrdiff_t loc_next
) {
    assert(node != NULL);
    assert(loc_next >= 0);

    const void *cur = node;
    const void *last;
    do {
        last = cur;
    } while ((cur = ll_next(cur)) != NULL);

    return (void *) last;
}

void *cx_linked_list_prev(
        const void *begin,
        ptrdiff_t loc_next,
        const void *node
) {
    assert(begin != NULL);
    assert(node != NULL);
    assert(loc_next >= 0);
    if (begin == node) return NULL;
    const void *cur = begin;
    const void *next;
    while (1) {
        next = ll_next(cur);
        if (next == node) return (void *) cur;
        cur = next;
    }
}

void cx_linked_list_link(
        void *left,
        void *right,
        ptrdiff_t loc_prev,
        ptrdiff_t loc_next
) {
    assert(loc_next >= 0);
    ll_next(left) = right;
    if (loc_prev >= 0) {
        ll_prev(right) = left;
    }
}

void cx_linked_list_unlink(
        void *left,
        void *right,
        ptrdiff_t loc_prev,
        ptrdiff_t loc_next
) {
    assert (loc_next >= 0);
    assert(ll_next(left) == right);
    ll_next(left) = NULL;
    if (loc_prev >= 0) {
        assert(ll_prev(right) == left);
        ll_prev(right) = NULL;
    }
}

void cx_linked_list_add(
        void **begin,
        void **end,
        ptrdiff_t loc_prev,
        ptrdiff_t loc_next,
        void *new_node
) {
    void *last;
    if (end == NULL) {
        assert(begin != NULL);
        last = *begin == NULL ? NULL : cx_linked_list_last(*begin, loc_next);
    } else {
        last = *end;
    }
    cx_linked_list_insert_chain(begin, end, loc_prev, loc_next, last, new_node, new_node);
}

void cx_linked_list_prepend(
        void **begin,
        void **end,
        ptrdiff_t loc_prev,
        ptrdiff_t loc_next,
        void *new_node
) {
    cx_linked_list_insert_chain(begin, end, loc_prev, loc_next, NULL, new_node, new_node);
}

void cx_linked_list_insert(
        void **begin,
        void **end,
        ptrdiff_t loc_prev,
        ptrdiff_t loc_next,
        void *node,
        void *new_node
) {
    cx_linked_list_insert_chain(begin, end, loc_prev, loc_next, node, new_node, new_node);
}

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
) {
    // find the end of the chain, if not specified
    if (insert_end == NULL) {
        insert_end = cx_linked_list_last(insert_begin, loc_next);
    }

    // determine the successor
    void *successor;
    if (node == NULL) {
        assert(begin != NULL || (end != NULL && loc_prev >= 0));
        if (begin != NULL) {
            successor = *begin;
            *begin = insert_begin;
        } else {
            successor = *end == NULL ? NULL : cx_linked_list_first(*end, loc_prev);
        }
    } else {
        successor = ll_next(node);
        cx_linked_list_link(node, insert_begin, loc_prev, loc_next);
    }

    if (successor == NULL) {
        // the list ends with the new chain
        if (end != NULL) {
            *end = insert_end;
        }
    } else {
        cx_linked_list_link(insert_end, successor, loc_prev, loc_next);
    }
}

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
) {
    assert(ll_next(new_node) == NULL);
    cx_linked_list_insert_sorted_chain(
            begin, end, loc_prev, loc_next, new_node, cmp_func);
}

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
) {
    assert(begin != NULL);
    assert(loc_next >= 0);
    assert(insert_begin != NULL);

    // track currently observed nodes
    void *dest_prev = NULL;
    void *dest = *begin;
    void *src = insert_begin;

    // special case: list is empty
    if (dest == NULL) {
        *begin = src;
        if (end != NULL) {
            *end = cx_linked_list_last(src, loc_next);
        }
        return;
    }

    // search the list for insertion points
    while (dest != NULL && src != NULL) {
        // compare current list node with source node
        // if less or equal, skip
        if (cmp_func(dest, src) <= 0) {
            dest_prev = dest;
            dest = ll_next(dest);
            continue;
        }

        // determine chain of elements that can be inserted
        void *end_of_chain = src;
        void *next_in_chain = ll_next(src);
        while (next_in_chain != NULL) {
            // once we become larger than the list elem, break
            if (cmp_func(dest, next_in_chain) <= 0) {
                break;
            }
            // otherwise, we can insert one more
            end_of_chain = next_in_chain;
            next_in_chain = ll_next(next_in_chain);
        }

        // insert the elements
        if (dest_prev == NULL) {
            // new begin
            *begin = src;
        } else {
            cx_linked_list_link(dest_prev, src, loc_prev, loc_next);
        }
        cx_linked_list_link(end_of_chain, dest, loc_prev, loc_next);

        // continue with next
        src = next_in_chain;
        dest_prev = dest;
        dest = ll_next(dest);
    }

    // insert remaining items
    if (src != NULL) {
        cx_linked_list_link(dest_prev, src, loc_prev, loc_next);
    }

    // determine new end of list, if requested
    if (end != NULL) {
        *end = cx_linked_list_last(
                dest != NULL ? dest : dest_prev, loc_next);
    }
}

size_t cx_linked_list_remove_chain(
        void **begin,
        void **end,
        ptrdiff_t loc_prev,
        ptrdiff_t loc_next,
        void *node,
        size_t num
) {
    assert(node != NULL);
    assert(loc_next >= 0);
    assert(loc_prev >= 0 || begin != NULL);

    // easy exit
    if (num == 0) return 0;

    // find adjacent nodes
    void *prev;
    if (loc_prev >= 0) {
        prev = ll_prev(node);
    } else {
        prev = cx_linked_list_prev(*begin, loc_next, node);
    }

    void *next = ll_next(node);
    size_t removed = 1;
    for (; removed < num && next != NULL ; removed++) {
        next = ll_next(next);
    }

    // update next pointer of prev node, or set begin
    if (prev == NULL) {
        if (begin != NULL) {
            *begin = next;
        }
    } else {
        ll_next(prev) = next;
    }

    // update prev pointer of next node, or set end
    if (next == NULL) {
        if (end != NULL) {
            *end = prev;
        }
    } else if (loc_prev >= 0) {
        ll_prev(next) = prev;
    }

    return removed;
}

size_t cx_linked_list_size(
        const void *node,
        ptrdiff_t loc_next
) {
    assert(loc_next >= 0);
    size_t size = 0;
    while (node != NULL) {
        node = ll_next(node);
        size++;
    }
    return size;
}

#ifndef CX_LINKED_LIST_SORT_SBO_SIZE
#define CX_LINKED_LIST_SORT_SBO_SIZE 1024
#endif

static void cx_linked_list_sort_merge(
        ptrdiff_t loc_prev,
        ptrdiff_t loc_next,
        ptrdiff_t loc_data,
        size_t length,
        void *ls,
        void *le,
        void *re,
        cx_compare_func cmp_func,
        void **begin,
        void **end
) {
    void *sbo[CX_LINKED_LIST_SORT_SBO_SIZE];
    void **sorted = length >= CX_LINKED_LIST_SORT_SBO_SIZE ?
                    cxMallocDefault(sizeof(void *) * length) : sbo;
    if (sorted == NULL) abort();
    void *rc, *lc;

    lc = ls;
    rc = le;
    size_t n = 0;
    while (lc && lc != le && rc != re) {
        if (cmp_func(ll_data(lc), ll_data(rc)) <= 0) {
            sorted[n] = lc;
            lc = ll_next(lc);
        } else {
            sorted[n] = rc;
            rc = ll_next(rc);
        }
        n++;
    }
    while (lc && lc != le) {
        sorted[n] = lc;
        lc = ll_next(lc);
        n++;
    }
    while (rc && rc != re) {
        sorted[n] = rc;
        rc = ll_next(rc);
        n++;
    }

    // Update pointer
    if (loc_prev >= 0) ll_prev(sorted[0]) = NULL;
    for (size_t i = 0 ; i < length - 1; i++) {
        cx_linked_list_link(sorted[i], sorted[i + 1], loc_prev, loc_next);
    }
    ll_next(sorted[length - 1]) = NULL;

    *begin = sorted[0];
    *end = sorted[length - 1];
    if (sorted != sbo) {
        cxFreeDefault(sorted);
    }
}

void cx_linked_list_sort( // NOLINT(misc-no-recursion) - purposely recursive function
        void **begin,
        void **end,
        ptrdiff_t loc_prev,
        ptrdiff_t loc_next,
        ptrdiff_t loc_data,
        cx_compare_func cmp_func
) {
    assert(begin != NULL);
    assert(loc_next >= 0);
    assert(loc_data >= 0);
    assert(cmp_func);

    void *lc, *ls, *le, *re;

    // set start node
    ls = *begin;

    // early exit when this list is empty
    if (ls == NULL) return;

    // check how many elements are already sorted
    lc = ls;
    size_t ln = 1;
    while (ll_next(lc) != NULL && cmp_func(ll_data(ll_next(lc)), ll_data(lc)) > 0) {
        lc = ll_next(lc);
        ln++;
    }
    le = ll_next(lc);

    // if first unsorted node is NULL, the list is already completely sorted
    if (le != NULL) {
        void *rc;
        size_t rn = 1;
        rc = le;
        // skip already sorted elements
        while (ll_next(rc) != NULL && cmp_func(ll_data(ll_next(rc)), ll_data(rc)) > 0) {
            rc = ll_next(rc);
            rn++;
        }
        re = ll_next(rc);

        // {ls,...,le->prev} and {rs,...,re->prev} are sorted - merge them
        void *sorted_begin, *sorted_end;
        cx_linked_list_sort_merge(loc_prev, loc_next, loc_data,
                                  ln + rn, ls, le, re, cmp_func,
                                  &sorted_begin, &sorted_end);

        // Something left? Sort it!
        size_t remainder_length = cx_linked_list_size(re, loc_next);
        if (remainder_length > 0) {
            void *remainder = re;
            cx_linked_list_sort(&remainder, NULL, loc_prev, loc_next, loc_data, cmp_func);

            // merge sorted list with (also sorted) remainder
            cx_linked_list_sort_merge(loc_prev, loc_next, loc_data,
                                      ln + rn + remainder_length,
                                      sorted_begin, remainder, NULL, cmp_func,
                                      &sorted_begin, &sorted_end);
        }
        *begin = sorted_begin;
        if (end) *end = sorted_end;
    }
}

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
) {
    const void *left = begin_left, *right = begin_right;

    while (left != NULL && right != NULL) {
        const void *left_data = ll_data(left);
        const void *right_data = ll_data(right);
        int result = cmp_func(left_data, right_data);
        if (result != 0) return result;
        left = ll_advance(left);
        right = ll_advance(right);
    }

    if (left != NULL) { return 1; }
    else if (right != NULL) { return -1; }
    else { return 0; }
}

void cx_linked_list_reverse(
        void **begin,
        void **end,
        ptrdiff_t loc_prev,
        ptrdiff_t loc_next
) {
    assert(begin != NULL);
    assert(loc_next >= 0);

    // swap all links
    void *prev = NULL;
    void *cur = *begin;
    while (cur != NULL) {
        void *next = ll_next(cur);

        ll_next(cur) = prev;
        if (loc_prev >= 0) {
            ll_prev(cur) = next;
        }

        prev = cur;
        cur = next;
    }

    // update begin and end
    if (end != NULL) {
        *end = *begin;
    }
    *begin = prev;
}

// HIGH LEVEL LINKED LIST IMPLEMENTATION

typedef struct cx_linked_list_node cx_linked_list_node;
struct cx_linked_list_node {
    cx_linked_list_node *prev;
    cx_linked_list_node *next;
    char payload[];
};

#define CX_LL_LOC_PREV offsetof(cx_linked_list_node, prev)
#define CX_LL_LOC_NEXT offsetof(cx_linked_list_node, next)
#define CX_LL_LOC_DATA offsetof(cx_linked_list_node, payload)

typedef struct {
    struct cx_list_s base;
    cx_linked_list_node *begin;
    cx_linked_list_node *end;
} cx_linked_list;

static cx_linked_list_node *cx_ll_node_at(
        const cx_linked_list *list,
        size_t index
) {
    if (index >= list->base.collection.size) {
        return NULL;
    } else if (index > list->base.collection.size / 2) {
        return cx_linked_list_at(list->end, list->base.collection.size - 1, CX_LL_LOC_PREV, index);
    } else {
        return cx_linked_list_at(list->begin, 0, CX_LL_LOC_NEXT, index);
    }
}

static cx_linked_list_node *cx_ll_malloc_node(const struct cx_list_s *list) {
    return cxMalloc(list->collection.allocator,
                    sizeof(cx_linked_list_node) + list->collection.elem_size);
}

static int cx_ll_insert_at(
        struct cx_list_s *list,
        cx_linked_list_node *node,
        const void *elem
) {

    // create the new new_node
    cx_linked_list_node *new_node = cx_ll_malloc_node(list);

    // sortir if failed
    if (new_node == NULL) return 1;

    // initialize new new_node
    new_node->prev = new_node->next = NULL;
    if (elem != NULL) {
        memcpy(new_node->payload, elem, list->collection.elem_size);
    }

    // insert
    cx_linked_list *ll = (cx_linked_list *) list;
    cx_linked_list_insert_chain(
            (void **) &ll->begin, (void **) &ll->end,
            CX_LL_LOC_PREV, CX_LL_LOC_NEXT,
            node, new_node, new_node
    );

    // increase the size and return
    list->collection.size++;
    return 0;
}

static size_t cx_ll_insert_array(
        struct cx_list_s *list,
        size_t index,
        const void *array,
        size_t n
) {
    // out-of bounds and corner case check
    if (index > list->collection.size || n == 0) return 0;

    // find position efficiently
    cx_linked_list_node *node = index == 0 ? NULL : cx_ll_node_at((cx_linked_list *) list, index - 1);

    // perform first insert
    if (0 != cx_ll_insert_at(list, node, array)) return 1;

    // is there more?
    if (n == 1) return 1;

    // we now know exactly where we are
    node = node == NULL ? ((cx_linked_list *) list)->begin : node->next;

    // we can add the remaining nodes and immediately advance to the inserted node
    const char *source = array;
    for (size_t i = 1; i < n; i++) {
        source += list->collection.elem_size;
        if (0 != cx_ll_insert_at(list, node, source)) return i;
        node = node->next;
    }
    return n;
}

static void *cx_ll_insert_element(
        struct cx_list_s *list,
        size_t index,
        const void *element
) {
    // out-of-bounds check
    if (index > list->collection.size) return NULL;

    // find position efficiently
    cx_linked_list_node *node = index == 0 ? NULL : cx_ll_node_at((cx_linked_list *) list, index - 1);

    // perform first insert
    if (cx_ll_insert_at(list, node, element)) return NULL;

    // return a pointer to the data of the inserted node
    if (node == NULL) {
        return ((cx_linked_list *) list)->begin->payload;
    } else {
        return node->next->payload;
    }
}

static _Thread_local cx_compare_func cx_ll_insert_sorted_cmp_func;

static int cx_ll_insert_sorted_cmp_helper(const void *l, const void *r) {
    const cx_linked_list_node *left = l;
    const cx_linked_list_node *right = r;
    return cx_ll_insert_sorted_cmp_func(left->payload, right->payload);
}

static size_t cx_ll_insert_sorted(
        struct cx_list_s *list,
        const void *array,
        size_t n
) {
    // special case
    if (n == 0) return 0;

    // create a new chain of nodes
    cx_linked_list_node *chain = cx_ll_malloc_node(list);
    if (chain == NULL) return 0;

    memcpy(chain->payload, array, list->collection.elem_size);
    chain->prev = NULL;
    chain->next = NULL;

    // add all elements from the array to that chain
    cx_linked_list_node *prev = chain;
    const char *src = array;
    size_t inserted = 1;
    for (; inserted < n; inserted++) {
        cx_linked_list_node *next = cx_ll_malloc_node(list);
        if (next == NULL) break;
        src += list->collection.elem_size;
        memcpy(next->payload, src, list->collection.elem_size);
        prev->next = next;
        next->prev = prev;
        prev = next;
    }
    prev->next = NULL;

    // invoke the low level function
    cx_linked_list *ll = (cx_linked_list *) list;
    cx_ll_insert_sorted_cmp_func = list->collection.cmpfunc;
    cx_linked_list_insert_sorted_chain(
            (void **) &ll->begin,
            (void **) &ll->end,
            CX_LL_LOC_PREV,
            CX_LL_LOC_NEXT,
            chain,
            cx_ll_insert_sorted_cmp_helper
    );

    // adjust the list metadata
    list->collection.size += inserted;

    return inserted;
}

static size_t cx_ll_remove(
        struct cx_list_s *list,
        size_t index,
        size_t num,
        void *targetbuf
) {
    cx_linked_list *ll = (cx_linked_list *) list;
    cx_linked_list_node *node = cx_ll_node_at(ll, index);

    // out-of-bounds check
    if (node == NULL) return 0;

    // remove
    size_t removed = cx_linked_list_remove_chain(
            (void **) &ll->begin,
            (void **) &ll->end,
            CX_LL_LOC_PREV,
            CX_LL_LOC_NEXT,
            node,
            num
    );

    // adjust size
    list->collection.size -= removed;

    // copy or destroy the removed chain
    if (targetbuf == NULL) {
        cx_linked_list_node *n = node;
        for (size_t i = 0; i < removed; i++) {
            // element destruction
            cx_invoke_destructor(list, n->payload);

            // free the node and advance
            void *next = n->next;
            cxFree(list->collection.allocator, n);
            n = next;
        }
    } else {
        char *dest = targetbuf;
        cx_linked_list_node *n = node;
        for (size_t i = 0; i < removed; i++) {
            // copy payload
            memcpy(dest, n->payload, list->collection.elem_size);

            // advance target buffer
            dest += list->collection.elem_size;

            // free the node and advance
            void *next = n->next;
            cxFree(list->collection.allocator, n);
            n = next;
        }
    }

    return removed;
}

static void cx_ll_clear(struct cx_list_s *list) {
    if (list->collection.size == 0) return;

    cx_linked_list *ll = (cx_linked_list *) list;
    cx_linked_list_node *node = ll->begin;
    while (node != NULL) {
        cx_invoke_destructor(list, node->payload);
        cx_linked_list_node *next = node->next;
        cxFree(list->collection.allocator, node);
        node = next;
    }
    ll->begin = ll->end = NULL;
    list->collection.size = 0;
}

static int cx_ll_swap(
        struct cx_list_s *list,
        size_t i,
        size_t j
) {
    if (i >= list->collection.size || j >= list->collection.size) return 1;
    if (i == j) return 0;

    // perform an optimized search that finds both elements in one run
    cx_linked_list *ll = (cx_linked_list *) list;
    size_t mid = list->collection.size / 2;
    size_t left, right;
    if (i < j) {
        left = i;
        right = j;
    } else {
        left = j;
        right = i;
    }
    cx_linked_list_node *nleft = NULL, *nright = NULL;
    if (left < mid && right < mid) {
        // case 1: both items left from mid
        nleft = cx_ll_node_at(ll, left);
        assert(nleft != NULL);
        nright = nleft;
        for (size_t c = left; c < right; c++) {
            nright = nright->next;
        }
    } else if (left >= mid && right >= mid) {
        // case 2: both items right from mid
        nright = cx_ll_node_at(ll, right);
        assert(nright != NULL);
        nleft = nright;
        for (size_t c = right; c > left; c--) {
            nleft = nleft->prev;
        }
    } else {
        // case 3: one item left, one item right

        // chose the closest to begin / end
        size_t closest;
        size_t other;
        size_t diff2boundary = list->collection.size - right - 1;
        if (left <= diff2boundary) {
            closest = left;
            other = right;
            nleft = cx_ll_node_at(ll, left);
        } else {
            closest = right;
            other = left;
            diff2boundary = left;
            nright = cx_ll_node_at(ll, right);
        }

        // is other element closer to us or closer to boundary?
        if (right - left <= diff2boundary) {
            // search other element starting from already found element
            if (closest == left) {
                nright = nleft;
                for (size_t c = left; c < right; c++) {
                    nright = nright->next;
                }
            } else {
                nleft = nright;
                for (size_t c = right; c > left; c--) {
                    nleft = nleft->prev;
                }
            }
        } else {
            // search other element starting at the boundary
            if (closest == left) {
                nright = cx_ll_node_at(ll, other);
            } else {
                nleft = cx_ll_node_at(ll, other);
            }
        }
    }

    cx_linked_list_node *prev = nleft->prev;
    cx_linked_list_node *next = nright->next;
    cx_linked_list_node *midstart = nleft->next;
    cx_linked_list_node *midend = nright->prev;

    if (prev == NULL) {
        ll->begin = nright;
    } else {
        prev->next = nright;
    }
    nright->prev = prev;
    if (midstart == nright) {
        // special case: both nodes are adjacent
        nright->next = nleft;
        nleft->prev = nright;
    } else {
        // likely case: a chain is between the two nodes
        nright->next = midstart;
        midstart->prev = nright;
        midend->next = nleft;
        nleft->prev = midend;
    }
    nleft->next = next;
    if (next == NULL) {
        ll->end = nleft;
    } else {
        next->prev = nleft;
    }

    return 0;
}

static void *cx_ll_at(
        const struct cx_list_s *list,
        size_t index
) {
    cx_linked_list *ll = (cx_linked_list *) list;
    cx_linked_list_node *node = cx_ll_node_at(ll, index);
    return node == NULL ? NULL : node->payload;
}

static size_t cx_ll_find_remove(
        struct cx_list_s *list,
        const void *elem,
        bool remove
) {
    if (list->collection.size == 0) return 0;

    size_t index;
    cx_linked_list *ll = ((cx_linked_list *) list);
    cx_linked_list_node *node = cx_linked_list_find(
            ll->begin,
            CX_LL_LOC_NEXT, CX_LL_LOC_DATA,
            list->collection.cmpfunc, elem,
            &index
    );
    if (node == NULL) {
        return list->collection.size;
    }
    if (remove) {
        cx_invoke_destructor(list, node->payload);
        cx_linked_list_remove((void **) &ll->begin, (void **) &ll->end,
                              CX_LL_LOC_PREV, CX_LL_LOC_NEXT, node);
        list->collection.size--;
        cxFree(list->collection.allocator, node);
    }
    return index;
}

static void cx_ll_sort(struct cx_list_s *list) {
    cx_linked_list *ll = (cx_linked_list *) list;
    cx_linked_list_sort((void **) &ll->begin, (void **) &ll->end,
                        CX_LL_LOC_PREV, CX_LL_LOC_NEXT, CX_LL_LOC_DATA,
                        list->collection.cmpfunc);
}

static void cx_ll_reverse(struct cx_list_s *list) {
    cx_linked_list *ll = (cx_linked_list *) list;
    cx_linked_list_reverse((void **) &ll->begin, (void **) &ll->end, CX_LL_LOC_PREV, CX_LL_LOC_NEXT);
}

static int cx_ll_compare(
        const struct cx_list_s *list,
        const struct cx_list_s *other
) {
    cx_linked_list *left = (cx_linked_list *) list;
    cx_linked_list *right = (cx_linked_list *) other;
    return cx_linked_list_compare(left->begin, right->begin,
                                  CX_LL_LOC_NEXT, CX_LL_LOC_DATA,
                                  list->collection.cmpfunc);
}

static bool cx_ll_iter_valid(const void *it) {
    const struct cx_iterator_s *iter = it;
    return iter->elem_handle != NULL;
}

static void cx_ll_iter_next(void *it) {
    struct cx_iterator_s *iter = it;
    if (iter->base.remove) {
        iter->base.remove = false;
        struct cx_list_s *list = iter->src_handle.m;
        cx_linked_list *ll = iter->src_handle.m;
        cx_linked_list_node *node = iter->elem_handle;
        iter->elem_handle = node->next;
        cx_invoke_destructor(list, node->payload);
        cx_linked_list_remove((void **) &ll->begin, (void **) &ll->end,
                              CX_LL_LOC_PREV, CX_LL_LOC_NEXT, node);
        list->collection.size--;
        cxFree(list->collection.allocator, node);
    } else {
        iter->index++;
        cx_linked_list_node *node = iter->elem_handle;
        iter->elem_handle = node->next;
    }
}

static void cx_ll_iter_prev(void *it) {
    struct cx_iterator_s *iter = it;
    if (iter->base.remove) {
        iter->base.remove = false;
        struct cx_list_s *list = iter->src_handle.m;
        cx_linked_list *ll = iter->src_handle.m;
        cx_linked_list_node *node = iter->elem_handle;
        iter->elem_handle = node->prev;
        iter->index--;
        cx_invoke_destructor(list, node->payload);
        cx_linked_list_remove((void **) &ll->begin, (void **) &ll->end,
                              CX_LL_LOC_PREV, CX_LL_LOC_NEXT, node);
        list->collection.size--;
        cxFree(list->collection.allocator, node);
    } else {
        iter->index--;
        cx_linked_list_node *node = iter->elem_handle;
        iter->elem_handle = node->prev;
    }
}

static void *cx_ll_iter_current(const void *it) {
    const struct cx_iterator_s *iter = it;
    cx_linked_list_node *node = iter->elem_handle;
    return node->payload;
}

static CxIterator cx_ll_iterator(
        const struct cx_list_s *list,
        size_t index,
        bool backwards
) {
    CxIterator iter;
    iter.index = index;
    iter.src_handle.c = list;
    iter.elem_handle = cx_ll_node_at((const cx_linked_list *) list, index);
    iter.elem_size = list->collection.elem_size;
    iter.elem_count = list->collection.size;
    iter.base.valid = cx_ll_iter_valid;
    iter.base.current = cx_ll_iter_current;
    iter.base.next = backwards ? cx_ll_iter_prev : cx_ll_iter_next;
    iter.base.mutating = false;
    iter.base.remove = false;
    return iter;
}

static int cx_ll_insert_iter(
        CxIterator *iter,
        const void *elem,
        int prepend
) {
    struct cx_list_s *list = iter->src_handle.m;
    cx_linked_list_node *node = iter->elem_handle;
    if (node != NULL) {
        assert(prepend >= 0 && prepend <= 1);
        cx_linked_list_node *choice[2] = {node, node->prev};
        int result = cx_ll_insert_at(list, choice[prepend], elem);
        if (result == 0) {
            iter->elem_count++;
            if (prepend) {
                iter->index++;
            }
        }
        return result;
    } else {
        if (cx_ll_insert_element(list, list->collection.size, elem) == NULL) {
            return 1;
        }
        iter->elem_count++;
        iter->index = list->collection.size;
        return 0;
    }
}

static void cx_ll_destructor(CxList *list) {
    cx_linked_list *ll = (cx_linked_list *) list;

    cx_linked_list_node *node = ll->begin;
    while (node) {
        cx_invoke_destructor(list, node->payload);
        void *next = node->next;
        cxFree(list->collection.allocator, node);
        node = next;
    }

    cxFree(list->collection.allocator, list);
}

static cx_list_class cx_linked_list_class = {
        cx_ll_destructor,
        cx_ll_insert_element,
        cx_ll_insert_array,
        cx_ll_insert_sorted,
        cx_ll_insert_iter,
        cx_ll_remove,
        cx_ll_clear,
        cx_ll_swap,
        cx_ll_at,
        cx_ll_find_remove,
        cx_ll_sort,
        cx_ll_compare,
        cx_ll_reverse,
        cx_ll_iterator,
};

CxList *cxLinkedListCreate(
        const CxAllocator *allocator,
        cx_compare_func comparator,
        size_t elem_size
) {
    if (allocator == NULL) {
        allocator = cxDefaultAllocator;
    }

    cx_linked_list *list = cxCalloc(allocator, 1, sizeof(cx_linked_list));
    if (list == NULL) return NULL;
    cx_list_init((CxList*)list, &cx_linked_list_class,
            allocator, comparator, elem_size);

    return (CxList *) list;
}

mercurial