Sun, 23 Nov 2025 13:15:19 +0100
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. */ #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); } static void *cx_linked_list_insert_sorted_chain_impl( void **begin, void **end, ptrdiff_t loc_prev, ptrdiff_t loc_next, void *insert_begin, cx_compare_func cmp_func, bool allow_duplicates ) { assert(begin != NULL); assert(loc_next >= 0); assert(insert_begin != NULL); // strategy: build completely new chains from scratch void *source_original = *begin; void *source_argument = insert_begin; void *new_begin = NULL; void *new_end = NULL; void *dup_begin = NULL; void *dup_end = NULL; // determine the new start { int d = source_original == NULL ? 1 : cmp_func(source_original, source_argument); if (d <= 0) { // the new chain starts with the original chain new_begin = new_end = source_original; source_original = ll_next(source_original); if (d == 0) { if (allow_duplicates) { // duplicate allowed, append it to the chain cx_linked_list_link(new_end, source_argument, loc_prev, loc_next); new_end = source_argument; } else { // duplicate is not allowed, start a duplicate chain with the argument dup_begin = dup_end = source_argument; } source_argument = ll_next(source_argument); } } else { // input is smaller, or there is no original chain; // start the new chain with the source argument new_begin = new_end = source_argument; source_argument = ll_next(source_argument); } } // now successively compare the elements and add them to the correct chains while (source_original != NULL && source_argument != NULL) { int d = cmp_func(source_original, source_argument); if (d <= 0) { // the original is not larger, add it to the chain cx_linked_list_link(new_end, source_original, loc_prev, loc_next); new_end = source_original; source_original = ll_next(source_original); if (d == 0) { if (allow_duplicates) { // duplicate allowed, append it to the chain cx_linked_list_link(new_end, source_argument, loc_prev, loc_next); new_end = source_argument; } else { // duplicate is not allowed, append it to the duplicate chain if (dup_end == NULL) { dup_begin = dup_end = source_argument; } else { cx_linked_list_link(dup_end, source_argument, loc_prev, loc_next); dup_end = source_argument; } } source_argument = ll_next(source_argument); } } else { // the original is larger, append the source argument to the chain // check if we must discard the source argument as duplicate if (!allow_duplicates && cmp_func(new_end, source_argument) == 0) { if (dup_end == NULL) { dup_begin = dup_end = source_argument; } else { cx_linked_list_link(dup_end, source_argument, loc_prev, loc_next); dup_end = source_argument; } } else { // no duplicate or duplicates allowed cx_linked_list_link(new_end, source_argument, loc_prev, loc_next); new_end = source_argument; } source_argument = ll_next(source_argument); } } if (source_original != NULL) { // something is left from the original chain, append it cx_linked_list_link(new_end, source_original, loc_prev, loc_next); new_end = cx_linked_list_last(source_original, loc_next); } else if (source_argument != NULL) { // something is left from the input chain; // when we allow duplicates, append it if (allow_duplicates) { cx_linked_list_link(new_end, source_argument, loc_prev, loc_next); new_end = cx_linked_list_last(source_argument, loc_next); } else { // otherwise we must check one-by-one while (source_argument != NULL) { if (cmp_func(new_end, source_argument) == 0) { if (dup_end == NULL) { dup_begin = dup_end = source_argument; } else { cx_linked_list_link(dup_end, source_argument, loc_prev, loc_next); dup_end = source_argument; } } else { cx_linked_list_link(new_end, source_argument, loc_prev, loc_next); new_end = source_argument; } source_argument = ll_next(source_argument); } } } // null the next pointers at the end of the chain ll_next(new_end) = NULL; if (dup_end != NULL) { ll_next(dup_end) = NULL; } // null the optional prev pointers if (loc_prev >= 0) { ll_prev(new_begin) = NULL; if (dup_begin != NULL) { ll_prev(dup_begin) = NULL; } } // output *begin = new_begin; if (end != NULL) { *end = new_end; } return dup_begin; } 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 ) { cx_linked_list_insert_sorted_chain_impl( begin, end, loc_prev, loc_next, insert_begin, cmp_func, true); } 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 ) { assert(ll_next(new_node) == NULL); return NULL != cx_linked_list_insert_unique_chain( begin, end, loc_prev, loc_next, new_node, cmp_func); } 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 ) { return cx_linked_list_insert_sorted_chain_impl( begin, end, loc_prev, loc_next, insert_begin, cmp_func, false); } 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; } void cx_linked_list_remove( void **begin, void **end, ptrdiff_t loc_prev, ptrdiff_t loc_next, void *node ) { cx_linked_list_remove_chain(begin, end, loc_prev, loc_next, node, 1); } 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(); // LCOV_EXCL_LINE 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 static void *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, list->loc_prev, index); } else { return cx_linked_list_at(list->begin, 0, list->loc_next, index); } } static void *cx_ll_malloc_node(const cx_linked_list *list) { return cxZalloc(list->base.collection.allocator, list->loc_data + list->base.collection.elem_size + list->extra_data_len); } static int cx_ll_insert_at( struct cx_list_s *list, void *node, const void *elem ) { cx_linked_list *ll = (cx_linked_list *) list; // create the new new_node void *new_node = cx_ll_malloc_node(ll); // sortir if failed if (new_node == NULL) return 1; // copy the data if (elem != NULL) { memcpy((char*)new_node + ll->loc_data, elem, list->collection.elem_size); } // insert cx_linked_list_insert_chain( &ll->begin, &ll->end, ll->loc_prev, 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 void *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 cx_linked_list *ll = (cx_linked_list *) list; node = node == NULL ? ((cx_linked_list *) list)->begin : CX_LL_PTR(node, ll->loc_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++) { if (source != NULL) { source += list->collection.elem_size; } if (0 != cx_ll_insert_at(list, node, source)) return i; node = CX_LL_PTR(node, ll->loc_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 void *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 cx_linked_list *ll = (cx_linked_list *) list; if (node == NULL) { return (char*)(ll->begin) + ll->loc_data; } else { char *next = CX_LL_PTR(node, ll->loc_next); return next + ll->loc_data; } } static _Thread_local cx_compare_func cx_ll_insert_sorted_cmp_func; static _Thread_local off_t cx_ll_insert_sorted_loc_data; static int cx_ll_insert_sorted_cmp_helper(const void *l, const void *r) { const char *left = (const char*)l + cx_ll_insert_sorted_loc_data; const char *right = (const char*)r + cx_ll_insert_sorted_loc_data; return cx_ll_insert_sorted_cmp_func(left, right); } static size_t cx_ll_insert_sorted_impl( struct cx_list_s *list, const void *array, size_t n, bool allow_duplicates ) { cx_linked_list *ll = (cx_linked_list *) list; // special case if (n == 0) return 0; // create a new chain of nodes void *chain = cx_ll_malloc_node(ll); if (chain == NULL) return 0; memcpy((char*)chain + ll->loc_data, array, list->collection.elem_size); // add all elements from the array to that chain void *prev = chain; const char *src = array; size_t inserted = 1; for (; inserted < n; inserted++) { void *next = cx_ll_malloc_node(ll); if (next == NULL) break; src += list->collection.elem_size; memcpy((char*)next + ll->loc_data, src, list->collection.elem_size); CX_LL_PTR(prev, ll->loc_next) = next; CX_LL_PTR(next, ll->loc_prev) = prev; prev = next; } CX_LL_PTR(prev, ll->loc_next) = NULL; // invoke the low level function cx_ll_insert_sorted_cmp_func = list->collection.cmpfunc; cx_ll_insert_sorted_loc_data = ll->loc_data; if (allow_duplicates) { cx_linked_list_insert_sorted_chain( &ll->begin, &ll->end, ll->loc_prev, ll->loc_next, chain, cx_ll_insert_sorted_cmp_helper ); list->collection.size += inserted; } else { void *duplicates = cx_linked_list_insert_unique_chain( &ll->begin, &ll->end, ll->loc_prev, ll->loc_next, chain, cx_ll_insert_sorted_cmp_helper ); list->collection.size += inserted; // free the nodes that did not make it into the list while (duplicates != NULL) { void *next = CX_LL_PTR(duplicates, ll->loc_next); cxFree(list->collection.allocator, duplicates); duplicates = next; list->collection.size--; } } return inserted; } static size_t cx_ll_insert_sorted( struct cx_list_s *list, const void *array, size_t n ) { return cx_ll_insert_sorted_impl(list, array, n, true); } static size_t cx_ll_insert_unique( struct cx_list_s *list, const void *array, size_t n ) { return cx_ll_insert_sorted_impl(list, array, n, false); } 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; void *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, ll->loc_prev, ll->loc_next, node, num ); // adjust size list->collection.size -= removed; // copy or destroy the removed chain if (targetbuf == NULL) { char *n = node; for (size_t i = 0; i < removed; i++) { // element destruction cx_invoke_destructor(list, n + ll->loc_data); // free the node and advance void *next = CX_LL_PTR(n, ll->loc_next); cxFree(list->collection.allocator, n); n = next; } } else { char *dest = targetbuf; char *n = node; for (size_t i = 0; i < removed; i++) { // copy payload memcpy(dest, n + ll->loc_data, list->collection.elem_size); // advance target buffer dest += list->collection.elem_size; // free the node and advance void *next = CX_LL_PTR(n, ll->loc_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; char *node = ll->begin; while (node != NULL) { cx_invoke_destructor(list, node + ll->loc_data); void *next = CX_LL_PTR(node, ll->loc_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; } void *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 = CX_LL_PTR(nright, ll->loc_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 = CX_LL_PTR(nleft, ll->loc_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 = CX_LL_PTR(nright, ll->loc_next); } } else { nleft = nright; for (size_t c = right; c > left; c--) { nleft = CX_LL_PTR(nleft, ll->loc_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); } } } void *prev = CX_LL_PTR(nleft, ll->loc_prev); void *next = CX_LL_PTR(nright, ll->loc_next); void *midstart = CX_LL_PTR(nleft, ll->loc_next); void *midend = CX_LL_PTR(nright, ll->loc_prev); if (prev == NULL) { ll->begin = nright; } else { CX_LL_PTR(prev, ll->loc_next) = nright; } CX_LL_PTR(nright, ll->loc_prev) = prev; if (midstart == nright) { // special case: both nodes are adjacent CX_LL_PTR(nright, ll->loc_next) = nleft; CX_LL_PTR(nleft, ll->loc_prev) = nright; } else { // likely case: a chain is between the two nodes CX_LL_PTR(nright, ll->loc_next) = midstart; CX_LL_PTR(midstart, ll->loc_prev) = nright; CX_LL_PTR(midend, ll->loc_next) = nleft; CX_LL_PTR(nleft, ll->loc_prev) = midend; } CX_LL_PTR(nleft, ll->loc_next) = next; if (next == NULL) { ll->end = nleft; } else { CX_LL_PTR(next, ll->loc_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; char *node = cx_ll_node_at(ll, index); return node == NULL ? NULL : node + ll->loc_data; } 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; char *node = cx_linked_list_find( ll->begin, ll->loc_next, ll->loc_data, list->collection.cmpfunc, elem, &index ); if (node == NULL) { return list->collection.size; } if (remove) { cx_invoke_destructor(list, node + ll->loc_data); cx_linked_list_remove(&ll->begin, &ll->end, ll->loc_prev, 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(&ll->begin, &ll->end, ll->loc_prev, ll->loc_next, 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(&ll->begin, &ll->end, ll->loc_prev, 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; assert(left->loc_next == right->loc_next); assert(left->loc_data == right->loc_data); return cx_linked_list_compare(left->begin, right->begin, left->loc_next, left->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; cx_linked_list *ll = iter->src_handle; if (iter->base.remove) { iter->base.remove = false; struct cx_list_s *list = iter->src_handle; char *node = iter->elem_handle; iter->elem_handle = CX_LL_PTR(node, ll->loc_next); cx_invoke_destructor(list, node + ll->loc_data); cx_linked_list_remove(&ll->begin, &ll->end, ll->loc_prev, ll->loc_next, node); list->collection.size--; iter->elem_count--; cxFree(list->collection.allocator, node); } else { iter->index++; void *node = iter->elem_handle; iter->elem_handle = CX_LL_PTR(node, ll->loc_next); } } static void cx_ll_iter_prev(void *it) { struct cx_iterator_s *iter = it; cx_linked_list *ll = iter->src_handle; if (iter->base.remove) { iter->base.remove = false; struct cx_list_s *list = iter->src_handle; char *node = iter->elem_handle; iter->elem_handle = CX_LL_PTR(node, ll->loc_prev); iter->index--; cx_invoke_destructor(list, node + ll->loc_data); cx_linked_list_remove(&ll->begin, &ll->end, ll->loc_prev, ll->loc_next, node); list->collection.size--; iter->elem_count--; cxFree(list->collection.allocator, node); } else { iter->index--; char *node = iter->elem_handle; iter->elem_handle = CX_LL_PTR(node, ll->loc_prev); } } static void *cx_ll_iter_current(const void *it) { const struct cx_iterator_s *iter = it; const cx_linked_list *ll = iter->src_handle; char *node = iter->elem_handle; return node + ll->loc_data; } static CxIterator cx_ll_iterator( const struct cx_list_s *list, size_t index, bool backwards ) { CxIterator iter; iter.index = index; iter.src_handle = (void*)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.allow_remove = true; 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; cx_linked_list *ll = iter->src_handle; void *node = iter->elem_handle; if (node != NULL) { assert(prepend >= 0 && prepend <= 1); void *choice[2] = {node, CX_LL_PTR(node, ll->loc_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; // LCOV_EXCL_LINE } 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; char *node = ll->begin; while (node) { cx_invoke_destructor(list, node + ll->loc_data); void *next = CX_LL_PTR(node, ll->loc_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_unique, 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, NULL, // no overallocation supported 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; list->extra_data_len = 0; list->loc_prev = 0; list->loc_next = sizeof(void*); list->loc_data = sizeof(void*)*2; cx_list_init((CxList*)list, &cx_linked_list_class, allocator, comparator, elem_size); return (CxList *) list; }