Skip to content

Commit 090a801

Browse files
authored
Merge pull request #209 from static-frame/copilot/slice-to-unit-function
Add `slice_to_unit()` C API for single-element slice detection
2 parents 2224ccb + 674dfee commit 090a801

6 files changed

Lines changed: 72 additions & 3 deletions

File tree

src/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from ._arraykit import first_true_1d as first_true_1d
2929
from ._arraykit import first_true_2d as first_true_2d
3030
from ._arraykit import slice_to_ascending_slice as slice_to_ascending_slice
31+
from ._arraykit import slice_to_unit as slice_to_unit
3132
from ._arraykit import array_to_tuple_array as array_to_tuple_array
3233
from ._arraykit import array_to_tuple_iter as array_to_tuple_iter
3334
from ._arraykit import nonzero_1d as nonzero_1d
@@ -37,4 +38,3 @@
3738
from ._arraykit import AutoMap as AutoMap
3839
from ._arraykit import FrozenAutoMap as FrozenAutoMap
3940
from ._arraykit import NonUniqueError as NonUniqueError
40-

src/__init__.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,5 +207,6 @@ def is_objectable_dt64(__array: np.ndarray, /) -> bool: ...
207207
def is_objectable(__array: np.ndarray, /) -> bool: ...
208208
def astype_array(__array: np.ndarray, __dtype: np.dtype | None, /) -> np.ndarray: ...
209209
def slice_to_ascending_slice(__slice: slice, __size: int) -> slice: ...
210+
def slice_to_unit(__slice: slice, /) -> int: ...
210211
def array_to_tuple_array(__array: np.ndarray) -> np.ndarray: ...
211212
def array_to_tuple_iter(__array: np.ndarray) -> tp.Iterator[tp.Tuple[tp.Any, ...]]: ...

src/_arraykit.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ static PyMethodDef arraykit_methods[] = {
2222
{"column_1d_filter", column_1d_filter, METH_O, NULL},
2323
{"row_1d_filter", row_1d_filter, METH_O, NULL},
2424
{"slice_to_ascending_slice", slice_to_ascending_slice, METH_VARARGS, NULL},
25+
{"slice_to_unit", slice_to_unit, METH_O, NULL},
2526
{"array_deepcopy",
2627
(PyCFunction)array_deepcopy,
2728
METH_VARARGS | METH_KEYWORDS,
@@ -157,4 +158,3 @@ PyInit__arraykit(void)
157158
#endif
158159
return m;
159160
}
160-

src/methods.c

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,55 @@ slice_to_ascending_slice(PyObject *Py_UNUSED(m), PyObject *args) {
6060
&PyLong_Type, &size)) {
6161
return NULL;
6262
}
63-
// will delegate NULL on eroror
63+
// will delegate NULL on error
6464
return AK_slice_to_ascending_slice(slice, PyLong_AsSsize_t(size));
6565
}
6666

67+
PyObject *
68+
slice_to_unit(PyObject *Py_UNUSED(m), PyObject *a)
69+
{
70+
if (!PySlice_Check(a)) {
71+
return PyErr_Format(PyExc_TypeError,
72+
"Expected a slice, not %s",
73+
Py_TYPE(a)->tp_name);
74+
}
75+
PyObject* py_start = ((PySliceObject*)a)->start;
76+
PyObject* py_stop = ((PySliceObject*)a)->stop;
77+
PyObject* py_step = ((PySliceObject*)a)->step;
78+
79+
if (py_stop == Py_None) {
80+
return PyLong_FromLong(-1);
81+
}
82+
83+
Py_ssize_t step = 1;
84+
if (py_step != Py_None) {
85+
step = PyLong_AsSsize_t(py_step);
86+
if (step == -1 && PyErr_Occurred()) {
87+
return NULL;
88+
}
89+
}
90+
if (step != 1) {
91+
return PyLong_FromLong(-1);
92+
}
93+
94+
Py_ssize_t start = 0;
95+
if (py_start != Py_None) {
96+
start = PyLong_AsSsize_t(py_start);
97+
if (start == -1 && PyErr_Occurred()) {
98+
return NULL;
99+
}
100+
}
101+
Py_ssize_t stop = PyLong_AsSsize_t(py_stop);
102+
if (stop == -1 && PyErr_Occurred()) {
103+
return NULL;
104+
}
105+
106+
if (start < 0 || stop < 0 || stop - start != 1) {
107+
return PyLong_FromLong(-1);
108+
}
109+
return PyLong_FromSsize_t(start);
110+
}
111+
67112
PyObject *
68113
column_2d_filter(PyObject *Py_UNUSED(m), PyObject *a)
69114
{

src/methods.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ row_1d_filter(PyObject *Py_UNUSED(m), PyObject *a);
1414
PyObject *
1515
slice_to_ascending_slice(PyObject *Py_UNUSED(m), PyObject *args);
1616

17+
// Return an integer when a slice is exactly a single positive-position unit, else -1.
18+
PyObject *
19+
slice_to_unit(PyObject *Py_UNUSED(m), PyObject *a);
20+
1721
// Reshape if necessary a flat ndim 1 array into a 2D array with one columns and rows of length.
1822
// related example: https://github.com/RhysU/ar/blob/master/ar-python.cpp
1923
PyObject *

test/test_util.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from arraykit import first_true_1d
2424
from arraykit import first_true_2d
2525
from arraykit import slice_to_ascending_slice
26+
from arraykit import slice_to_unit
2627
from arraykit import array_to_tuple_array
2728
from arraykit import array_to_tuple_iter
2829

@@ -953,6 +954,24 @@ def test_slice_to_ascending_slice_i(self) -> None:
953954
slice(1, 2, None)
954955
)
955956

957+
def test_slice_to_unit_a(self) -> None:
958+
self.assertEqual(slice_to_unit(slice(3, 4)), 3)
959+
self.assertEqual(slice_to_unit(slice(0, 1)), 0)
960+
self.assertEqual(slice_to_unit(slice(None, 1)), 0)
961+
962+
def test_slice_to_unit_b(self) -> None:
963+
self.assertEqual(slice_to_unit(slice(0, 2)), -1)
964+
self.assertEqual(slice_to_unit(slice(5, 5)), -1)
965+
self.assertEqual(slice_to_unit(slice(0, 1, 2)), -1)
966+
self.assertEqual(slice_to_unit(slice(0, 1, -1)), -1)
967+
self.assertEqual(slice_to_unit(slice(0, None)), -1)
968+
self.assertEqual(slice_to_unit(slice(None, 2)), -1)
969+
self.assertEqual(slice_to_unit(slice(-1, 0)), -1)
970+
971+
def test_slice_to_unit_c(self) -> None:
972+
with self.assertRaises(TypeError):
973+
_ = slice_to_unit(3)
974+
956975

957976
if __name__ == '__main__':
958977
unittest.main()

0 commit comments

Comments
 (0)