|
1 | 1 | #include "_listobject.h" |
2 | 2 |
|
3 | 3 | /* This implements the C component of the Numba typed list. It is loosely |
4 | | - * inspired by the list implementation of the cpython list. The exact commit |
5 | | - * is: |
| 4 | + * inspired by the list implementation of the cpython list with some parts |
| 5 | + * taken from the cpython slice implementation. The exact commit-id of the |
| 6 | + * relevant files are: |
6 | 7 | * |
7 | 8 | * https://github.com/python/cpython/blob/51ddab8dae056867f3595ab3400bffc93f67c8d4/Objects/listobject.c |
| 9 | + * https://github.com/python/cpython/blob/51ddab8dae056867f3595ab3400bffc93f67c8d4/Objects/sliceobject.c |
8 | 10 | * |
9 | 11 | * Algorithmically, this list is very similar to the cpython implementation so |
10 | 12 | * it should have the same performance (Big-O) characteristics for accessing, |
|
32 | 34 | * - Setting an item numba_list_getitem |
33 | 35 | * - Poping an item numba_list_pop |
34 | 36 | * - Resizing the list numba_list_resize |
| 37 | + * - Deleting a slice numba_list_delete_slice |
35 | 38 | * |
36 | | - * As you can see, no functions related to slicing have been implemented. This |
| 39 | + * As you can see, only a single function for slices is implemented. The rest |
37 | 40 | * is all done entirely at the compiler level which then calls the c functions |
38 | 41 | * to mutate the list accordingly. Since slicing allows for replace, insert and |
39 | 42 | * delete operations over multiple items, we can simply implement those using |
@@ -384,6 +387,106 @@ numba_list_resize(NB_List *lp, Py_ssize_t newsize) { |
384 | 387 | return LIST_OK; |
385 | 388 | } |
386 | 389 |
|
| 390 | +/* Delete a slice |
| 391 | + * |
| 392 | + * start: the start index of ths slice |
| 393 | + * stop: the stop index of the slice (not included) |
| 394 | + * step: the step to take |
| 395 | + * |
| 396 | + * This function assumes that the start and stop were clipped appropriately. |
| 397 | + * I.e. if step > 0 start >= 0 and stop <= len(l) and |
| 398 | + * if step < 0 start <= length and stop >= -1 |
| 399 | + * step != 0 and no Python negative indexing allowed. |
| 400 | + * |
| 401 | + * This code was copied and edited from the relevant section in |
| 402 | + * list_ass_subscript from the cpython implementation, see the top of this file |
| 403 | + * for the exact source |
| 404 | + */ |
| 405 | +int |
| 406 | +numba_list_delete_slice(NB_List *lp, |
| 407 | + Py_ssize_t start, Py_ssize_t stop, Py_ssize_t step) { |
| 408 | + int result, i, slicelength, new_length; |
| 409 | + char *loc, *new_loc; |
| 410 | + Py_ssize_t leftover_bytes, cur, lim; |
| 411 | + // calculate the slicelength, taken from PySlice_AdjustIndices, see the top |
| 412 | + // of this file for the exact source |
| 413 | + if (step > 0) { |
| 414 | + slicelength = start < stop ? (stop - start - 1) / step + 1 : 0; |
| 415 | + } else { |
| 416 | + slicelength = stop < start ? (start - stop - 1) / -step + 1 : 0; |
| 417 | + } |
| 418 | + if (slicelength <= 0){ |
| 419 | + return LIST_OK; |
| 420 | + } |
| 421 | + new_length = lp->size - slicelength; |
| 422 | + // reverse step and indices |
| 423 | + if (step < 0) { |
| 424 | + stop = start + 1; |
| 425 | + start = stop + step * (slicelength - 1) - 1; |
| 426 | + step = -step; |
| 427 | + } |
| 428 | + if (step == 1) { |
| 429 | + // decref if needed |
| 430 | + if (lp->methods.item_decref) { |
| 431 | + for (i = start ; i < stop ; i++){ |
| 432 | + loc = lp->items + lp->item_size * i; |
| 433 | + lp->methods.item_decref(loc); |
| 434 | + } |
| 435 | + } |
| 436 | + // memove items into place |
| 437 | + leftover_bytes = (lp->size - stop) * lp->item_size; |
| 438 | + loc = lp->items + lp->item_size * start; |
| 439 | + new_loc = lp->items + lp->item_size * stop; |
| 440 | + memmove(loc, new_loc, leftover_bytes); |
| 441 | + } |
| 442 | + else { // step != 1 |
| 443 | + /* drawing pictures might help understand these for |
| 444 | + * loops. Basically, we memmove the parts of the |
| 445 | + * list that are *not* part of the slice: step-1 |
| 446 | + * items for each item that is part of the slice, |
| 447 | + * and then tail end of the list that was not |
| 448 | + * covered by the slice |
| 449 | + * |
| 450 | + * */ |
| 451 | + for (cur = start, // index of item to be deleted |
| 452 | + i = 0; // counter of total items deleted so far |
| 453 | + cur < stop; |
| 454 | + cur += step, |
| 455 | + i++) { |
| 456 | + lim = step - 1; // number of leftover items after deletion of item |
| 457 | + // clip limit, in case we are at the end of the slice, and there |
| 458 | + // are now less than step-1 items to be moved |
| 459 | + if (cur + step >= lp->size) { |
| 460 | + lim = lp->size - cur - 1; |
| 461 | + } |
| 462 | + // decref item being removed |
| 463 | + loc = lp->items + lp->item_size * cur; |
| 464 | + list_decref_item(lp, loc); |
| 465 | + /* memmove the aforementiond step-1 (or less) items |
| 466 | + * dst : index of deleted item minus total deleted sofar |
| 467 | + * src : index of deleted item plus one (next item) |
| 468 | + */ |
| 469 | + memmove(lp->items + lp->item_size * (cur - i), |
| 470 | + lp->items + lp->item_size * (cur + 1), |
| 471 | + lim * lp->item_size); |
| 472 | + } |
| 473 | + // memmove tail of the list |
| 474 | + cur = start + slicelength * step; |
| 475 | + if (cur < lp->size) { |
| 476 | + memmove(lp->items + lp->item_size * (cur - slicelength), |
| 477 | + lp->items + lp->item_size * cur, |
| 478 | + (lp->size - cur) * lp->item_size); |
| 479 | + } |
| 480 | + } |
| 481 | + // resize to correct size |
| 482 | + result = numba_list_resize(lp, new_length); |
| 483 | + if(result < LIST_OK) { |
| 484 | + // Since we are decreasing the size, this should never happen |
| 485 | + return result; |
| 486 | + } |
| 487 | + return LIST_OK; |
| 488 | +} |
| 489 | + |
387 | 490 |
|
388 | 491 | /* Return the size of the list iterator (NB_ListIter) struct. |
389 | 492 | */ |
@@ -446,6 +549,7 @@ numba_test_list(void) { |
446 | 549 | NB_ListIter iter; |
447 | 550 | char got_item[4] = "\x00\x00\x00\x00"; |
448 | 551 | const char *test_items_1 = NULL, *test_items_2 = NULL; |
| 552 | + char *test_items_3 = NULL; |
449 | 553 | puts("test_list"); |
450 | 554 |
|
451 | 555 |
|
@@ -610,11 +714,156 @@ numba_test_list(void) { |
610 | 714 | case 1: CHECK(lp->allocated == 0); break; |
611 | 715 | } |
612 | 716 | } |
| 717 | + // free existing list |
| 718 | + numba_list_free(lp); |
| 719 | + |
| 720 | + |
| 721 | + // Setup list for testing delete_slice |
| 722 | + status = numba_list_new(&lp, 1, 0); |
| 723 | + CHECK(status == LIST_OK); |
| 724 | + CHECK(lp->item_size == 1); |
| 725 | + CHECK(lp->size == 0); |
| 726 | + CHECK(lp->allocated == 0); |
| 727 | + for (i = 0; i < 17 ; i++) { |
| 728 | + status = numba_list_append(lp, (const char*)&i); |
| 729 | + CHECK(status == LIST_OK); |
| 730 | + } |
| 731 | + CHECK(lp->size == 17); |
| 732 | + test_items_3 = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10"; |
| 733 | + CHECK(memcmp(lp->items, test_items_3, 17) == 0); |
| 734 | + |
| 735 | + // delete multiple elements from the middle |
| 736 | + status = numba_list_delete_slice(lp, 2, 5, 1); |
| 737 | + CHECK(status == LIST_OK); |
| 738 | + CHECK(lp->size == 14); |
| 739 | + test_items_3 = "\x00\x01\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10"; |
| 740 | + CHECK(memcmp(lp->items, test_items_3, 14) == 0); |
| 741 | + |
| 742 | + // delete single element from start |
| 743 | + status = numba_list_delete_slice(lp, 0, 1, 1); |
| 744 | + CHECK(status == LIST_OK); |
| 745 | + CHECK(lp->size == 13); |
| 746 | + test_items_3 = "\x01\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10"; |
| 747 | + CHECK(memcmp(lp->items, test_items_3, 13) == 0); |
| 748 | + |
| 749 | + // delete single element from end |
| 750 | + status = numba_list_delete_slice(lp, 12, 13, 1); |
| 751 | + CHECK(status == LIST_OK); |
| 752 | + CHECK(lp->size == 12); |
| 753 | + test_items_3 = "\x01\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"; |
| 754 | + CHECK(memcmp(lp->items, test_items_3, 12) == 0); |
| 755 | + |
| 756 | + // delete single element from middle |
| 757 | + status = numba_list_delete_slice(lp, 4, 5, 1); |
| 758 | + CHECK(status == LIST_OK); |
| 759 | + CHECK(lp->size == 11); |
| 760 | + test_items_3 = "\x01\x05\x06\x07\x09\x0a\x0b\x0c\x0d\x0e\x0f"; |
| 761 | + CHECK(memcmp(lp->items, test_items_3, 11) == 0); |
| 762 | + |
| 763 | + // delete all elements except first and last |
| 764 | + status = numba_list_delete_slice(lp, 1, 10, 1); |
| 765 | + CHECK(status == LIST_OK); |
| 766 | + CHECK(lp->size == 2); |
| 767 | + test_items_3 = "\x01\x0f"; |
| 768 | + CHECK(memcmp(lp->items, test_items_3, 2) == 0); |
| 769 | + |
| 770 | + // delete all remaining elements |
| 771 | + status = numba_list_delete_slice(lp, 0, lp->size, 1); |
| 772 | + CHECK(status == LIST_OK); |
| 773 | + CHECK(lp->size == 0); |
| 774 | + test_items_3 = ""; |
| 775 | + CHECK(memcmp(lp->items, test_items_3, 0) == 0); |
| 776 | + |
| 777 | + // free existing list |
| 778 | + numba_list_free(lp); |
| 779 | + |
| 780 | + // Setup list for testing delete_slice with non unary step |
| 781 | + status = numba_list_new(&lp, 1, 0); |
| 782 | + CHECK(status == LIST_OK); |
| 783 | + CHECK(lp->item_size == 1); |
| 784 | + CHECK(lp->size == 0); |
| 785 | + CHECK(lp->allocated == 0); |
| 786 | + for (i = 0; i < 17 ; i++) { |
| 787 | + status = numba_list_append(lp, (const char*)&i); |
| 788 | + CHECK(status == LIST_OK); |
| 789 | + } |
| 790 | + CHECK(lp->size == 17); |
| 791 | + |
| 792 | + // delete all items with odd index |
| 793 | + status = numba_list_delete_slice(lp, 0, 17, 2); |
| 794 | + CHECK(status == LIST_OK); |
| 795 | + CHECK(lp->size == 8); |
| 796 | + test_items_3 = "\x01\x03\x05\x07\x09\x0b\x0d\x0f"; |
| 797 | + CHECK(memcmp(lp->items, test_items_3, 8) == 0); |
| 798 | + |
| 799 | + // delete with a step of 4, starting at index 1 |
| 800 | + status = numba_list_delete_slice(lp, 1, 8, 4); |
| 801 | + CHECK(status == LIST_OK); |
| 802 | + CHECK(lp->size == 6); |
| 803 | + test_items_3 = "\x01\x05\x07\x09\x0d\x0f"; |
| 804 | + CHECK(memcmp(lp->items, test_items_3, 6) == 0); |
| 805 | + |
| 806 | + // delete with a step of 2, but finish before end of list |
| 807 | + status = numba_list_delete_slice(lp, 0, 4, 2); |
| 808 | + CHECK(status == LIST_OK); |
| 809 | + CHECK(lp->size == 4); |
| 810 | + test_items_3 = "\x05\x09\x0d\x0f"; |
| 811 | + CHECK(memcmp(lp->items, test_items_3, 4) == 0); |
| 812 | + |
| 813 | + // no-op on empty slice |
| 814 | + status = numba_list_delete_slice(lp, 0, 0, 1); |
| 815 | + CHECK(status == LIST_OK); |
| 816 | + CHECK(lp->size == 4); |
| 817 | + test_items_3 = "\x05\x09\x0d\x0f"; |
| 818 | + CHECK(memcmp(lp->items, test_items_3, 4) == 0); |
| 819 | + |
| 820 | + // no-op on empty slice, non-zero index |
| 821 | + status = numba_list_delete_slice(lp, 2, 2, 1); |
| 822 | + CHECK(status == LIST_OK); |
| 823 | + CHECK(lp->size == 4); |
| 824 | + test_items_3 = "\x05\x09\x0d\x0f"; |
| 825 | + CHECK(memcmp(lp->items, test_items_3, 4) == 0); |
| 826 | + |
| 827 | + // free list and return 0 |
| 828 | + numba_list_free(lp); |
| 829 | + |
| 830 | + // Setup list for testing delete_slice with negative step |
| 831 | + status = numba_list_new(&lp, 1, 0); |
| 832 | + CHECK(status == LIST_OK); |
| 833 | + CHECK(lp->item_size == 1); |
| 834 | + CHECK(lp->size == 0); |
| 835 | + CHECK(lp->allocated == 0); |
| 836 | + for (i = 0; i < 17 ; i++) { |
| 837 | + status = numba_list_append(lp, (const char*)&i); |
| 838 | + CHECK(status == LIST_OK); |
| 839 | + } |
| 840 | + CHECK(lp->size == 17); |
| 841 | + |
| 842 | + // delete all items using unary negative slice |
| 843 | + status = numba_list_delete_slice(lp, 16, -1, -1); |
| 844 | + CHECK(status == LIST_OK); |
| 845 | + CHECK(lp->size == 0); |
| 846 | + |
| 847 | + // refill list |
| 848 | + for (i = 0; i < 17 ; i++) { |
| 849 | + status = numba_list_append(lp, (const char*)&i); |
| 850 | + CHECK(status == LIST_OK); |
| 851 | + } |
| 852 | + |
| 853 | + // delete all items using unary negative slice |
| 854 | + // need to start at index of last item (16) and |
| 855 | + // go beyond first item, i.e. -1 in Cd |
| 856 | + status = numba_list_delete_slice(lp, 16, -1, -2); |
| 857 | + CHECK(status == LIST_OK); |
| 858 | + CHECK(lp->size == 8); |
| 859 | + test_items_3 = "\x01\x03\x05\x07\x09\x0b\x0d\x0f"; |
| 860 | + CHECK(memcmp(lp->items, test_items_3, 8) == 0); |
613 | 861 |
|
614 | 862 | // free list and return 0 |
615 | 863 | numba_list_free(lp); |
616 | 864 | return 0; |
617 | 865 |
|
| 866 | + |
618 | 867 | } |
619 | 868 |
|
620 | 869 | #undef CHECK |
0 commit comments