src/array_list.c

Mon, 20 Mar 2023 18:05:12 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Mon, 20 Mar 2023 18:05:12 +0100
changeset 665
c4041b07165e
parent 664
af5bf4603a5d
child 666
b5dd654deb3b
permissions
-rw-r--r--

fix hashmap iterator skipping the second element in some cases

universe@606 1 /*
universe@606 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
universe@606 3 *
universe@606 4 * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
universe@606 5 *
universe@606 6 * Redistribution and use in source and binary forms, with or without
universe@606 7 * modification, are permitted provided that the following conditions are met:
universe@606 8 *
universe@606 9 * 1. Redistributions of source code must retain the above copyright
universe@606 10 * notice, this list of conditions and the following disclaimer.
universe@606 11 *
universe@606 12 * 2. Redistributions in binary form must reproduce the above copyright
universe@606 13 * notice, this list of conditions and the following disclaimer in the
universe@606 14 * documentation and/or other materials provided with the distribution.
universe@606 15 *
universe@606 16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
universe@606 17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
universe@606 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
universe@606 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
universe@606 20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
universe@606 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
universe@606 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
universe@606 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
universe@606 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
universe@606 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
universe@606 26 * POSSIBILITY OF SUCH DAMAGE.
universe@606 27 */
universe@606 28
universe@606 29 #include "cx/array_list.h"
universe@610 30 #include <assert.h>
universe@610 31 #include <string.h>
universe@606 32
universe@628 33 // LOW LEVEL ARRAY LIST FUNCTIONS
universe@607 34
universe@612 35 enum cx_array_copy_result cx_array_copy(
universe@610 36 void **target,
universe@610 37 size_t *size,
universe@610 38 size_t *capacity,
universe@610 39 size_t index,
universe@610 40 void const *src,
universe@610 41 size_t elem_size,
universe@610 42 size_t elem_count,
universe@610 43 struct cx_array_reallocator_s *reallocator
universe@610 44 ) {
universe@628 45 // assert pointers
universe@610 46 assert(target != NULL);
universe@610 47 assert(size != NULL);
universe@610 48 assert(src != NULL);
universe@607 49
universe@628 50 // determine capacity
universe@610 51 size_t cap = capacity == NULL ? *size : *capacity;
universe@610 52
universe@628 53 // check if resize is required
universe@627 54 size_t minsize = index + elem_count;
universe@627 55 size_t newsize = *size < minsize ? minsize : *size;
universe@610 56 bool needrealloc = newsize > cap;
universe@610 57
universe@628 58 // reallocate if possible
universe@610 59 if (needrealloc) {
universe@628 60 // a reallocator and a capacity variable must be available
universe@610 61 if (reallocator == NULL || capacity == NULL) {
universe@610 62 return CX_ARRAY_COPY_REALLOC_NOT_SUPPORTED;
universe@610 63 }
universe@610 64
universe@628 65 // check, if we need to repair the src pointer
universe@611 66 uintptr_t targetaddr = (uintptr_t) *target;
universe@611 67 uintptr_t srcaddr = (uintptr_t) src;
universe@611 68 bool repairsrc = targetaddr <= srcaddr
universe@611 69 && srcaddr < targetaddr + cap * elem_size;
universe@611 70
universe@628 71 // calculate new capacity (next number divisible by 16)
universe@625 72 cap = newsize - (newsize % 16) + 16;
universe@625 73 assert(cap > newsize);
universe@610 74
universe@628 75 // perform reallocation
universe@610 76 void *newmem = reallocator->realloc(
universe@610 77 *target, cap, elem_size, reallocator
universe@610 78 );
universe@610 79 if (newmem == NULL) {
universe@610 80 return CX_ARRAY_COPY_REALLOC_FAILED;
universe@610 81 }
universe@610 82
universe@628 83 // repair src pointer, if necessary
universe@611 84 if (repairsrc) {
universe@611 85 src = ((char *) newmem) + (srcaddr - targetaddr);
universe@611 86 }
universe@611 87
universe@628 88 // store new pointer and capacity
universe@610 89 *target = newmem;
universe@610 90 *capacity = cap;
universe@610 91 }
universe@610 92
universe@628 93 // determine target pointer
universe@610 94 char *start = *target;
universe@610 95 start += index * elem_size;
universe@610 96
universe@628 97 // copy elements and set new size
universe@611 98 memmove(start, src, elem_count * elem_size);
universe@610 99 *size = newsize;
universe@610 100
universe@628 101 // return successfully
universe@610 102 return CX_ARRAY_COPY_SUCCESS;
universe@610 103 }
universe@607 104
universe@643 105 #ifndef CX_ARRAY_SWAP_SBO_SIZE
universe@623 106 #define CX_ARRAY_SWAP_SBO_SIZE 512
universe@643 107 #endif
universe@623 108
universe@623 109 void cx_array_swap(
universe@623 110 void *arr,
universe@623 111 size_t elem_size,
universe@623 112 size_t idx1,
universe@623 113 size_t idx2
universe@623 114 ) {
universe@660 115 assert(arr != NULL);
universe@660 116
universe@628 117 // short circuit
universe@623 118 if (idx1 == idx2) return;
universe@623 119
universe@623 120 char sbo_mem[CX_ARRAY_SWAP_SBO_SIZE];
universe@623 121 void *tmp;
universe@623 122
universe@628 123 // decide if we can use the local buffer
universe@623 124 if (elem_size > CX_ARRAY_SWAP_SBO_SIZE) {
universe@623 125 tmp = malloc(elem_size);
universe@628 126 // we don't want to enforce error handling
universe@623 127 if (tmp == NULL) abort();
universe@623 128 } else {
universe@623 129 tmp = sbo_mem;
universe@623 130 }
universe@623 131
universe@628 132 // calculate memory locations
universe@623 133 char *left = arr, *right = arr;
universe@623 134 left += idx1 * elem_size;
universe@623 135 right += idx2 * elem_size;
universe@623 136
universe@628 137 // three-way swap
universe@623 138 memcpy(tmp, left, elem_size);
universe@623 139 memcpy(left, right, elem_size);
universe@623 140 memcpy(right, tmp, elem_size);
universe@623 141
universe@628 142 // free dynamic memory, if it was needed
universe@623 143 if (tmp != sbo_mem) {
universe@623 144 free(tmp);
universe@623 145 }
universe@623 146 }
universe@623 147
universe@628 148 // HIGH LEVEL ARRAY LIST FUNCTIONS
universe@607 149
universe@607 150 typedef struct {
universe@607 151 struct cx_list_s base;
universe@607 152 void *data;
universe@610 153 struct cx_array_reallocator_s reallocator;
universe@607 154 } cx_array_list;
universe@607 155
universe@610 156 static void *cx_arl_realloc(
universe@610 157 void *array,
universe@610 158 size_t capacity,
universe@610 159 size_t elem_size,
universe@610 160 struct cx_array_reallocator_s *alloc
universe@610 161 ) {
universe@628 162 // retrieve the pointer to the list allocator
universe@610 163 CxAllocator const *al = alloc->ptr1;
universe@610 164
universe@628 165 // use the list allocator to reallocate the memory
universe@610 166 return cxRealloc(al, array, capacity * elem_size);
universe@610 167 }
universe@610 168
universe@607 169 static void cx_arl_destructor(struct cx_list_s *list) {
universe@610 170 cx_array_list *arl = (cx_array_list *) list;
universe@607 171 cxFree(list->allocator, arl->data);
universe@607 172 }
universe@607 173
universe@638 174 static size_t cx_arl_insert_array(
universe@629 175 struct cx_list_s *list,
universe@638 176 size_t index,
universe@629 177 void const *array,
universe@629 178 size_t n
universe@629 179 ) {
universe@638 180 // out of bounds and special case check
universe@638 181 if (index > list->size || n == 0) return 0;
universe@638 182
universe@638 183 // get a correctly typed pointer to the list
universe@629 184 cx_array_list *arl = (cx_array_list *) list;
universe@638 185
universe@638 186 // do we need to move some elements?
universe@638 187 if (index < list->size) {
universe@638 188 char const *first_to_move = (char const *) arl->data;
universe@638 189 first_to_move += index * list->itemsize;
universe@638 190 size_t elems_to_move = list->size - index;
universe@638 191 size_t start_of_moved = index + n;
universe@638 192
universe@638 193 if (CX_ARRAY_COPY_SUCCESS != cx_array_copy(
universe@638 194 &arl->data,
universe@638 195 &list->size,
universe@638 196 &list->capacity,
universe@638 197 start_of_moved,
universe@638 198 first_to_move,
universe@638 199 list->itemsize,
universe@638 200 elems_to_move,
universe@638 201 &arl->reallocator
universe@638 202 )) {
universe@638 203 // if moving existing elems is unsuccessful, abort
universe@638 204 return 0;
universe@638 205 }
universe@638 206 }
universe@638 207
universe@638 208 // note that if we had to move the elements, the following operation
universe@638 209 // is guaranteed to succeed, because we have the memory already allocated
universe@638 210 // therefore, it is impossible to leave this function with an invalid array
universe@638 211
universe@638 212 // place the new elements
universe@629 213 if (CX_ARRAY_COPY_SUCCESS == cx_array_copy(
universe@629 214 &arl->data,
universe@629 215 &list->size,
universe@629 216 &list->capacity,
universe@638 217 index,
universe@629 218 array,
universe@629 219 list->itemsize,
universe@629 220 n,
universe@629 221 &arl->reallocator
universe@629 222 )) {
universe@629 223 return n;
universe@629 224 } else {
universe@629 225 // array list implementation is "all or nothing"
universe@629 226 return 0;
universe@629 227 }
universe@629 228 }
universe@629 229
universe@641 230 static int cx_arl_insert_element(
universe@641 231 struct cx_list_s *list,
universe@641 232 size_t index,
universe@641 233 void const *element
universe@641 234 ) {
universe@641 235 return 1 != cx_arl_insert_array(list, index, element, 1);
universe@641 236 }
universe@641 237
universe@607 238 static int cx_arl_insert_iter(
universe@630 239 struct cx_mut_iterator_s *iter,
universe@607 240 void const *elem,
universe@607 241 int prepend
universe@607 242 ) {
universe@619 243 struct cx_list_s *list = iter->src_handle;
universe@619 244 if (iter->index < list->size) {
universe@641 245 int result = cx_arl_insert_element(
universe@619 246 list,
universe@619 247 iter->index + 1 - prepend,
universe@641 248 elem
universe@619 249 );
universe@619 250 if (result == 0 && prepend != 0) {
universe@619 251 iter->index++;
universe@619 252 iter->elem_handle = ((char *) iter->elem_handle) + list->itemsize;
universe@619 253 }
universe@619 254 return result;
universe@619 255 } else {
universe@641 256 int result = cx_arl_insert_element(list, list->size, elem);
universe@619 257 iter->index = list->size;
universe@619 258 return result;
universe@619 259 }
universe@607 260 }
universe@607 261
universe@607 262 static int cx_arl_remove(
universe@607 263 struct cx_list_s *list,
universe@607 264 size_t index
universe@607 265 ) {
universe@664 266 cx_array_list *arl = (cx_array_list *) list;
universe@664 267
universe@628 268 // out-of-bounds check
universe@613 269 if (index >= list->size) {
universe@613 270 return 1;
universe@613 271 }
universe@613 272
universe@664 273 // content destruction
universe@664 274 if (list->content_destructor_type != CX_DESTRUCTOR_NONE) {
universe@664 275 char *ptr = arl->data;
universe@664 276 ptr += index * list->itemsize;
universe@664 277 cx_list_invoke_destructor(list, ptr);
universe@664 278 }
universe@664 279
universe@628 280 // short-circuit removal of last element
universe@624 281 if (index == list->size - 1) {
universe@624 282 list->size--;
universe@624 283 return 0;
universe@624 284 }
universe@613 285
universe@628 286 // just move the elements starting at index to the left
universe@613 287 int result = cx_array_copy(
universe@613 288 &arl->data,
universe@613 289 &list->size,
universe@613 290 &list->capacity,
universe@613 291 index,
universe@613 292 ((char *) arl->data) + (index + 1) * list->itemsize,
universe@613 293 list->itemsize,
universe@626 294 list->size - index - 1,
universe@613 295 &arl->reallocator
universe@613 296 );
universe@613 297 if (result == 0) {
universe@628 298 // decrease the size
universe@613 299 list->size--;
universe@613 300 }
universe@613 301 return result;
universe@607 302 }
universe@607 303
universe@664 304 static void cx_arl_clear(struct cx_list_s *list) {
universe@664 305 if (list->size == 0) return;
universe@664 306
universe@664 307 cx_array_list *arl = (cx_array_list *) list;
universe@664 308 char *ptr = arl->data;
universe@664 309
universe@664 310 switch (list->content_destructor_type) {
universe@664 311 case CX_DESTRUCTOR_SIMPLE: {
universe@664 312 for (size_t i = 0; i < list->size; i++) {
universe@664 313 list->simple_destructor(ptr);
universe@664 314 ptr += list->itemsize;
universe@664 315 }
universe@664 316 break;
universe@664 317 }
universe@664 318 case CX_DESTRUCTOR_ADVANCED: {
universe@664 319 for (size_t i = 0; i < list->size; i++) {
universe@664 320 list->advanced_destructor.func(list->advanced_destructor.data,
universe@664 321 ptr);
universe@664 322 ptr += list->itemsize;
universe@664 323 }
universe@664 324 break;
universe@664 325 }
universe@664 326 case CX_DESTRUCTOR_NONE:
universe@664 327 break; // nothing
universe@664 328 }
universe@664 329 }
universe@664 330
universe@647 331 static int cx_arl_swap(
universe@647 332 struct cx_list_s *list,
universe@647 333 size_t i,
universe@647 334 size_t j
universe@647 335 ) {
universe@647 336 if (i >= list->size || j >= list->size) return 1;
universe@647 337 cx_array_list *arl = (cx_array_list *) list;
universe@647 338 cx_array_swap(arl->data, list->itemsize, i, j);
universe@647 339 return 0;
universe@647 340 }
universe@647 341
universe@610 342 static void *cx_arl_at(
universe@607 343 struct cx_list_s const *list,
universe@607 344 size_t index
universe@607 345 ) {
universe@610 346 if (index < list->size) {
universe@610 347 cx_array_list const *arl = (cx_array_list const *) list;
universe@610 348 char *space = arl->data;
universe@610 349 return space + index * list->itemsize;
universe@610 350 } else {
universe@610 351 return NULL;
universe@610 352 }
universe@607 353 }
universe@607 354
universe@607 355 static size_t cx_arl_find(
universe@607 356 struct cx_list_s const *list,
universe@607 357 void const *elem
universe@607 358 ) {
universe@660 359 assert(list->cmpfunc != NULL);
universe@614 360 char *cur = ((cx_array_list const *) list)->data;
universe@614 361
universe@614 362 for (size_t i = 0; i < list->size; i++) {
universe@614 363 if (0 == list->cmpfunc(elem, cur)) {
universe@614 364 return i;
universe@614 365 }
universe@614 366 cur += list->itemsize;
universe@614 367 }
universe@614 368
universe@614 369 return list->size;
universe@607 370 }
universe@607 371
universe@607 372 static void cx_arl_sort(struct cx_list_s *list) {
universe@660 373 assert(list->cmpfunc != NULL);
universe@615 374 qsort(((cx_array_list *) list)->data,
universe@615 375 list->size,
universe@615 376 list->itemsize,
universe@615 377 list->cmpfunc
universe@615 378 );
universe@607 379 }
universe@607 380
universe@607 381 static int cx_arl_compare(
universe@607 382 struct cx_list_s const *list,
universe@607 383 struct cx_list_s const *other
universe@607 384 ) {
universe@660 385 assert(list->cmpfunc != NULL);
universe@622 386 if (list->size == other->size) {
universe@622 387 char const *left = ((cx_array_list const *) list)->data;
universe@622 388 char const *right = ((cx_array_list const *) other)->data;
universe@622 389 for (size_t i = 0; i < list->size; i++) {
universe@622 390 int d = list->cmpfunc(left, right);
universe@622 391 if (d != 0) {
universe@622 392 return d;
universe@622 393 }
universe@622 394 left += list->itemsize;
universe@622 395 right += other->itemsize;
universe@622 396 }
universe@622 397 return 0;
universe@622 398 } else {
universe@622 399 return list->size < other->size ? -1 : 1;
universe@622 400 }
universe@607 401 }
universe@607 402
universe@607 403 static void cx_arl_reverse(struct cx_list_s *list) {
universe@623 404 if (list->size < 2) return;
universe@623 405 void *data = ((cx_array_list const *) list)->data;
universe@623 406 size_t half = list->size / 2;
universe@623 407 for (size_t i = 0; i < half; i++) {
universe@623 408 cx_array_swap(data, list->itemsize, i, list->size - 1 - i);
universe@623 409 }
universe@607 410 }
universe@607 411
universe@630 412 static bool cx_arl_iter_valid(void const *it) {
universe@630 413 struct cx_iterator_s const *iter = it;
universe@616 414 struct cx_list_s const *list = iter->src_handle;
universe@616 415 return iter->index < list->size;
universe@616 416 }
universe@616 417
universe@630 418 static void *cx_arl_iter_current(void const *it) {
universe@630 419 struct cx_iterator_s const *iter = it;
universe@616 420 return iter->elem_handle;
universe@616 421 }
universe@616 422
universe@630 423 static void cx_arl_iter_next(void *it) {
universe@630 424 struct cx_iterator_base_s *itbase = it;
universe@630 425 if (itbase->remove) {
universe@630 426 struct cx_mut_iterator_s *iter = it;
universe@630 427 itbase->remove = false;
universe@616 428 cx_arl_remove(iter->src_handle, iter->index);
universe@616 429 } else {
universe@630 430 struct cx_iterator_s *iter = it;
universe@616 431 iter->index++;
universe@620 432 iter->elem_handle =
universe@620 433 ((char *) iter->elem_handle)
universe@620 434 + ((struct cx_list_s const *) iter->src_handle)->itemsize;
universe@616 435 }
universe@616 436 }
universe@616 437
universe@655 438 static void cx_arl_iter_prev(void *it) {
universe@655 439 struct cx_iterator_base_s *itbase = it;
universe@655 440 struct cx_mut_iterator_s *iter = it;
universe@655 441 cx_array_list *const list = iter->src_handle;
universe@655 442 if (itbase->remove) {
universe@655 443 itbase->remove = false;
universe@655 444 cx_arl_remove(iter->src_handle, iter->index);
universe@655 445 }
universe@655 446 iter->index--;
universe@655 447 if (iter->index < list->base.size) {
universe@655 448 iter->elem_handle = ((char *) list->data)
universe@655 449 + iter->index * list->base.itemsize;
universe@655 450 }
universe@655 451 }
universe@655 452
universe@630 453 static bool cx_arl_iter_flag_rm(void *it) {
universe@630 454 struct cx_iterator_base_s *iter = it;
universe@630 455 if (iter->mutating) {
universe@630 456 iter->remove = true;
universe@630 457 return true;
universe@630 458 } else {
universe@630 459 return false;
universe@630 460 }
universe@630 461 }
universe@630 462
universe@607 463 static struct cx_iterator_s cx_arl_iterator(
universe@630 464 struct cx_list_s const *list,
universe@655 465 size_t index,
universe@655 466 bool backwards
universe@607 467 ) {
universe@607 468 struct cx_iterator_s iter;
universe@607 469
universe@616 470 iter.index = index;
universe@616 471 iter.src_handle = list;
universe@616 472 iter.elem_handle = cx_arl_at(list, index);
universe@630 473 iter.base.valid = cx_arl_iter_valid;
universe@630 474 iter.base.current = cx_arl_iter_current;
universe@655 475 iter.base.next = backwards ? cx_arl_iter_prev : cx_arl_iter_next;
universe@630 476 iter.base.flag_removal = cx_arl_iter_flag_rm;
universe@630 477 iter.base.remove = false;
universe@630 478 iter.base.mutating = false;
universe@616 479
universe@607 480 return iter;
universe@607 481 }
universe@607 482
universe@607 483 static cx_list_class cx_array_list_class = {
universe@607 484 cx_arl_destructor,
universe@641 485 cx_arl_insert_element,
universe@638 486 cx_arl_insert_array,
universe@607 487 cx_arl_insert_iter,
universe@607 488 cx_arl_remove,
universe@664 489 cx_arl_clear,
universe@647 490 cx_arl_swap,
universe@607 491 cx_arl_at,
universe@607 492 cx_arl_find,
universe@607 493 cx_arl_sort,
universe@607 494 cx_arl_compare,
universe@607 495 cx_arl_reverse,
universe@607 496 cx_arl_iterator,
universe@607 497 };
universe@607 498
universe@662 499 static CxList *cx_array_list_create(
universe@606 500 CxAllocator const *allocator,
universe@606 501 CxListComparator comparator,
universe@606 502 size_t item_size,
universe@606 503 size_t initial_capacity
universe@606 504 ) {
universe@607 505 cx_array_list *list = cxCalloc(allocator, 1, sizeof(cx_array_list));
universe@607 506 if (list == NULL) return NULL;
universe@607 507
universe@607 508 list->data = cxCalloc(allocator, initial_capacity, item_size);
universe@607 509 if (list->data == NULL) {
universe@607 510 cxFree(allocator, list);
universe@607 511 return NULL;
universe@607 512 }
universe@607 513
universe@607 514 list->base.cl = &cx_array_list_class;
universe@607 515 list->base.allocator = allocator;
universe@607 516 list->base.cmpfunc = comparator;
universe@607 517 list->base.itemsize = item_size;
universe@607 518 list->base.capacity = initial_capacity;
universe@607 519
universe@628 520 // configure the reallocator
universe@610 521 list->reallocator.realloc = cx_arl_realloc;
universe@610 522 list->reallocator.ptr1 = (void *) allocator;
universe@610 523
universe@607 524 return (CxList *) list;
universe@606 525 }
universe@662 526
universe@662 527 CxList *cxArrayListCreate(
universe@662 528 CxAllocator const *allocator,
universe@662 529 CxListComparator comparator,
universe@662 530 size_t item_size,
universe@662 531 size_t initial_capacity
universe@662 532 ) {
universe@662 533 return cx_array_list_create(allocator, comparator,
universe@662 534 item_size, initial_capacity);
universe@662 535 }
universe@662 536
universe@662 537 CxList *cxArrayListCreateSimple(
universe@662 538 size_t item_size,
universe@662 539 size_t initial_capacity
universe@662 540 ) {
universe@662 541 return cx_array_list_create(cxDefaultAllocator, NULL,
universe@662 542 item_size, initial_capacity);
universe@662 543 }

mercurial