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.
- {arraykit-0.6.2/arraykit.egg-info → arraykit-0.7.0}/PKG-INFO +1 -1
- {arraykit-0.6.2 → arraykit-0.7.0}/README.rst +15 -1
- {arraykit-0.6.2 → arraykit-0.7.0/arraykit.egg-info}/PKG-INFO +1 -1
- {arraykit-0.6.2 → arraykit-0.7.0}/setup.py +1 -1
- {arraykit-0.6.2 → arraykit-0.7.0}/src/__init__.py +2 -0
- {arraykit-0.6.2 → arraykit-0.7.0}/src/__init__.pyi +2 -0
- {arraykit-0.6.2 → arraykit-0.7.0}/src/_arraykit.c +210 -63
- {arraykit-0.6.2 → arraykit-0.7.0}/test/test_util.py +89 -0
- {arraykit-0.6.2 → arraykit-0.7.0}/LICENSE.txt +0 -0
- {arraykit-0.6.2 → arraykit-0.7.0}/MANIFEST.in +0 -0
- {arraykit-0.6.2 → arraykit-0.7.0}/arraykit.egg-info/SOURCES.txt +0 -0
- {arraykit-0.6.2 → arraykit-0.7.0}/arraykit.egg-info/dependency_links.txt +0 -0
- {arraykit-0.6.2 → arraykit-0.7.0}/arraykit.egg-info/requires.txt +0 -0
- {arraykit-0.6.2 → arraykit-0.7.0}/arraykit.egg-info/top_level.txt +0 -0
- {arraykit-0.6.2 → arraykit-0.7.0}/setup.cfg +0 -0
- {arraykit-0.6.2 → arraykit-0.7.0}/src/py.typed +0 -0
- {arraykit-0.6.2 → arraykit-0.7.0}/test/test_array_go.py +0 -0
- {arraykit-0.6.2 → arraykit-0.7.0}/test/test_block_index.py +0 -0
- {arraykit-0.6.2 → arraykit-0.7.0}/test/test_delimited_to_arrays.py +0 -0
- {arraykit-0.6.2 → arraykit-0.7.0}/test/test_delimited_to_arrays_integration.py +0 -0
- {arraykit-0.6.2 → arraykit-0.7.0}/test/test_delimited_to_arrays_property.py +0 -0
- {arraykit-0.6.2 → arraykit-0.7.0}/test/test_nonzero_1d.py +0 -0
- {arraykit-0.6.2 → arraykit-0.7.0}/test/test_nonzero_1d_property.py +0 -0
- {arraykit-0.6.2 → arraykit-0.7.0}/test/test_pyi.py +0 -0
- {arraykit-0.6.2 → arraykit-0.7.0}/test/test_split_after_count.py +0 -0
- {arraykit-0.6.2 → arraykit-0.7.0}/test/test_tri_map.py +0 -0
- {arraykit-0.6.2 → arraykit-0.7.0}/test/test_type_discovery.py +0 -0
|
@@ -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
|
|
|
@@ -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.
|
|
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
|
|
840
|
-
#define
|
|
841
|
-
#define
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
|
3577
|
-
Py_ssize_t capacity = count_max
|
|
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) {
|
|
3744
|
+
if (*p) {indices[count++] = p - p_start;}
|
|
3595
3745
|
p++;
|
|
3596
|
-
if (*p) {
|
|
3746
|
+
if (*p) {indices[count++] = p - p_start;}
|
|
3597
3747
|
p++;
|
|
3598
|
-
if (*p) {
|
|
3748
|
+
if (*p) {indices[count++] = p - p_start;}
|
|
3599
3749
|
p++;
|
|
3600
|
-
if (*p) {
|
|
3750
|
+
if (*p) {indices[count++] = p - p_start;}
|
|
3601
3751
|
p++;
|
|
3602
|
-
if (*p) {
|
|
3752
|
+
if (*p) {indices[count++] = p - p_start;}
|
|
3603
3753
|
p++;
|
|
3604
|
-
if (*p) {
|
|
3754
|
+
if (*p) {indices[count++] = p - p_start;}
|
|
3605
3755
|
p++;
|
|
3606
|
-
if (*p) {
|
|
3756
|
+
if (*p) {indices[count++] = p - p_start;}
|
|
3607
3757
|
p++;
|
|
3608
|
-
if (*p) {
|
|
3758
|
+
if (*p) {indices[count++] = p - p_start;}
|
|
3609
3759
|
p++;
|
|
3610
3760
|
}
|
|
3611
3761
|
while (p < p_end) {
|
|
3612
|
-
if (*p) {
|
|
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)) {
|
|
3771
|
+
if (*(npy_bool*)PyArray_GETPTR1(array, i)) {indices[count++] = i;}
|
|
3622
3772
|
i++;
|
|
3623
|
-
if (*(npy_bool*)PyArray_GETPTR1(array, i)) {
|
|
3773
|
+
if (*(npy_bool*)PyArray_GETPTR1(array, i)) {indices[count++] = i;}
|
|
3624
3774
|
i++;
|
|
3625
|
-
if (*(npy_bool*)PyArray_GETPTR1(array, i)) {
|
|
3775
|
+
if (*(npy_bool*)PyArray_GETPTR1(array, i)) {indices[count++] = i;}
|
|
3626
3776
|
i++;
|
|
3627
|
-
if (*(npy_bool*)PyArray_GETPTR1(array, i)) {
|
|
3777
|
+
if (*(npy_bool*)PyArray_GETPTR1(array, i)) {indices[count++] = i;}
|
|
3628
3778
|
i++;
|
|
3629
|
-
if (*(npy_bool*)PyArray_GETPTR1(array, i)) {
|
|
3779
|
+
if (*(npy_bool*)PyArray_GETPTR1(array, i)) {indices[count++] = i;}
|
|
3630
3780
|
i++;
|
|
3631
|
-
if (*(npy_bool*)PyArray_GETPTR1(array, i)) {
|
|
3781
|
+
if (*(npy_bool*)PyArray_GETPTR1(array, i)) {indices[count++] = i;}
|
|
3632
3782
|
i++;
|
|
3633
|
-
if (*(npy_bool*)PyArray_GETPTR1(array, i)) {
|
|
3783
|
+
if (*(npy_bool*)PyArray_GETPTR1(array, i)) {indices[count++] = i;}
|
|
3634
3784
|
i++;
|
|
3635
|
-
if (*(npy_bool*)PyArray_GETPTR1(array, i)) {
|
|
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)) {
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|