Skip to content

Commit 269dfa3

Browse files
authored
Merge pull request #129 from static-frame/128/block-index-zero-slice
`BlockIndex` handling of empty iterators
2 parents 5d74ee1 + 5a55e31 commit 269dfa3

File tree

2 files changed

+85
-23
lines changed

2 files changed

+85
-23
lines changed

src/_arraykit.c

+63-23
Original file line numberDiff line numberDiff line change
@@ -3263,17 +3263,61 @@ name_filter(PyObject *Py_UNUSED(m), PyObject *n) {
32633263
return n;
32643264
}
32653265

3266+
// Returns NULL on error. Returns a new reference.
3267+
static inline PyObject*
3268+
AK_build_pair_ssize_t(Py_ssize_t a, Py_ssize_t b)
3269+
{
3270+
PyObject* t = PyTuple_New(2);
3271+
if (t == NULL) {
3272+
return NULL;
3273+
}
3274+
PyObject* py_a = PyLong_FromSsize_t(a);
3275+
if (py_a == NULL) {
3276+
Py_DECREF(t);
3277+
return NULL;
3278+
}
3279+
PyObject* py_b = PyLong_FromSsize_t(b);
3280+
if (py_b == NULL) {
3281+
Py_DECREF(t);
3282+
Py_DECREF(py_a);
3283+
return NULL;
3284+
}
3285+
PyTuple_SET_ITEM(t, 0, py_a);
3286+
PyTuple_SET_ITEM(t, 1, py_b);
3287+
return t;
3288+
}
3289+
3290+
// Returns NULL on error. Returns a new reference. Note that a reference is stolen from the PyObject argument.
3291+
static inline PyObject*
3292+
AK_build_pair_ssize_t_slice(Py_ssize_t a, PyObject* py_b)
3293+
{
3294+
if (py_b == NULL) { // construction failed
3295+
return NULL;
3296+
}
3297+
PyObject* t = PyTuple_New(2);
3298+
if (t == NULL) {
3299+
return NULL;
3300+
}
3301+
PyObject* py_a = PyLong_FromSsize_t(a);
3302+
if (py_a == NULL) {
3303+
Py_DECREF(t);
3304+
return NULL;
3305+
}
3306+
PyTuple_SET_ITEM(t, 0, py_a);
3307+
PyTuple_SET_ITEM(t, 1, py_b);
3308+
return t;
3309+
}
3310+
32663311
// Represent a 1D array as a 2D array with length as rows of a single-column array.
32673312
// https://stackoverflow.com/questions/56182259/how-does-one-acces-numpy-multidimensionnal-array-in-c-extensions
32683313
static PyObject *
32693314
shape_filter(PyObject *Py_UNUSED(m), PyObject *a) {
32703315
AK_CHECK_NUMPY_ARRAY_1D_2D(a);
32713316
PyArrayObject *array = (PyArrayObject *)a;
3272-
3273-
npy_intp size0 = PyArray_DIM(array, 0);
3317+
npy_intp rows = PyArray_DIM(array, 0);
32743318
// If 1D array, set size for axis 1 at 1, else use 2D array to get the size of axis 1
3275-
npy_intp size1 = PyArray_NDIM(array) == 1 ? 1 : PyArray_DIM(array, 1);
3276-
return Py_BuildValue("nn", size0, size1);
3319+
npy_intp cols = PyArray_NDIM(array) == 1 ? 1 : PyArray_DIM(array, 1);
3320+
return AK_build_pair_ssize_t(rows, cols);
32773321
}
32783322

32793323
// Reshape if necessary a flat ndim 1 array into a 2D array with one columns and rows of length.
@@ -4240,7 +4284,7 @@ AK_BI_item(BlockIndexObject* self, Py_ssize_t i) {
42404284
return NULL;
42414285
}
42424286
BlockIndexRecord* biri = &self->bir[i];
4243-
return Py_BuildValue("nn", biri->block, biri->column); // maybe NULL
4287+
return AK_build_pair_ssize_t(biri->block, biri->column); // may be NULL
42444288
}
42454289

42464290
//------------------------------------------------------------------------------
@@ -4671,6 +4715,8 @@ typedef struct BIIterContiguousObject {
46714715
bool reduce; // optionally reduce slices to integers
46724716
} BIIterContiguousObject;
46734717

4718+
4719+
// Create a new contiguous slice iterator. Return NULL on error. Steals a reference to PyObject* iter.
46744720
static PyObject *
46754721
BIIterContiguous_new(BlockIndexObject *bi,
46764722
bool reversed,
@@ -4684,9 +4730,7 @@ BIIterContiguous_new(BlockIndexObject *bi,
46844730
Py_INCREF((PyObject*)bi);
46854731
bii->bi = bi;
46864732

4687-
Py_INCREF(iter);
4688-
bii->iter = iter;
4689-
4733+
bii->iter = iter; // steals ref
46904734
bii->reversed = reversed;
46914735

46924736
bii->last_block = -1;
@@ -4746,9 +4790,8 @@ BIIterContiguous_reversed(BIIterContiguousObject *self)
47464790
}
47474791
PyObject* biiter = BIIterContiguous_new(self->bi,
47484792
reversed,
4749-
self->iter,
4793+
iter, // steals ref
47504794
self->reduce);
4751-
Py_DECREF(iter);
47524795
return biiter;
47534796
}
47544797

