arraykit 0.6.2__tar.gz → 0.7.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. {arraykit-0.6.2/arraykit.egg-info → arraykit-0.7.0}/PKG-INFO +1 -1
  2. {arraykit-0.6.2 → arraykit-0.7.0}/README.rst +15 -1
  3. {arraykit-0.6.2 → arraykit-0.7.0/arraykit.egg-info}/PKG-INFO +1 -1
  4. {arraykit-0.6.2 → arraykit-0.7.0}/setup.py +1 -1
  5. {arraykit-0.6.2 → arraykit-0.7.0}/src/__init__.py +2 -0
  6. {arraykit-0.6.2 → arraykit-0.7.0}/src/__init__.pyi +2 -0
  7. {arraykit-0.6.2 → arraykit-0.7.0}/src/_arraykit.c +210 -63
  8. {arraykit-0.6.2 → arraykit-0.7.0}/test/test_util.py +89 -0
  9. {arraykit-0.6.2 → arraykit-0.7.0}/LICENSE.txt +0 -0
  10. {arraykit-0.6.2 → arraykit-0.7.0}/MANIFEST.in +0 -0
  11. {arraykit-0.6.2 → arraykit-0.7.0}/arraykit.egg-info/SOURCES.txt +0 -0
  12. {arraykit-0.6.2 → arraykit-0.7.0}/arraykit.egg-info/dependency_links.txt +0 -0
  13. {arraykit-0.6.2 → arraykit-0.7.0}/arraykit.egg-info/requires.txt +0 -0
  14. {arraykit-0.6.2 → arraykit-0.7.0}/arraykit.egg-info/top_level.txt +0 -0
  15. {arraykit-0.6.2 → arraykit-0.7.0}/setup.cfg +0 -0
  16. {arraykit-0.6.2 → arraykit-0.7.0}/src/py.typed +0 -0
  17. {arraykit-0.6.2 → arraykit-0.7.0}/test/test_array_go.py +0 -0
  18. {arraykit-0.6.2 → arraykit-0.7.0}/test/test_block_index.py +0 -0
  19. {arraykit-0.6.2 → arraykit-0.7.0}/test/test_delimited_to_arrays.py +0 -0
  20. {arraykit-0.6.2 → arraykit-0.7.0}/test/test_delimited_to_arrays_integration.py +0 -0
  21. {arraykit-0.6.2 → arraykit-0.7.0}/test/test_delimited_to_arrays_property.py +0 -0
  22. {arraykit-0.6.2 → arraykit-0.7.0}/test/test_nonzero_1d.py +0 -0
  23. {arraykit-0.6.2 → arraykit-0.7.0}/test/test_nonzero_1d_property.py +0 -0
  24. {arraykit-0.6.2 → arraykit-0.7.0}/test/test_pyi.py +0 -0
  25. {arraykit-0.6.2 → arraykit-0.7.0}/test/test_split_after_count.py +0 -0
  26. {arraykit-0.6.2 → arraykit-0.7.0}/test/test_tri_map.py +0 -0
  27. {arraykit-0.6.2 → arraykit-0.7.0}/test/test_type_discovery.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: arraykit
3
- Version: 0.6.2
3
+ Version: 0.7.0
4
4
  Summary: Array utilities for StaticFrame
5
5
  Home-page: https://github.com/static-frame/arraykit
6
6
  Author: Christopher Ariza, Brandt Bucher, Charles Burkland
@@ -37,10 +37,24 @@ ArrayKit requires the following:
37
37
  What is New in ArrayKit
38
38
  -------------------------
39
39
 
40
+ 0.7.0
41
+ ............
42
+
43
+ Added ``array2d_to_array1d()``.
44
+
45
+ Added ``array2d_tuple_iter()``.
46
+
47
+
48
+ 0.6.3
49
+ ............
50
+
51
+ Optimized memory allocation strategy for ``nonzero_1d()``.
52
+
53
+
40
54
  0.6.2
41
55
  ............
42
56
 
43
- Extended ``nonzero_1d()`` to support non-contiguous arrays,
57
+ Extended ``nonzero_1d()`` to support non-contiguous arrays.
44
58
 
45
59
  Optimizations to ``TriMap`` when mapping to object and flexible dtypes.
46
60
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: arraykit
3
- Version: 0.6.2
3
+ Version: 0.7.0
4
4
  Summary: Array utilities for StaticFrame
5
5
  Home-page: https://github.com/static-frame/arraykit
6
6
  Author: Christopher Ariza, Brandt Bucher, Charles Burkland
@@ -5,7 +5,7 @@ from setuptools import Extension # type: ignore
5
5
  from setuptools import setup
6
6
  from pathlib import Path
7
7
 
