Skip to content

Commit

Permalink
Merge pull request #176 from static-frame/175/no-double-tuple
Browse files Browse the repository at this point in the history
  • Loading branch information
flexatone authored Jun 23, 2024
2 parents bae0ba2 + f76e22e commit 6e37b63
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 27 deletions.
41 changes: 37 additions & 4 deletions src/_arraykit.c
Original file line number Diff line number Diff line change
Expand Up @@ -3573,7 +3573,7 @@ array_to_tuple_array(PyObject *Py_UNUSED(m), PyObject *a)
i++;
}
}
else { // ndim == 1
else if (PyArray_TYPE(input_array) != NPY_OBJECT) { // ndim == 1, not object
while (p < p_end) {
tuple = PyTuple_New(1);
if (tuple == NULL) {
Expand All @@ -3590,6 +3590,24 @@ array_to_tuple_array(PyObject *Py_UNUSED(m), PyObject *a)
i++;
}
}
else { // ndim == 1, object
while (p < p_end) {
item = *(PyObject**)PyArray_GETPTR1(input_array, i);
Py_INCREF(item); // always incref
if (PyTuple_Check(item)) {
tuple = item; // do not double pack
}
else {
tuple = PyTuple_New(1);
if (tuple == NULL) {
goto error;
}
PyTuple_SET_ITEM(tuple, 0, item); // steals reference to item
}
*p++ = tuple; // assign with new ref, no incr needed
i++;
}
}
PyArray_CLEARFLAGS((PyArrayObject *)output, NPY_ARRAY_WRITEABLE);
return output;
error:
Expand Down Expand Up @@ -3664,10 +3682,10 @@ ATT_iternext(ATTObject *self) {
Py_DECREF(tuple);
return NULL;
}
PyTuple_SET_ITEM(tuple, j, item); // steals reference to item
PyTuple_SET_ITEM(tuple, j, item); // steals ref
}
}
else { // ndim == 1
else if (PyArray_TYPE(array) != NPY_OBJECT) { // ndim == 1, not object
tuple = PyTuple_New(1);
if (tuple == NULL) {
return NULL;
Expand All @@ -3677,7 +3695,22 @@ ATT_iternext(ATTObject *self) {
Py_DECREF(tuple);
return NULL;
}
PyTuple_SET_ITEM(tuple, 0, item); // steals reference to item
PyTuple_SET_ITEM(tuple, 0, item); // steals ref
}
else { // ndim == 1, object
item = *(PyObject**)PyArray_GETPTR1(array, i);
Py_INCREF(item); // always incref
if (PyTuple_Check(item)) {
tuple = item; // do not double pack
}
else {
tuple = PyTuple_New(1);
if (tuple == NULL) {
Py_DECREF(item);
return NULL;
}
PyTuple_SET_ITEM(tuple, 0, item); // steals ref
}
}
self->pos++;
return tuple;
Expand Down
70 changes: 47 additions & 23 deletions test/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,33 +286,47 @@ def test_array_deepcopy_h(self) -> None:
a2 = array_deepcopy(a1, ())

#---------------------------------------------------------------------------
def test_array2d_to_array1d_1d_a(self) -> None:
def test_array_to_tuple_array_1d_a(self) -> None:
a1 = np.arange(10)
a2 = array_to_tuple_array(a1)
self.assertEqual(a2.tolist(), [(0,), (1,), (2,), (3,), (4,), (5,), (6,), (7,), (8,), (9,)])

def test_array2d_to_array1d_1d_b(self) -> None:
def test_array_to_tuple_array_1d_b(self) -> None:
a1 = np.array(['aaa', 'b', 'ccc'])
a2 = array_to_tuple_array(a1)
self.assertEqual(a2.tolist(), [('aaa',), ('b',), ('ccc',)])

def test_array2d_to_array1d_1d_c(self) -> None:
def test_array_to_tuple_array_1d_c(self) -> None:
a1 = np.array([None, 'b', 30])
a2 = array_to_tuple_array(a1)
self.assertEqual(a2.tolist(), [(None,), ('b',), (30,)])

def test_array2d_to_array1d_1d_d(self) -> None:
def test_array_to_tuple_array_1d_d(self) -> None:
a1 = np.array([('a', 10), ('b', 30), ('c', 5)], dtype=object)
a2 = array_to_tuple_array(a1)
a2 = array_to_tuple_array(a1) # from 2d
self.assertEqual(a2.tolist(), [('a', 10), ('b', 30), ('c', 5)])
a3 = array_to_tuple_array(a2) # from 1d
self.assertEqual(a3.tolist(), [('a', 10), ('b', 30), ('c', 5)])

def test_array2d_to_array1d_1d_e(self) -> None:
def test_array_to_tuple_array_1d_e(self) -> None:
a1 = np.array([True, False, True], dtype=object)
a2 = array_to_tuple_array(a1)
self.assertIs(a2[0][0].__class__, bool)
self.assertEqual(a2.tolist(), [(True,), (False,), (True,)])

def test_array2d_to_array1d_b(self) -> None:
def test_array_to_tuple_array_1d_f(self) -> None:
a1 = np.array([None, None, None], dtype=object)
a1[0] = 3
a1[1] = ('a', 30)
a1[2] = (None, True, 90000000)

a2 = array_to_tuple_array(a1)
self.assertEqual(a2.tolist(), [(3,), ('a', 30), (None, True, 90000000)])

a3 = array_to_tuple_array(a2)
self.assertEqual(a3.tolist(), [(3,), ('a', 30), (None, True, 90000000)])

def test_array_to_tuple_array_b(self) -> None:
a1 = np.arange(10, dtype=np.int64).reshape(5, 2)
result = array_to_tuple_array(a1)
assert isinstance(result[0], tuple)
Expand All @@ -322,35 +336,35 @@ def test_array2d_to_array1d_b(self) -> None:
self.assertEqual(tuple(result), ((0, 1), (2, 3), (4, 5), (6, 7), (8, 9)))


def test_array2d_to_array1d_c(self) -> None:
def test_array_to_tuple_array_c(self) -> None:
a1 = np.array([["a", "b"], ["ccc", "ddd"], ["ee", "ff"]])
a2 = array_to_tuple_array(a1)
self.assertEqual(a2.tolist(), [('a', 'b'), ('ccc', 'ddd'), ('ee', 'ff')])

def test_array2d_to_array1d_d(self) -> None:
def test_array_to_tuple_array_d(self) -> None:
a1 = np.array([[3, 5], [10, 20], [7, 2]], dtype=np.uint8)
a2 = array_to_tuple_array(a1)
self.assertEqual(a2.tolist(), [(3, 5), (10, 20), (7, 2)])
self.assertIs(type(a2[0][0]), np.uint8)

def test_array2d_to_array1d_e(self) -> None:
def test_array_to_tuple_array_e(self) -> None:
a1 = np.arange(20, dtype=np.int64).reshape(4, 5)
result = array_to_tuple_array(a1)
self.assertEqual(result.tolist(), [(0, 1, 2, 3, 4), (5, 6, 7, 8, 9), (10, 11, 12, 13, 14), (15, 16, 17, 18, 19)])

#---------------------------------------------------------------------------
def test_array2d_tuple_iter_a(self) -> None:
def test_array_to_tuple_iter_a(self) -> None:
a1 = np.arange(20, dtype=np.int64).reshape(4, 5)
result = list(array_to_tuple_iter(a1))
self.assertEqual(len(result), 4)
self.assertEqual(result, [(0, 1, 2, 3, 4), (5, 6, 7, 8, 9), (10, 11, 12, 13, 14), (15, 16, 17, 18, 19)])

def test_array2d_tuple_iter_b(self) -> None:
def test_array_to_tuple_iter_b(self) -> None:
a1 = np.arange(20, dtype=np.int64).reshape(10, 2)
result = list(array_to_tuple_iter(a1))
self.assertEqual(result, [(0, 1), (2, 3), (4, 5), (6, 7), (8, 9), (10, 11), (12, 13), (14, 15), (16, 17), (18, 19)])

def test_array2d_tuple_iter_c(self) -> None:
def test_array_to_tuple_iter_c(self) -> None:
a1 = np.array([['aaa', 'bb'], ['c', 'dd'], ['ee', 'fffff']])
it = array_to_tuple_iter(a1)
self.assertEqual(it.__length_hint__(), 3)
Expand All @@ -363,52 +377,62 @@ def test_array2d_tuple_iter_c(self) -> None:
with self.assertRaises(StopIteration):
next(it)

def test_array2d_tuple_iter_d(self) -> None:
def test_array_to_tuple_iter_d(self) -> None:
a1 = np.array([['aaa', 'bb'], ['c', 'dd'], ['ee', 'fffff']])
it = array_to_tuple_iter(a1)
# __reversed__ not implemented
with self.assertRaises(TypeError):
reversed(it)

def test_array2d_tuple_iter_e(self) -> None:
def test_array_to_tuple_iter_e(self) -> None:
a1 = np.array([[None, 'bb'], [None, 'dd'], [3, None]])
it = array_to_tuple_iter(a1)
del a1
self.assertEqual(list(it), [(None, 'bb'), (None, 'dd'), (3, None)])

def test_array2d_tuple_iter_f(self) -> None:
def test_array_to_tuple_iter_f(self) -> None:
a1 = np.array([[None, 'bb'], [None, 'dd'], [3, None]])
it1 = array_to_tuple_iter(a1)
del a1
it2 = iter(it1)
self.assertEqual(list(it1), [(None, 'bb'), (None, 'dd'), (3, None)])
self.assertEqual(list(it2), []) # expected behavior

def test_array2d_tuple_iter_g(self) -> None:
def test_array_to_tuple_iter_g(self) -> None:
a1 = np.array([[None, 'bb'], [None, 'dd'], [3, None]])
it1 = array_to_tuple_iter(a1)
it2 = array_to_tuple_iter(a1)
del a1
self.assertEqual(list(it1), [(None, 'bb'), (None, 'dd'), (3, None)])
self.assertEqual(list(it2), [(None, 'bb'), (None, 'dd'), (3, None)])

def test_array2d_tuple_iter_1d_a(self) -> None:
def test_array_to_tuple_iter_1d_a(self) -> None:
a1 = np.array(['bb', 'c', 'aaa'])
result = list(array_to_tuple_iter(a1))
self.assertEqual(len(result), 3)
self.assertEqual(result, [('bb',), ('c',), ('aaa',)])

def test_array2d_tuple_iter_1d_b(self) -> None:
def test_array_to_tuple_iter_1d_b(self) -> None:
a1 = np.array([20, -1, 8])
result = list(array_to_tuple_iter(a1))
self.assertEqual(len(result), 3)
self.assertEqual(result, [(20,), (-1,), (8,)])

def test_array2d_tuple_iter_1d_c(self) -> None:
def test_array_to_tuple_iter_1d_c(self) -> None:
a1 = np.array([('a', 4), ('c', -1), ('d', 8)], dtype=object)
result = list(array_to_tuple_iter(a1))
self.assertEqual(len(result), 3)
self.assertEqual(result, [('a', 4), ('c', -1), ('d', 8)])
a2 = list(array_to_tuple_iter(a1))
self.assertEqual(len(a2), 3)
self.assertEqual(a2, [('a', 4), ('c', -1), ('d', 8)])

def test_array_to_tuple_iter_1d_d(self) -> None:
a1 = np.array([None, None, None], dtype=object)
a1[0] = 3
a1[1] = ('a', 30)
a1[2] = (None, True, 90000000)

a2 = list(array_to_tuple_iter(a1))
self.assertEqual(a2, [(3,), ('a', 30), (None, True, 90000000)])


#---------------------------------------------------------------------------

Expand Down

0 comments on commit 6e37b63

Please sign in to comment.