@@ -4788,7 +4831,10 @@ BIIterContiguous_iternext(BIIterContiguousObject *self)
47884831
}
47894832
// no more pairs, return previous slice_start, flag for end on next call
47904833
self->next_block = -2;
4791-
return Py_BuildValue("nN", // N steals ref
4834+
if (self->last_block == -1) { // iter produced no values, terminate
4835+
break;
4836+
}
4837+
return AK_build_pair_ssize_t_slice( // steals ref
47924838
self->last_block,
47934839
AK_build_slice_inclusive(slice_start,
47944840
self->last_column,
@@ -4813,7 +4859,7 @@ BIIterContiguous_iternext(BIIterContiguousObject *self)
48134859
}
48144860
self->next_block = block;
48154861
self->next_column = column;
4816-
return Py_BuildValue("nN", // N steals ref
4862+
return AK_build_pair_ssize_t_slice( // steals ref
48174863
self->last_block,
48184864
AK_build_slice_inclusive(slice_start,
48194865
self->last_column,
@@ -5211,7 +5257,7 @@ BlockIndex_to_list(BlockIndexObject *self, PyObject *Py_UNUSED(unused)) {
52115257
BlockIndexRecord* bir = self->bir;
52125258

52135259
for (Py_ssize_t i = 0; i < self->bir_count; i++) {
5214-
PyObject* item = Py_BuildValue("nn", bir[i].block, bir[i].column);
5260+
PyObject* item = AK_build_pair_ssize_t(bir[i].block, bir[i].column);
52155261
if (item == NULL) {
52165262
Py_DECREF(list);
52175263
return NULL;
@@ -5248,17 +5294,14 @@ BlockIndex_getstate(BlockIndexObject *self) {
52485294
return NULL;
52495295
}
52505296
PyObject* dt = self->dtype == NULL ? Py_None : (PyObject*) self->dtype;
5251-
52525297
// state might be NULL on failure; assume exception set
5253-
PyObject* state = Py_BuildValue("nnnnOO",
5298+
PyObject* state = Py_BuildValue("nnnnNO", // use N to steal ref of bytes
52545299
self->block_count,
52555300
self->row_count,
52565301
self->bir_count,
52575302
self->bir_capacity,
5258-
bi,
5303+
bi, // stolen new ref
52595304
dt); // increfs passed object
5260-
5261-
Py_DECREF(bi);
52625305
return state;
52635306
}
52645307

@@ -5283,7 +5326,7 @@ BlockIndex_shape_getter(BlockIndexObject *self, void* Py_UNUSED(closure))
52835326
{
52845327
if (self->shape == NULL || self->shape_recache) {
52855328
Py_XDECREF(self->shape); // get rid of old if it exists
5286-
self->shape = Py_BuildValue("nn", self->row_count, self->bir_count); // new ref
5329+
self->shape = AK_build_pair_ssize_t(self->row_count, self->bir_count);
52875330
}
52885331
// shape is not null and shape_recache is false
52895332
Py_INCREF(self->shape); // for caller
@@ -5447,14 +5490,11 @@ BlockIndex_iter_contiguous(BlockIndexObject *self, PyObject *args, PyObject *kwa
54475490
)) {
54485491
return NULL;
54495492
}
5450-
5451-
// might need to store enum type for branching
54525493
PyObject* iter = BIIterSelector_new(self, selector, 0, BIIS_UNKNOWN, ascending);
54535494
if (iter == NULL) {
54545495
return NULL; // exception set
54555496
}
5456-
PyObject* biiter = BIIterContiguous_new(self, 0, iter, reduce); // might be NULL, will incref iter
5457-
Py_DECREF(iter);
5497+
PyObject* biiter = BIIterContiguous_new(self, 0, iter, reduce); // might be NULL, steals iter ref
54585498
return biiter;
54595499
}
54605500

test/test_block_index.py

+22
Original file line numberDiff line numberDiff line change
@@ -794,3 +794,25 @@ def test_block_index_iter_contiguous_h2(self) -> None:
794794
(0, 2),
795795
(1, 1)])
796796

797+
798+
def test_block_index_iter_contiguous_i1(self) -> None:
799+
bi1 = BlockIndex()
800+
bi1.register(np.arange(6).reshape(2,3))
801+
bi1.register(np.arange(6).reshape(2,3))
802+
803+
self.assertEqual(list(bi1.iter_select(slice(0, 0))), [])
804+
self.assertEqual(list(bi1.iter_contiguous(slice(0, 0))), [])
805+
806+
self.assertEqual(list(bi1.iter_select(slice(30, 60, 2))), [])
807+
self.assertEqual(list(bi1.iter_contiguous(slice(30, 60, 2))), [])
808+
809+
def test_block_index_iter_contiguous_i2(self) -> None:
810+
bi1 = BlockIndex()
811+
bi1.register(np.arange(6).reshape(2,3))
812+
bi1.register(np.arange(6).reshape(2,3))
813+
814+
self.assertEqual(list(bi1.iter_select([])), [])
815+
self.assertEqual(list(bi1.iter_contiguous([])), [])
816+
817+
self.assertEqual(list(bi1.iter_select(np.full(len(bi1), False))), [])
818+
self.assertEqual(list(bi1.iter_contiguous(np.full(len(bi1), False))), [])

0 commit comments

Comments
 (0)