8
- AK_VERSION = '0.6.2'
8
+ AK_VERSION = '0.7.0'
9
9
 
10
10
  def get_long_description() -> str:
11
11
  return '''The ArrayKit library provides utilities for creating and transforming NumPy arrays, implementing performance-critical StaticFrame operations as Python C extensions.
@@ -28,4 +28,6 @@ from ._arraykit import count_iteration as count_iteration
28
28
  from ._arraykit import first_true_1d as first_true_1d
29
29
  from ._arraykit import first_true_2d as first_true_2d
30
30
  from ._arraykit import slice_to_ascending_slice as slice_to_ascending_slice
31
+ from ._arraykit import array2d_to_array1d as array2d_to_array1d
32
+ from ._arraykit import array2d_tuple_iter as array2d_tuple_iter
31
33
  from ._arraykit import nonzero_1d as nonzero_1d
@@ -161,3 +161,5 @@ def first_true_1d(__array: np.ndarray, *, forward: bool) -> int: ...
161
161
  def first_true_2d(__array: np.ndarray, *, forward: bool, axis: int) -> np.ndarray: ...
162
162
  def nonzero_1d(__array: np.ndarray, /) -> np.ndarray: ...
163
163
  def slice_to_ascending_slice(__slice: slice, __size: int) -> slice: ...
164
+ def array2d_to_array1d(__array: np.ndarray) -> np.ndarray: ...
165
+ def array2d_tuple_iter(__array: np.ndarray) -> tp.Iterator[tp.Tuple[tp.Any, ...]]: ...
@@ -37,6 +37,18 @@ const static size_t UCS4_SIZE = sizeof(Py_UCS4);
37
37
  } \
38
38
  } while (0)
39
39
 
40
+ // Given a PyObject, raise if not an array or is not two dimensional.
41
+ # define AK_CHECK_NUMPY_ARRAY_2D(O) \
42
+ do { \
43
+ AK_CHECK_NUMPY_ARRAY(O) \
44
+ int ndim = PyArray_NDIM((PyArrayObject *)O); \
45
+ if (ndim != 2) { \
46
+ return PyErr_Format(PyExc_NotImplementedError,\
47
+ "Expected a 2D array, not %i.", \
48
+ ndim); \
49
+ } \
50
+ } while (0)
51
+
40
52
  // Placeholder of not implemented pathways / debugging.
41
53
  # define AK_NOT_IMPLEMENTED(msg) \
42
54
  do { \
@@ -836,9 +848,9 @@ AK_TP_ResolveLineResetField(AK_TypeParser* tp,
836
848
  static char* TRUE_LOWER = "true";
837
849
  static char* TRUE_UPPER = "TRUE";
838
850
 
839
- #define ERROR_NO_DIGITS 1
840
- #define ERROR_OVERFLOW 2
841
- #define ERROR_INVALID_CHARS 3
851
+ #define AK_ERROR_NO_DIGITS 1
852
+ #define AK_ERROR_OVERFLOW 2
853
+ #define AK_ERROR_INVALID_CHARS 3
842
854
 
843
855
  // Convert a Py_UCS4 array to a signed integer. Extended from pandas/_libs/src/parser/tokenizer.c. Sets `error` to values greater than 0 on error; never sets error on success.
844
856
  static inline npy_int64
@@ -866,7 +878,7 @@ AK_UCS4_to_int64(Py_UCS4 *p_item, Py_UCS4 *end, int *error, char tsep)
866
878
 
867
879
  // Check that there is a first digit.
868
880
  if (!AK_is_digit(*p)) {
869
- *error = ERROR_NO_DIGITS;
881
+ *error = AK_ERROR_NO_DIGITS;
870
882
  return 0;
871
883
  }
872
884
  if (isneg) {
@@ -891,7 +903,7 @@ AK_UCS4_to_int64(Py_UCS4 *p_item, Py_UCS4 *end, int *error, char tsep)
891
903
  if (p >= end) return number;
892
904
  d = *p;
893
905
  } else {
894
- *error = ERROR_OVERFLOW;
906
+ *error = AK_ERROR_OVERFLOW;
895
907
  return 0;
896
908
  }
897
909
  }
@@ -904,7 +916,7 @@ AK_UCS4_to_int64(Py_UCS4 *p_item, Py_UCS4 *end, int *error, char tsep)
904
916
  if (p >= end) return number;
905
917
  d = *p;
906
918
  } else {
907
- *error = ERROR_OVERFLOW;
919
+ *error = AK_ERROR_OVERFLOW;
908
920
  return 0;
909
921
  }
910
922
  }
@@ -931,7 +943,7 @@ AK_UCS4_to_int64(Py_UCS4 *p_item, Py_UCS4 *end, int *error, char tsep)
931
943
  if (p >= end) return number;
932
944
  d = *p;
933
945
  } else {
934
- *error = ERROR_OVERFLOW;
946
+ *error = AK_ERROR_OVERFLOW;
935
947
  return 0;
936
948
  }
937
949
  }
@@ -944,7 +956,7 @@ AK_UCS4_to_int64(Py_UCS4 *p_item, Py_UCS4 *end, int *error, char tsep)
944
956
  if (p >= end) return number;
945
957
  d = *p;
946
958
  } else {
947
- *error = ERROR_OVERFLOW;
959
+ *error = AK_ERROR_OVERFLOW;
948
960
  return 0;
949
961
  }
950
962
  }
@@ -952,7 +964,7 @@ AK_UCS4_to_int64(Py_UCS4 *p_item, Py_UCS4 *end, int *error, char tsep)
952
964
  }
953
965
  while (p < end) {
954
966
  if (!AK_is_space(*p)) {
955
- *error = ERROR_INVALID_CHARS;
967
+ *error = AK_ERROR_INVALID_CHARS;
956
968
  return 0;
957
969
  }
958
970
  p++;
@@ -975,7 +987,7 @@ AK_UCS4_to_uint64(Py_UCS4 *p_item, Py_UCS4 *end, int *error, char tsep)
975
987
  if (p >= end) return number;
976
988
  }
977
989
  if (*p == '-') {
978
- *error = ERROR_INVALID_CHARS;
990
+ *error = AK_ERROR_INVALID_CHARS;
979
991
  return 0;
980
992
  } else if (*p == '+') {
981
993
  p++;
@@ -984,7 +996,7 @@ AK_UCS4_to_uint64(Py_UCS4 *p_item, Py_UCS4 *end, int *error, char tsep)
984
996
 
985
997
  // Check that there is a first digit.
986
998
  if (!AK_is_digit(*p)) {
987
- *error = ERROR_NO_DIGITS;
999
+ *error = AK_ERROR_NO_DIGITS;
988
1000
  return 0;
989
1001
  }
990
1002
  // If number is less than pre_max, at least one more digit can be processed without overflowing.
@@ -1006,7 +1018,7 @@ AK_UCS4_to_uint64(Py_UCS4 *p_item, Py_UCS4 *end, int *error, char tsep)
1006
1018
  if (p >= end) return number;
1007
1019
  d = *p;
1008
1020
  } else {
1009
- *error = ERROR_OVERFLOW;
1021
+ *error = AK_ERROR_OVERFLOW;
1010
1022
  return 0;
1011
1023
  }
1012
1024
  }
@@ -1019,14 +1031,14 @@ AK_UCS4_to_uint64(Py_UCS4 *p_item, Py_UCS4 *end, int *error, char tsep)
1019
1031
  if (p >= end) return number;
1020
1032
  d = *p;
1021
1033
  } else {
1022
- *error = ERROR_OVERFLOW;
1034
+ *error = AK_ERROR_OVERFLOW;
1023
1035
  return 0;
1024
1036
  }
1025
1037
  }
1026
1038
  }
1027
1039
  while (p < end) {
1028
1040
  if (!AK_is_space(*p)) {
1029
- *error = ERROR_INVALID_CHARS;
1041
+ *error = AK_ERROR_INVALID_CHARS;
1030
1042
  return 0;
1031
1043
  }
1032
1044
  p++;
@@ -3511,6 +3523,167 @@ array_deepcopy(PyObject *m, PyObject *args, PyObject *kwargs)
3511
3523
  return AK_ArrayDeepCopy(m, (PyArrayObject*)array, memo);
3512
3524
  }
3513
3525
 
3526
+ //------------------------------------------------------------------------------
3527
+ // Given a 2D array, return a 1D object array of tuples.
3528
+ static PyObject *
3529
+ array2d_to_array1d(PyObject *Py_UNUSED(m), PyObject *a)
3530
+ {
3531
+ AK_CHECK_NUMPY_ARRAY_2D(a);
3532
+ PyArrayObject *input_array = (PyArrayObject *)a;
3533
+
3534
+ npy_intp num_rows = PyArray_DIM(input_array, 0);
3535
+ npy_intp num_cols = PyArray_DIM(input_array, 1);
3536
+
3537
+ npy_intp dims[] = {num_rows};
3538
+ // NOTE: this initializes values to NULL, not None
3539
+ PyObject* output = PyArray_SimpleNew(1, dims, NPY_OBJECT);
3540
+ if (output == NULL) {
3541
+ return NULL;
3542
+ }
3543
+
3544
+ PyObject** output_data = (PyObject**)PyArray_DATA((PyArrayObject*)output);
3545
+ PyObject** p = output_data;
3546
+ PyObject** p_end = p + num_rows;
3547
+ npy_intp i = 0;
3548
+ npy_intp j;
3549
+ PyObject* tuple;
3550
+ PyObject* item;
3551
+
3552
+ while (p < p_end) {
3553
+ tuple = PyTuple_New(num_cols);
3554
+ if (tuple == NULL) {
3555
+ goto error;
3556
+ }
3557
+ for (j = 0; j < num_cols; ++j) {
3558
+ // cannot assume input_array is contiguous
3559
+ item = PyArray_ToScalar(PyArray_GETPTR2(input_array, i, j), input_array);
3560
+ if (item == NULL) {
3561
+ Py_DECREF(tuple);
3562
+ goto error;
3563
+ }
3564
+ PyTuple_SET_ITEM(tuple, j, item); // steals reference to item
3565
+ }
3566
+ *p++ = tuple; // assign with new ref, no incr needed
3567
+ i++;
3568
+ }
3569
+ PyArray_CLEARFLAGS((PyArrayObject *)output, NPY_ARRAY_WRITEABLE);
3570
+ return output;
3571
+ error:
3572
+ p = output_data;
3573
+ p_end = p + num_rows;
3574
+ while (p < p_end) { // decref all tuples within array
3575
+ Py_XDECREF(*p++); // xdec as might be NULL
3576
+ }
3577
+ Py_DECREF(output);
3578
+ return NULL;
3579
+ }
3580
+
3581
+ //------------------------------------------------------------------------------
3582
+ // Array2DTuple Iterator
3583
+
3584
+ static PyTypeObject A2DTupleType;
3585
+
3586
+ typedef struct A2DTupleObject {
3587
+ PyObject_HEAD
3588
+ PyArrayObject* array;
3589
+ npy_intp num_rows;
3590
+ npy_intp num_cols;
3591
+ Py_ssize_t pos; // current index state, mutated in-place
3592
+ } A2DTupleObject;
3593
+
3594
+ static PyObject *
3595
+ A2DTuple_new(PyArrayObject* array,
3596
+ npy_intp num_rows,
3597
+ npy_intp num_cols) {
3598
+ A2DTupleObject* a2dt = PyObject_New(A2DTupleObject, &A2DTupleType);
3599
+ if (!a2dt) {
3600
+ return NULL;
3601
+ }
3602
+ Py_INCREF((PyObject*)array);
3603
+ a2dt->array = array;
3604
+ a2dt->num_rows = num_rows;
3605
+ a2dt->num_cols = num_cols;
3606
+ a2dt->pos = 0;
3607
+ return (PyObject *)a2dt;
3608
+ }
3609
+
3610
+ static void
3611
+ A2DTuple_dealloc(A2DTupleObject *self) {
3612
+ Py_DECREF((PyObject*)self->array);
3613
+ PyObject_Del((PyObject*)self);
3614
+ }
3615
+
3616
+ static PyObject*
3617
+ A2DTuple_iter(A2DTupleObject *self) {
3618
+ Py_INCREF(self);
3619
+ return (PyObject*)self;
3620
+ }
3621
+
3622
+ static PyObject *
3623
+ A2DTuple_iternext(A2DTupleObject *self) {
3624
+ Py_ssize_t i = self->pos;
3625
+ if (i < self->num_rows) {
3626
+ npy_intp num_cols = self->num_cols;
3627
+ PyArrayObject* array = self->array;
3628
+ PyObject* tuple = PyTuple_New(num_cols);
3629
+ PyObject* item;
3630
+ if (tuple == NULL) {
3631
+ return NULL;
3632
+ }
3633
+ for (npy_intp j = 0; j < num_cols; ++j) {
3634
+ // cannot assume array is contiguous
3635
+ item = PyArray_ToScalar(PyArray_GETPTR2(array, i, j), array);
3636
+ if (item == NULL) {
3637
+ Py_DECREF(tuple);
3638
+ return NULL;
3639
+ }
3640
+ PyTuple_SET_ITEM(tuple, j, item); // steals reference to item
3641
+ }
3642
+ self->pos++;
3643
+ return tuple;
3644
+ }
3645
+ return NULL;
3646
+ }
3647
+
3648
+ // static PyObject *
3649
+ // A2DTuple_reversed(A2DTupleObject *self) {
3650
+ // return A2DTuple_new(self->bi, !self->reversed);
3651
+ // }
3652
+
3653
+ static PyObject *
3654
+ A2DTuple_length_hint(A2DTupleObject *self) {
3655
+ Py_ssize_t len = Py_MAX(0, self->num_rows - self->pos);
3656
+ return PyLong_FromSsize_t(len);
3657
+ }
3658
+
3659
+ static PyMethodDef A2DTuple_methods[] = {
3660
+ {"__length_hint__", (PyCFunction)A2DTuple_length_hint, METH_NOARGS, NULL},
3661
+ // {"__reversed__", (PyCFunction)A2DTuple_reversed, METH_NOARGS, NULL},
3662
+ {NULL},
3663
+ };
3664
+
3665
+ static PyTypeObject A2DTupleType = {
3666
+ PyVarObject_HEAD_INIT(NULL, 0)
3667
+ .tp_basicsize = sizeof(A2DTupleObject),
3668
+ .tp_dealloc = (destructor) A2DTuple_dealloc,
3669
+ .tp_iter = (getiterfunc) A2DTuple_iter,
3670
+ .tp_iternext = (iternextfunc) A2DTuple_iternext,
3671
+ .tp_methods = A2DTuple_methods,
3672
+ .tp_name = "arraykit.A2DTupleIterator",
3673
+ };
3674
+
3675
+ // Given a 2D array, return an iterator of row tuples.
3676
+ static PyObject *
3677
+ array2d_tuple_iter(PyObject *Py_UNUSED(m), PyObject *a)
3678
+ {
3679
+ AK_CHECK_NUMPY_ARRAY_2D(a);
3680
+ PyArrayObject* array = (PyArrayObject *)a;
3681
+ npy_intp num_rows = PyArray_DIM(array, 0);
3682
+ npy_intp num_cols = PyArray_DIM(array, 1);
3683
+ return A2DTuple_new(array, num_rows, num_cols);
3684
+ }
3685
+
3686
+
3514
3687
  //------------------------------------------------------------------------------
3515
3688
  // type resolution
3516
3689
 
@@ -3535,32 +3708,9 @@ resolve_dtype_iter(PyObject *Py_UNUSED(m), PyObject *arg) {
3535
3708
  //------------------------------------------------------------------------------
3536
3709
  // general utility
3537
3710
 
3538
- #define NONZERO_APPEND_INDEX_RELATIVE { \
3539
- if (AK_UNLIKELY(count == capacity)) { \
3540
- capacity <<= 1; \
3541
- indices = (npy_int64*)realloc(indices, sizeof(npy_int64) * capacity);\
3542
- if (indices == NULL) { \
3543
- return NULL; \
3544
- } \
3545
- } \
3546
- indices[count++] = p - p_start; \
3547
- } \
3548
-
3549
- #define NONZERO_APPEND_INDEX_ABSOLUTE { \
3550
- if (AK_UNLIKELY(count == capacity)) { \
3551
- capacity <<= 1; \
3552
- indices = (npy_int64*)realloc(indices, sizeof(npy_int64) * capacity);\
3553
- if (indices == NULL) { \
3554
- return NULL; \
3555
- } \
3556
- } \
3557
- indices[count++] = i; \
3558
- } \
3559
-
3560
- // Given a Boolean, contiguous 1D array, return the index positions in an int64 array. Through experimentation it has been verified that doing full-size allocation of memory does not permit outperforming NumPy at 10_000_000 scale; but doing less optimizations does help. Using bit masks does not improve perforamnce over pointer arithmetic. Prescanning for all empty is very effective. Note that NumPy befits from first counting the nonzeros, then allocating only enough data for the expexted number.
3711
+ // Given a Boolean, contiguous 1D array, return the index positions in an int64 array. Through experimentation it has been verified that doing full-size allocation of memory provides the best performance at all scales. Using NpyIter, or using, bit masks does not improve performance over pointer arithmetic. Prescanning for all empty is very effective. Note that NumPy benefits from first counting the nonzeros, then allocating only enough data for the expexted number of indices.
3561
3712
  static inline PyObject*
3562
3713
  AK_nonzero_1d(PyArrayObject* array) {
3563
- // the maxiumum number of indices we could return is the size of the array; if this is under a certain number, probably better to just allocate that rather than reallocate
3564
3714
  PyObject* final;
3565
3715
  npy_intp count_max = PyArray_SIZE(array);
3566
3716
 
@@ -3573,8 +3723,8 @@ AK_nonzero_1d(PyArrayObject* array) {
3573
3723
  lldiv_t size_div = lldiv((long long)count_max, 8); // quot, rem
3574
3724
 
3575
3725
  Py_ssize_t count = 0;
3576
- // the maximum number of collected integers is equal to or less than count_max; for small count_max, we can just set that value; for large size, we set it to half the size
3577
- Py_ssize_t capacity = count_max < 1024 ? count_max : count_max / 8;
3726
+ // the maximum number of collected integers is equal to or less than count_max
3727
+ Py_ssize_t capacity = count_max;
3578
3728
  npy_int64* indices = (npy_int64*)malloc(sizeof(npy_int64) * capacity);
3579
3729
 
3580
3730
  NPY_BEGIN_THREADS_DEF;
@@ -3591,25 +3741,25 @@ AK_nonzero_1d(PyArrayObject* array) {
3591
3741
  p += 8; // no true within this 8 byte roll region
3592
3742
  continue;
3593
3743
  }
3594
- if (*p) {NONZERO_APPEND_INDEX_RELATIVE;}
3744
+ if (*p) {indices[count++] = p - p_start;}
3595
3745
  p++;
3596
- if (*p) {NONZERO_APPEND_INDEX_RELATIVE;}
3746
+ if (*p) {indices[count++] = p - p_start;}
3597
3747
  p++;
3598
- if (*p) {NONZERO_APPEND_INDEX_RELATIVE;}
3748
+ if (*p) {indices[count++] = p - p_start;}
3599
3749
  p++;
3600
- if (*p) {NONZERO_APPEND_INDEX_RELATIVE;}
3750
+ if (*p) {indices[count++] = p - p_start;}
3601
3751
  p++;
3602
- if (*p) {NONZERO_APPEND_INDEX_RELATIVE;}
3752
+ if (*p) {indices[count++] = p - p_start;}
3603
3753
  p++;
3604
- if (*p) {NONZERO_APPEND_INDEX_RELATIVE;}
3754
+ if (*p) {indices[count++] = p - p_start;}
3605
3755
  p++;
3606
- if (*p) {NONZERO_APPEND_INDEX_RELATIVE;}
3756
+ if (*p) {indices[count++] = p - p_start;}
3607
3757
  p++;
3608
- if (*p) {NONZERO_APPEND_INDEX_RELATIVE;}
3758
+ if (*p) {indices[count++] = p - p_start;}
3609
3759
  p++;
3610
3760
  }
3611
3761
  while (p < p_end) {
3612
- if (*p) {NONZERO_APPEND_INDEX_RELATIVE;}
3762
+ if (*p) {indices[count++] = p - p_start;}
3613
3763
  p++;
3614
3764
  }
3615
3765
  }
@@ -3618,25 +3768,25 @@ AK_nonzero_1d(PyArrayObject* array) {
3618
3768
  npy_intp i_end = count_max;
3619
3769
  npy_intp i_end_roll = count_max - size_div.rem;
3620
3770
  while (i < i_end_roll) {
3621
- if (*(npy_bool*)PyArray_GETPTR1(array, i)) {NONZERO_APPEND_INDEX_ABSOLUTE;}
3771
+ if (*(npy_bool*)PyArray_GETPTR1(array, i)) {indices[count++] = i;}
3622
3772
  i++;
3623
- if (*(npy_bool*)PyArray_GETPTR1(array, i)) {NONZERO_APPEND_INDEX_ABSOLUTE;}
3773
+ if (*(npy_bool*)PyArray_GETPTR1(array, i)) {indices[count++] = i;}
3624
3774
  i++;
3625
- if (*(npy_bool*)PyArray_GETPTR1(array, i)) {NONZERO_APPEND_INDEX_ABSOLUTE;}
3775
+ if (*(npy_bool*)PyArray_GETPTR1(array, i)) {indices[count++] = i;}
3626
3776
  i++;
3627
- if (*(npy_bool*)PyArray_GETPTR1(array, i)) {NONZERO_APPEND_INDEX_ABSOLUTE;}
3777
+ if (*(npy_bool*)PyArray_GETPTR1(array, i)) {indices[count++] = i;}
3628
3778
  i++;
3629
- if (*(npy_bool*)PyArray_GETPTR1(array, i)) {NONZERO_APPEND_INDEX_ABSOLUTE;}
3779
+ if (*(npy_bool*)PyArray_GETPTR1(array, i)) {indices[count++] = i;}
3630
3780
  i++;
3631
- if (*(npy_bool*)PyArray_GETPTR1(array, i)) {NONZERO_APPEND_INDEX_ABSOLUTE;}
3781
+ if (*(npy_bool*)PyArray_GETPTR1(array, i)) {indices[count++] = i;}
3632
3782
  i++;
3633
- if (*(npy_bool*)PyArray_GETPTR1(array, i)) {NONZERO_APPEND_INDEX_ABSOLUTE;}
3783
+ if (*(npy_bool*)PyArray_GETPTR1(array, i)) {indices[count++] = i;}
3634
3784
  i++;
3635
- if (*(npy_bool*)PyArray_GETPTR1(array, i)) {NONZERO_APPEND_INDEX_ABSOLUTE;}
3785
+ if (*(npy_bool*)PyArray_GETPTR1(array, i)) {indices[count++] = i;}
3636
3786
  i++;
3637
3787
  }
3638
3788
  while (i < i_end) {
3639
- if (*(npy_bool*)PyArray_GETPTR1(array, i)) {NONZERO_APPEND_INDEX_ABSOLUTE;}
3789
+ if (*(npy_bool*)PyArray_GETPTR1(array, i)) {indices[count++] = i;}
3640
3790
  i++;
3641
3791
  }
3642
3792
  }
@@ -3653,8 +3803,6 @@ AK_nonzero_1d(PyArrayObject* array) {
3653
3803
  PyArray_CLEARFLAGS((PyArrayObject*)final, NPY_ARRAY_WRITEABLE);
3654
3804
  return final;
3655
3805
  }
3656
- #undef NONZERO_APPEND_INDEX_RELATIVE
3657
- #undef NONZERO_APPEND_INDEX_ABSOLUTE
3658
3806
 
3659
3807
  static PyObject*
3660
3808
  nonzero_1d(PyObject *Py_UNUSED(m), PyObject *a) {
@@ -5969,7 +6117,6 @@ TriMap_dealloc(TriMapObject *self) {
5969
6117
  if (self->many_from != NULL) {
5970
6118
  // decref all arrays before freeing
5971
6119
  for (Py_ssize_t i = 0; i < self->many_count; i++) {
5972
- // NOTE: using dot to get to pointer?
5973
6120
  Py_DECREF((PyObject*)self->many_from[i].dst);
5974
6121
  }
5975
6122
  PyMem_Free(self->many_from);
@@ -6102,14 +6249,12 @@ TriMap_register_unmatched_dst(TriMapObject *self) {
6102
6249
  return NULL;
6103
6250
  }
6104
6251
  // derive indices for unmatched locations, call each with register_one
6105
- // PyObject* nonzero = PyArray_Nonzero(dst_unmatched);
6106
6252
  PyArrayObject* indices = (PyArrayObject*)AK_nonzero_1d(dst_unmatched);
6107
6253
  if (indices == NULL) {
6108
6254
  Py_DECREF((PyObject*)dst_unmatched);
6109
6255
  return NULL;
6110
6256
  }
6111
6257
  // borrow ref to array in 1-element tuple
6112
- // PyArrayObject *indices = (PyArrayObject*)PyTuple_GET_ITEM(nonzero, 0);
6113
6258
  npy_int64 *index_data = (npy_int64 *)PyArray_DATA(indices);
6114
6259
  npy_intp index_len = PyArray_SIZE(indices);
6115
6260
 
@@ -7286,6 +7431,8 @@ static PyMethodDef arraykit_methods[] = {
7286
7431
  (PyCFunction)array_deepcopy,
7287
7432
  METH_VARARGS | METH_KEYWORDS,
7288
7433
  NULL},
7434
+ {"array2d_to_array1d", array2d_to_array1d, METH_O, NULL},
7435
+ {"array2d_tuple_iter", array2d_tuple_iter, METH_O, NULL},
7289
7436
  {"resolve_dtype", resolve_dtype, METH_VARARGS, NULL},
7290
7437
  {"resolve_dtype_iter", resolve_dtype_iter, METH_O, NULL},
7291
7438
  {"first_true_1d",
@@ -22,6 +22,8 @@ from arraykit import count_iteration
22
22
  from arraykit import first_true_1d
23
23
  from arraykit import first_true_2d
24
24
  from arraykit import slice_to_ascending_slice
25
+ from arraykit import array2d_to_array1d
26
+ from arraykit import array2d_tuple_iter
25
27
 
26
28
  from performance.reference.util import get_new_indexers_and_screen_ak as get_new_indexers_and_screen_full
27
29
  from arraykit import get_new_indexers_and_screen
@@ -283,6 +285,93 @@ class TestUnit(unittest.TestCase):
283
285
  with self.assertRaises(TypeError):
284
286
  a2 = array_deepcopy(a1, ())
285
287
 
288
+ #---------------------------------------------------------------------------
289
+ def test_array2d_to_array1d_dummy(self) -> None:
290
+ a1 = np.arange(10)
291
+ with self.assertRaises(NotImplementedError):
292
+ # 1 dimensional
293
+ _ = array2d_to_array1d(a1)
294
+
295
+ def test_array2d_to_array1d_b(self) -> None:
296
+ a1 = np.arange(10, dtype=np.int64).reshape(5, 2)
297
+ result = array2d_to_array1d(a1)
298
+ assert isinstance(result[0], tuple)
299
+ assert result[0] == (0, 1)
300
+ self.assertIs(type(result[0][0]), np.int64)
301
+ self.assertFalse(result.flags.writeable)
302
+ self.assertEqual(tuple(result), ((0, 1), (2, 3), (4, 5), (6, 7), (8, 9)))
303
+
304
+
305
+ def test_array2d_to_array1d_c(self) -> None:
306
+ a1 = np.array([["a", "b"], ["ccc", "ddd"], ["ee", "ff"]])
307
+ a2 = array2d_to_array1d(a1)
308
+ self.assertEqual(a2.tolist(), [('a', 'b'), ('ccc', 'ddd'), ('ee', 'ff')])
309
+
310
+ def test_array2d_to_array1d_d(self) -> None:
311
+ a1 = np.array([[3, 5], [10, 20], [7, 2]], dtype=np.uint8)
312
+ a2 = array2d_to_array1d(a1)
313
+ self.assertEqual(a2.tolist(), [(3, 5), (10, 20), (7, 2)])
314
+ self.assertIs(type(a2[0][0]), np.uint8)
315
+
316
+ def test_array2d_to_array1d_e(self) -> None:
317
+ a1 = np.arange(20, dtype=np.int64).reshape(4, 5)
318
+ result = array2d_to_array1d(a1)
319
+ self.assertEqual(result.tolist(), [(0, 1, 2, 3, 4), (5, 6, 7, 8, 9), (10, 11, 12, 13, 14), (15, 16, 17, 18, 19)])
320
+
321
+ #---------------------------------------------------------------------------
322
+ def test_array2d_tuple_iter_a(self) -> None:
323
+ a1 = np.arange(20, dtype=np.int64).reshape(4, 5)
324
+ result = list(array2d_tuple_iter(a1))
325
+ self.assertEqual(len(result), 4)
326
+ self.assertEqual(result, [(0, 1, 2, 3, 4), (5, 6, 7, 8, 9), (10, 11, 12, 13, 14), (15, 16, 17, 18, 19)])
327
+
328
+ def test_array2d_tuple_iter_b(self) -> None:
329
+ a1 = np.arange(20, dtype=np.int64).reshape(10, 2)
330
+ result = list(array2d_tuple_iter(a1))
331
+ self.assertEqual(result, [(0, 1), (2, 3), (4, 5), (6, 7), (8, 9), (10, 11), (12, 13), (14, 15), (16, 17), (18, 19)])
332
+
333
+ def test_array2d_tuple_iter_c(self) -> None:
334
+ a1 = np.array([['aaa', 'bb'], ['c', 'dd'], ['ee', 'fffff']])
335
+ it = array2d_tuple_iter(a1)
336
+ self.assertEqual(it.__length_hint__(), 3)
337
+ self.assertEqual(next(it), ('aaa', 'bb'))
338
+ self.assertEqual(it.__length_hint__(), 2)
339
+ self.assertEqual(next(it), ('c', 'dd'))
340
+ self.assertEqual(it.__length_hint__(), 1)
341
+ self.assertEqual(next(it), ('ee', 'fffff'))
342
+ self.assertEqual(it.__length_hint__(), 0)
343
+ with self.assertRaises(StopIteration):
344
+ next(it)
345
+
346
+ def test_array2d_tuple_iter_d(self) -> None:
347
+ a1 = np.array([['aaa', 'bb'], ['c', 'dd'], ['ee', 'fffff']])
348
+ it = array2d_tuple_iter(a1)
349
+ # __reversed__ not implemented
350
+ with self.assertRaises(TypeError):
351
+ reversed(it)
352
+
353
+ def test_array2d_tuple_iter_e(self) -> None:
354
+ a1 = np.array([[None, 'bb'], [None, 'dd'], [3, None]])
355
+ it = array2d_tuple_iter(a1)
356
+ del a1
357
+ self.assertEqual(list(it), [(None, 'bb'), (None, 'dd'), (3, None)])
358
+
359
+ def test_array2d_tuple_iter_f(self) -> None:
360
+ a1 = np.array([[None, 'bb'], [None, 'dd'], [3, None]])
361
+ it1 = array2d_tuple_iter(a1)
362
+ del a1
363
+ it2 = iter(it1)
364
+ self.assertEqual(list(it1), [(None, 'bb'), (None, 'dd'), (3, None)])
365
+ self.assertEqual(list(it2), []) # expected behavior
366
+
367
+ def test_array2d_tuple_iter_g(self) -> None:
368
+ a1 = np.array([[None, 'bb'], [None, 'dd'], [3, None]])
369
+ it1 = array2d_tuple_iter(a1)
370
+ it2 = array2d_tuple_iter(a1)
371
+ del a1
372
+ self.assertEqual(list(it1), [(None, 'bb'), (None, 'dd'), (3, None)])
373
+ self.assertEqual(list(it2), [(None, 'bb'), (None, 'dd'), (3, None)])
374
+
286
375
  #---------------------------------------------------------------------------
287
376
 
288
377
  def test_isna_element_a(self) -> None:
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes