Skip to content

Commit 61653d3

Browse files
committed
Adds array_to_tuple source/header files
1 parent fb1af8b commit 61653d3

5 files changed

Lines changed: 278 additions & 0 deletions

File tree

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ def get_ext_dir(*components: tp.Iterable[str]) -> tp.Sequence[str]:
3535
sources=[
3636
'src/_arraykit.c',
3737
'src/array_go.c',
38+
'src/array_to_tuple.c',
3839
'src/block_index.c',
3940
'src/delimited_to_arrays.c',
4041
'src/methods.c',

src/_arraykit.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
# include "numpy/arrayobject.h"
77

88
# include "array_go.h"
9+
# include "array_to_tuple.h"
910
# include "block_index.h"
1011
# include "delimited_to_arrays.h"
1112
# include "methods.h"

src/array_to_tuple.c

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
# include "Python.h"
2+
3+
# define NO_IMPORT_ARRAY
4+
# define PY_ARRAY_UNIQUE_SYMBOL AK_ARRAY_API
5+
# define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
6+
7+
# include "numpy/arrayobject.h"
8+
9+
# include "array_to_tuple.h"
10+
# include "utilities.h"
11+
12+
// Given a 1D or 2D array, return a 1D object array of tuples.
13+
PyObject *
14+
array_to_tuple_array(PyObject *Py_UNUSED(m), PyObject *a)
15+
{
16+
AK_CHECK_NUMPY_ARRAY(a);
17+
PyArrayObject *input_array = (PyArrayObject *)a;
18+
int ndim = PyArray_NDIM(input_array);
19+
if (ndim != 1 && ndim != 2) {
20+
return PyErr_Format(PyExc_NotImplementedError,
21+
"Expected 1D or 2D array, not %i.",
22+
ndim);
23+
}
24+
25+
npy_intp num_rows = PyArray_DIM(input_array, 0);
26+
npy_intp dims[] = {num_rows};
27+
// NOTE: this initializes values to NULL, not None
28+
PyObject* output = PyArray_SimpleNew(1, dims, NPY_OBJECT);
29+
if (output == NULL) {
30+
return NULL;
31+
}
32+
33+
PyObject** output_data = (PyObject**)PyArray_DATA((PyArrayObject*)output);
34+
PyObject** p = output_data;
35+
PyObject** p_end = p + num_rows;
36+
npy_intp i = 0;
37+
PyObject* tuple;
38+
PyObject* item;
39+
40+
if (ndim == 2) {
41+
npy_intp num_cols = PyArray_DIM(input_array, 1);
42+
npy_intp j;
43+
while (p < p_end) {
44+
tuple = PyTuple_New(num_cols);
45+
if (tuple == NULL) {
46+
goto error;
47+
}
48+
for (j = 0; j < num_cols; ++j) {
49+
// cannot assume input_array is contiguous
50+
item = PyArray_ToScalar(PyArray_GETPTR2(input_array, i, j), input_array);
51+
if (item == NULL) {
52+
Py_DECREF(tuple);
53+
goto error;
54+
}
55+
PyTuple_SET_ITEM(tuple, j, item); // steals reference to item
56+
}
57+
*p++ = tuple; // assign with new ref, no incr needed
58+
i++;
59+
}
60+
}
61+
else if (PyArray_TYPE(input_array) != NPY_OBJECT) { // ndim == 1, not object
62+
while (p < p_end) {
63+
tuple = PyTuple_New(1);
64+
if (tuple == NULL) {
65+
goto error;
66+
}
67+
// scalar returned in is native PyObject from object arrays
68+
item = PyArray_ToScalar(PyArray_GETPTR1(input_array, i), input_array);
69+
if (item == NULL) {
70+
Py_DECREF(tuple);
71+
goto error;
72+
}
73+
PyTuple_SET_ITEM(tuple, 0, item); // steals reference to item
74+
*p++ = tuple; // assign with new ref, no incr needed
75+
i++;
76+
}
77+
}
78+
else { // ndim == 1, object
79+
while (p < p_end) {
80+
item = *(PyObject**)PyArray_GETPTR1(input_array, i);
81+
Py_INCREF(item); // always incref
82+
if (PyTuple_Check(item)) {
83+
tuple = item; // do not double pack
84+
}
85+
else {
86+
tuple = PyTuple_New(1);
87+
if (tuple == NULL) {
88+
goto error;
89+
}
90+
PyTuple_SET_ITEM(tuple, 0, item); // steals reference to item
91+
}
92+
*p++ = tuple; // assign with new ref, no incr needed
93+
i++;
94+
}
95+
}
96+
PyArray_CLEARFLAGS((PyArrayObject *)output, NPY_ARRAY_WRITEABLE);
97+
return output;
98+
error:
99+
p = output_data;
100+
p_end = p + num_rows;
101+
while (p < p_end) { // decref all tuples within array
102+
Py_XDECREF(*p++); // xdec as might be NULL
103+
}
104+
Py_DECREF(output);
105+
return NULL;
106+
}
107+
108+
//------------------------------------------------------------------------------
109+
// ArrayToTupleIterator
110+
111+
static PyTypeObject ATTType;
112+
113+
typedef struct ATTObject {
114+
PyObject_HEAD
115+
PyArrayObject* array;
116+
npy_intp num_rows;
117+
npy_intp num_cols;
118+
Py_ssize_t pos; // current index state, mutated in-place
119+
} ATTObject;
120+
121+
static inline PyObject *
122+
ATT_new(PyArrayObject* array,
123+
npy_intp num_rows,
124+
npy_intp num_cols) {
125+
ATTObject* a2dt = PyObject_New(ATTObject, &ATTType);
126+
if (!a2dt) {
127+
return NULL;
128+
}
129+
Py_INCREF((PyObject*)array);
130+
a2dt->array = array;
131+
a2dt->num_rows = num_rows;
132+
a2dt->num_cols = num_cols; // -1 for 1D array
133+
a2dt->pos = 0;
134+
return (PyObject *)a2dt;
135+
}
136+
137+
static inline void
138+
ATT_dealloc(ATTObject *self) {
139+
Py_DECREF((PyObject*)self->array);
140+
PyObject_Del((PyObject*)self);
141+
}
142+
143+
static inline PyObject*
144+
ATT_iter(ATTObject *self) {
145+
Py_INCREF(self);
146+
return (PyObject*)self;
147+
}
148+
149+
static inline PyObject *
150+
ATT_iternext(ATTObject *self) {
151+
Py_ssize_t i = self->pos;
152+
if (i < self->num_rows) {
153+
npy_intp num_cols = self->num_cols;
154+
PyArrayObject* array = self->array;
155+
PyObject* item;
156+
PyObject* tuple;
157+
158+
if (num_cols > -1) { // ndim == 2
159+
tuple = PyTuple_New(num_cols);
160+
if (tuple == NULL) {
161+
return NULL;
162+
}
163+
for (npy_intp j = 0; j < num_cols; ++j) {
164+
// cannot assume array is contiguous
165+
item = PyArray_ToScalar(PyArray_GETPTR2(array, i, j), array);
166+
if (item == NULL) {
167+
Py_DECREF(tuple);
168+
return NULL;
169+
}
170+
PyTuple_SET_ITEM(tuple, j, item); // steals ref
171+
}
172+
}
173+
else if (PyArray_TYPE(array) != NPY_OBJECT) { // ndim == 1, not object
174+
tuple = PyTuple_New(1);
175+
if (tuple == NULL) {
176+
return NULL;
177+
}
178+
item = PyArray_ToScalar(PyArray_GETPTR1(array, i), array);
179+
if (item == NULL) {
180+
Py_DECREF(tuple);
181+
return NULL;
182+
}
183+
PyTuple_SET_ITEM(tuple, 0, item); // steals ref
184+
}
185+
else { // ndim == 1, object
186+
item = *(PyObject**)PyArray_GETPTR1(array, i);
187+
Py_INCREF(item); // always incref
188+
if (PyTuple_Check(item)) {
189+
tuple = item; // do not double pack
190+
}
191+
else {
192+
tuple = PyTuple_New(1);
193+
if (tuple == NULL) {
194+
Py_DECREF(item);
195+
return NULL;
196+
}
197+
PyTuple_SET_ITEM(tuple, 0, item); // steals ref
198+
}
199+
}
200+
self->pos++;
201+
return tuple;
202+
}
203+
return NULL;
204+
}
205+
206+
// static PyObject *
207+
// ATT_reversed(ATTObject *self) {
208+
// return ATT_new(self->bi, !self->reversed);
209+
// }
210+
211+
static inline PyObject *
212+
ATT_length_hint(ATTObject *self) {
213+
Py_ssize_t len = Py_MAX(0, self->num_rows - self->pos);
214+
return PyLong_FromSsize_t(len);
215+
}
216+
217+
static PyMethodDef ATT_methods[] = {
218+
{"__length_hint__", (PyCFunction)ATT_length_hint, METH_NOARGS, NULL},
219+
// {"__reversed__", (PyCFunction)ATT_reversed, METH_NOARGS, NULL},
220+
{NULL},
221+
};
222+
223+
static PyTypeObject ATTType = {
224+
PyVarObject_HEAD_INIT(NULL, 0)
225+
.tp_basicsize = sizeof(ATTObject),
226+
.tp_dealloc = (destructor) ATT_dealloc,
227+
.tp_iter = (getiterfunc) ATT_iter,
228+
.tp_iternext = (iternextfunc) ATT_iternext,
229+
.tp_methods = ATT_methods,
230+
.tp_name = "arraykit.ATTIterator",
231+
};
232+
233+
// Given a 2D array, return an iterator of row tuples.
234+
PyObject *
235+
array_to_tuple_iter(PyObject *Py_UNUSED(m), PyObject *a)
236+
{
237+
AK_CHECK_NUMPY_ARRAY(a);
238+
PyArrayObject *array = (PyArrayObject *)a;
239+
int ndim = PyArray_NDIM(array);
240+
if (ndim != 1 && ndim != 2) {
241+
return PyErr_Format(PyExc_NotImplementedError,
242+
"Expected 1D or 2D array, not %i.",
243+
ndim);
244+
}
245+
npy_intp num_rows = PyArray_DIM(array, 0);
246+
npy_intp num_cols = -1; // indicate 1d
247+
if (ndim == 2) {
248+
num_cols = PyArray_DIM(array, 1);
249+
}
250+
return ATT_new(array, num_rows, num_cols);
251+
}

src/array_to_tuple.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# ifndef ARRAYKIT_SRC_ARRAY_TO_TUPLE_H_
2+
# define ARRAYKIT_SRC_ARRAY_TO_TUPLE_H_
3+
4+
# include "Python.h"
5+
6+
// Given a 1D or 2D array, return a 1D object array of tuples.
7+
PyObject *
8+
array_to_tuple_array(PyObject *Py_UNUSED(m), PyObject *a);
9+
10+
// Given a 2D array, return an iterator of row tuples.
11+
PyObject *
12+
array_to_tuple_iter(PyObject *Py_UNUSED(m), PyObject *a);
13+
14+
# endif // ARRAYKIT_SRC_ARRAY_TO_TUPLE_H_

src/utilities.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,17 @@
3434
} \
3535
} while (0)
3636

37+
// Given a PyObject, raise if not an array or is not two dimensional.
38+
# define AK_CHECK_NUMPY_ARRAY_2D(O) \
39+
do { \
40+
AK_CHECK_NUMPY_ARRAY(O) \
41+
int ndim = PyArray_NDIM((PyArrayObject *)O); \
42+
if (ndim != 2) { \
43+
return PyErr_Format(PyExc_NotImplementedError,\
44+
"Expected a 2D array, not %i.", \
45+
ndim); \
46+
} \
47+
} while (0)
3748

3849
# if defined __GNUC__ || defined __clang__
3950
# define AK_LIKELY(X) __builtin_expect(!!(X), 1)

0 commit comments

Comments
 (0)