diff options
-rw-r--r-- | numpy/core/src/multiarray/item_selection.c | 87 | ||||
-rw-r--r-- | numpy/core/src/npysort/heapsort.c.src | 67 | ||||
-rw-r--r-- | numpy/core/src/npysort/mergesort.c.src | 85 | ||||
-rw-r--r-- | numpy/core/src/npysort/npysort_common.h | 53 | ||||
-rw-r--r-- | numpy/core/src/npysort/quicksort.c.src | 50 | ||||
-rw-r--r-- | numpy/core/src/private/npy_sort.h | 9 | ||||
-rw-r--r-- | numpy/core/tests/test_multiarray.py | 67 |
7 files changed, 361 insertions, 57 deletions
diff --git a/numpy/core/src/multiarray/item_selection.c b/numpy/core/src/multiarray/item_selection.c index 977580d70..76b26d266 100644 --- a/numpy/core/src/multiarray/item_selection.c +++ b/numpy/core/src/multiarray/item_selection.c @@ -890,7 +890,7 @@ _new_argsort(PyArrayObject *op, int axis, NPY_SORTKIND which) static PyArrayObject *global_obj; static int -qsortCompare (const void *a, const void *b) +sortCompare (const void *a, const void *b) { return PyArray_DESCR(global_obj)->f->compare(a,b,global_obj); } @@ -954,7 +954,9 @@ PyArray_Sort(PyArrayObject *op, int axis, NPY_SORTKIND which) PyArrayObject *ap = NULL, *store_arr = NULL; char *ip; int i, n, m, elsize, orign; - int axis_orig=axis; + int res = 0; + int axis_orig = axis; + int (*sort)(void *, size_t, size_t, npy_comparator); n = PyArray_NDIM(op); if ((n == 0) || (PyArray_SIZE(op) == 1)) { @@ -975,13 +977,30 @@ PyArray_Sort(PyArrayObject *op, int axis, NPY_SORTKIND which) if (PyArray_DESCR(op)->f->sort[which] != NULL) { return _new_sort(op, axis, which); } - if ((which != NPY_QUICKSORT) - || PyArray_DESCR(op)->f->compare == NULL) { + + if (PyArray_DESCR(op)->f->compare == NULL) { PyErr_SetString(PyExc_TypeError, - "desired sort not supported for this type"); + "type does not have compare function"); return -1; } + switch (which) { + case NPY_QUICKSORT : + sort = npy_quicksort; + break; + case NPY_HEAPSORT : + sort = npy_heapsort; + break; + case NPY_MERGESORT : + sort = npy_mergesort; + break; + default: + PyErr_SetString(PyExc_TypeError, + "requested sort kind is not supported"); + goto fail; + } + + SWAPAXES2(op); ap = (PyArrayObject *)PyArray_FromAny((PyObject *)op, @@ -1001,13 +1020,26 @@ PyArray_Sort(PyArrayObject *op, int axis, NPY_SORTKIND which) store_arr = global_obj; global_obj = ap; for (ip = PyArray_DATA(ap), i = 0; i < n; i++, ip += elsize*m) { - qsort(ip, m, elsize, qsortCompare); + res = sort(ip, m, elsize, sortCompare); + if (res < 0) { + break; + } } global_obj = store_arr; if (PyErr_Occurred()) { goto fail; } + else if (res == -NPY_ENOMEM) { + PyErr_NoMemory(); + goto fail; + } + else if (res == -NPY_ECOMP) { + PyErr_SetString(PyExc_TypeError, + "sort comparison failed"); + goto fail; + } + finish: Py_DECREF(ap); /* Should update op if needed */ @@ -1045,6 +1077,8 @@ PyArray_ArgSort(PyArrayObject *op, int axis, NPY_SORTKIND which) npy_intp i, j, n, m, orign; int argsort_elsize; char *store_ptr; + int res = 0; + int (*sort)(void *, size_t, size_t, npy_comparator); n = PyArray_NDIM(op); if ((n == 0) || (PyArray_SIZE(op) == 1)) { @@ -1071,14 +1105,32 @@ PyArray_ArgSort(PyArrayObject *op, int axis, NPY_SORTKIND which) return (PyObject *)ret; } - if ((which != NPY_QUICKSORT) || PyArray_DESCR(op2)->f->compare == NULL) { + if (PyArray_DESCR(op2)->f->compare == NULL) { PyErr_SetString(PyExc_TypeError, - "requested sort not available for type"); + "type does not have compare function"); Py_DECREF(op2); op = NULL; goto fail; } + switch (which) { + case NPY_QUICKSORT : + sort = npy_quicksort; + break; + case NPY_HEAPSORT : + sort = npy_heapsort; + break; + case NPY_MERGESORT : + sort = npy_mergesort; + break; + default: + PyErr_SetString(PyExc_TypeError, + "requested sort kind is not supported"); + Py_DECREF(op2); + op = NULL; + goto fail; + } + /* ap will contain the reference to op2 */ SWAPAXES(ap, op2); op = (PyArrayObject *)PyArray_ContiguousFromAny((PyObject *)ap, @@ -1109,11 +1161,27 @@ PyArray_ArgSort(PyArrayObject *op, int axis, NPY_SORTKIND which) for (j = 0; j < m; j++) { ip[j] = j; } - qsort((char *)ip, m, sizeof(npy_intp), argsort_static_compare); + res = sort((char *)ip, m, sizeof(npy_intp), argsort_static_compare); + if (res < 0) { + break; + } } global_data = store_ptr; global_obj = store; + if (PyErr_Occurred()) { + goto fail; + } + else if (res == -NPY_ENOMEM) { + PyErr_NoMemory(); + goto fail; + } + else if (res == -NPY_ECOMP) { + PyErr_SetString(PyExc_TypeError, + "sort comparison failed"); + goto fail; + } + finish: Py_DECREF(op); SWAPBACK(op, ret); @@ -1123,7 +1191,6 @@ PyArray_ArgSort(PyArrayObject *op, int axis, NPY_SORTKIND which) Py_XDECREF(op); Py_XDECREF(ret); return NULL; - } diff --git a/numpy/core/src/npysort/heapsort.c.src b/numpy/core/src/npysort/heapsort.c.src index 7fd7c2fd1..6a8c79f7a 100644 --- a/numpy/core/src/npysort/heapsort.c.src +++ b/numpy/core/src/npysort/heapsort.c.src @@ -182,7 +182,7 @@ heapsort_@suff@(@type@ *start, npy_intp n, PyArrayObject *arr) size_t len = PyArray_ITEMSIZE(arr)/sizeof(@type@); @type@ *tmp = malloc(PyArray_ITEMSIZE(arr)); @type@ *a = start - len; - npy_intp i,j,l; + npy_intp i, j, l; for (l = n>>1; l > 0; --l) { @TYPE@_COPY(tmp, a + l*len, len); @@ -274,3 +274,68 @@ aheapsort_@suff@(@type@ *v, npy_intp *tosort, npy_intp n, PyArrayObject *arr) } /**end repeat**/ + + +/* + ***************************************************************************** + ** GENERIC SORT ** + ***************************************************************************** + */ + + +/* + * This sort has almost the same signature as libc qsort and is intended to + * provide a heapsort for array types that don't have type specific sorts. + * The difference in the signature is an error return, as it might be the + * case that a memory allocation fails. + */ +int +npy_heapsort(void *base, size_t num, size_t size, npy_comparator cmp) +{ + char *tmp = (char *) malloc(size); + char *a = (char *) base - size; + size_t i, j, l; + + if (tmp == NULL) { + return -NPY_ENOMEM; + } + + for (l = num >> 1; l > 0; --l) { + GENERIC_COPY(tmp, a + l*size, size); + for (i = l, j = l << 1; j <= num;) { + if (j < num && GENERIC_LT(a + j*size, a + (j+1)*size, cmp)) + j += 1; + if (GENERIC_LT(tmp, a + j*size, cmp)) { + GENERIC_COPY(a + i*size, a + j*size, size); + i = j; + j += j; + } + else { + break; + } + } + GENERIC_COPY(a + i*size, tmp, size); + } + + for (; num > 1;) { + GENERIC_COPY(tmp, a + num*size, size); + GENERIC_COPY(a + num*size, a + size, size); + num -= 1; + for (i = 1, j = 2; j <= num;) { + if (j < num && GENERIC_LT(a + j*size, a + (j+1)*size, cmp)) + j++; + if (GENERIC_LT(tmp, a + j*size, cmp)) { + GENERIC_COPY(a + i*size, a + j*size, size); + i = j; + j += j; + } + else { + break; + } + } + GENERIC_COPY(a + i*size, tmp, size); + } + + free(tmp); + return 0; +} diff --git a/numpy/core/src/npysort/mergesort.c.src b/numpy/core/src/npysort/mergesort.c.src index 59582deda..a4f6bdb99 100644 --- a/numpy/core/src/npysort/mergesort.c.src +++ b/numpy/core/src/npysort/mergesort.c.src @@ -340,3 +340,88 @@ amergesort_@suff@(@type@ *v, npy_intp *tosort, npy_intp num, PyArrayObject *arr) } /**end repeat**/ + + +/* + ***************************************************************************** + ** GENERIC SORT ** + ***************************************************************************** + */ + + +static void +npy_mergesort0(char *pl, char *pr, char *pw, char *vp, size_t size, npy_comparator cmp) +{ + char *pi, *pj, *pk, *pm; + + if ((size_t)(pr - pl) > SMALL_MERGESORT*size) { + /* merge sort */ + pm = pl + (((pr - pl)/size) >> 1)*size; + npy_mergesort0(pl, pm, pw, vp, size, cmp); + npy_mergesort0(pm, pr, pw, vp, size, cmp); + GENERIC_COPY(pw, pl, pm - pl); + pi = pw + (pm - pl); + pj = pw; + pk = pl; + while (pj < pi && pm < pr) { + if (GENERIC_LT(pm, pj, cmp)) { + GENERIC_COPY(pk, pm, size); + pm += size; + } + else { + GENERIC_COPY(pk, pj, size); + pj += size; + } + pk += size; + } + GENERIC_COPY(pk, pj, pi - pj); + } + else { + /* insertion sort */ + for (pi = pl + size; pi < pr; pi += size) { + GENERIC_COPY(vp, pi, size); + pj = pi; + pk = pi - size; + while (pj > pl && GENERIC_LT(vp, pk, cmp)) { + GENERIC_COPY(pj, pk, size); + pj -= size; + pk -= size; + } + GENERIC_COPY(pj, vp, size); + } + } +} + + +/* + * This sort has almost the same signature as libc qsort and is intended to + * provide a mergesort for array types that don't have type specific sorts. + * The difference in the signature is an error return, as it might be the + * case that a memory allocation fails. + */ +int +npy_mergesort(void *base, size_t num, size_t size, npy_comparator cmp) +{ + char *pl, *pr, *pw, *vp; + int err = 0; + + pl = (char *)base; + pr = pl + num*size; + pw = (char *) PyDataMem_NEW((num/2)*size); + if (pw == NULL) { + err = -NPY_ENOMEM; + goto fail_0; + } + vp = (char *) PyDataMem_NEW(size); + if (vp == NULL) { + err = -NPY_ENOMEM; + goto fail_1; + } + npy_mergesort0(pl, pr, pw, vp, size, cmp); + + PyDataMem_FREE(vp); +fail_1: + PyDataMem_FREE(pw); +fail_0: + return err; +} diff --git a/numpy/core/src/npysort/npysort_common.h b/numpy/core/src/npysort/npysort_common.h index bc1d278b0..318a80222 100644 --- a/numpy/core/src/npysort/npysort_common.h +++ b/numpy/core/src/npysort/npysort_common.h @@ -1,15 +1,9 @@ #ifndef __NPY_SORT_COMMON_H__ #define __NPY_SORT_COMMON_H__ -/* -#include "Python.h" -#include "numpy/npy_common.h" -#include "numpy/npy_math.h" -#include "npy_config.h" -*/ +#include <stdlib.h> #include <numpy/ndarraytypes.h> - /* ***************************************************************************** ** SWAP MACROS ** @@ -155,14 +149,17 @@ npy_half_lt_nonan(npy_half h1, npy_half h2) if (h1&0x8000u) { if (h2&0x8000u) { return (h1&0x7fffu) > (h2&0x7fffu); - } else { + } + else { /* Signed zeros are equal, have to check for it */ return (h1 != 0x8000u) || (h2 != 0x0000u); } - } else { + } + else { if (h2&0x8000u) { return 0; - } else { + } + else { return (h1&0x7fffu) < (h2&0x7fffu); } } @@ -176,7 +173,8 @@ HALF_LT(npy_half a, npy_half b) if (npy_half_isnan(b)) { ret = !npy_half_isnan(a); - } else { + } + else { ret = !npy_half_isnan(a) && npy_half_lt_nonan(a, b); } @@ -254,14 +252,6 @@ CLONGDOUBLE_LT(npy_clongdouble a, npy_clongdouble b) } -/* The PyObject functions are stubs for later use */ -NPY_INLINE static int -PyObject_LT(PyObject *pa, PyObject *pb) -{ - return 0; -} - - NPY_INLINE static void STRING_COPY(char *s1, char *s2, size_t len) { @@ -333,4 +323,29 @@ UNICODE_LT(npy_ucs4 *s1, npy_ucs4 *s2, size_t len) return ret; } + +NPY_INLINE static void +GENERIC_COPY(char *a, char *b, size_t len) +{ + memcpy(a, b, len); +} + + +NPY_INLINE static void +GENERIC_SWAP(char *a, char *b, size_t len) +{ + while(len--) { + const char t = *a; + *a++ = *b; + *b++ = t; + } +} + + +NPY_INLINE static int +GENERIC_LT(char *a, char *b, int (*cmp)(const void *, const void *)) +{ + return cmp(a, b) < 0; +} + #endif diff --git a/numpy/core/src/npysort/quicksort.c.src b/numpy/core/src/npysort/quicksort.c.src index 68140fe6f..2225887b1 100644 --- a/numpy/core/src/npysort/quicksort.c.src +++ b/numpy/core/src/npysort/quicksort.c.src @@ -63,10 +63,12 @@ int quicksort_@suff@(@type@ *start, npy_intp num, void *NOT_USED) { + @type@ vp; @type@ *pl = start; @type@ *pr = start + num - 1; - @type@ vp; - @type@ *stack[PYA_QS_STACK], **sptr = stack, *pm, *pi, *pj, *pk; + @type@ *stack[PYA_QS_STACK]; + @type@ **sptr = stack; + @type@ *pm, *pi, *pj, *pk; for (;;) { while ((pr - pl) > SMALL_QUICKSORT) { @@ -127,30 +129,30 @@ int aquicksort_@suff@(@type@ *v, npy_intp* tosort, npy_intp num, void *NOT_USED) { @type@ vp; - npy_intp *pl, *pr; - npy_intp *stack[PYA_QS_STACK], **sptr=stack, *pm, *pi, *pj, *pk, vi; - - pl = tosort; - pr = tosort + num - 1; + npy_intp *pl = tosort; + npy_intp *pr = tosort + num - 1; + npy_intp *stack[PYA_QS_STACK]; + npy_intp **sptr = stack; + npy_intp *pm, *pi, *pj, *pk, vi; for (;;) { while ((pr - pl) > SMALL_QUICKSORT) { /* quicksort partition */ pm = pl + ((pr - pl) >> 1); - if (@TYPE@_LT(v[*pm],v[*pl])) INTP_SWAP(*pm,*pl); - if (@TYPE@_LT(v[*pr],v[*pm])) INTP_SWAP(*pr,*pm); - if (@TYPE@_LT(v[*pm],v[*pl])) INTP_SWAP(*pm,*pl); + if (@TYPE@_LT(v[*pm],v[*pl])) INTP_SWAP(*pm, *pl); + if (@TYPE@_LT(v[*pr],v[*pm])) INTP_SWAP(*pr, *pm); + if (@TYPE@_LT(v[*pm],v[*pl])) INTP_SWAP(*pm, *pl); vp = v[*pm]; pi = pl; pj = pr - 1; - INTP_SWAP(*pm,*pj); + INTP_SWAP(*pm, *pj); for (;;) { - do ++pi; while (@TYPE@_LT(v[*pi],vp)); - do --pj; while (@TYPE@_LT(vp,v[*pj])); + do ++pi; while (@TYPE@_LT(v[*pi], vp)); + do --pj; while (@TYPE@_LT(vp, v[*pj])); if (pi >= pj) { break; } - INTP_SWAP(*pi,*pj); + INTP_SWAP(*pi, *pj); } pk = pr - 1; INTP_SWAP(*pi,*pk); @@ -339,3 +341,23 @@ aquicksort_@suff@(@type@ *v, npy_intp* tosort, npy_intp num, PyArrayObject *arr) } /**end repeat**/ + + +/* + ***************************************************************************** + ** GENERIC SORT ** + ***************************************************************************** + */ + + +/* + * This sort has almost the same signature as libc qsort and is intended to + * supply an error return for compatibility with the other generic sort + * kinds. + */ +int +npy_quicksort(void *base, size_t num, size_t size, npy_comparator cmp) +{ + qsort(base, num, size, cmp); + return 0; +} diff --git a/numpy/core/src/private/npy_sort.h b/numpy/core/src/private/npy_sort.h index 94d831f86..120e7efc2 100644 --- a/numpy/core/src/private/npy_sort.h +++ b/numpy/core/src/private/npy_sort.h @@ -6,6 +6,10 @@ #include <numpy/npy_common.h> #include <numpy/ndarraytypes.h> +#define NPY_ENOMEM 1 +#define NPY_ECOMP 2 + +typedef int (*npy_comparator)(const void *, const void *); int quicksort_bool(npy_bool *vec, npy_intp cnt, void *null); int heapsort_bool(npy_bool *vec, npy_intp cnt, void *null); @@ -166,4 +170,9 @@ int aquicksort_unicode(npy_ucs4 *vec, npy_intp *ind, npy_intp cnt, PyArrayObject int aheapsort_unicode(npy_ucs4 *vec, npy_intp *ind, npy_intp cnt, PyArrayObject *arr); int amergesort_unicode(npy_ucs4 *vec, npy_intp *ind, npy_intp cnt, PyArrayObject *arr); + +int npy_quicksort(void *base, size_t num, size_t size, npy_comparator cmp); +int npy_heapsort(void *base, size_t num, size_t size, npy_comparator cmp); +int npy_mergesort(void *base, size_t num, size_t size, npy_comparator cmp); + #endif diff --git a/numpy/core/tests/test_multiarray.py b/numpy/core/tests/test_multiarray.py index ab4e3e479..fba7a2981 100644 --- a/numpy/core/tests/test_multiarray.py +++ b/numpy/core/tests/test_multiarray.py @@ -538,7 +538,7 @@ class TestMethods(TestCase): c.sort(kind=kind) assert_equal(c, a, msg) - # test unicode sort. + # test unicode sorts. s = 'aaaaaaaa' a = np.array([s + chr(i) for i in range(100)], dtype=np.unicode) b = a[::-1].copy() @@ -551,11 +551,11 @@ class TestMethods(TestCase): c.sort(kind=kind) assert_equal(c, a, msg) - # test object array sort. + # test object array sorts. a = np.empty((100,), dtype=np.object) a[:] = range(100) b = a[::-1] - for kind in ['q'] : + for kind in ['q', 'h', 'm'] : msg = "object sort, kind=%s" % kind c = a.copy(); c.sort(kind=kind) @@ -564,11 +564,11 @@ class TestMethods(TestCase): c.sort(kind=kind) assert_equal(c, a, msg) - # test record array sort. + # test record array sorts. dt = np.dtype([('f',float),('i',int)]) a = array([(i,i) for i in range(100)], dtype = dt) b = a[::-1] - for kind in ['q'] : + for kind in ['q', 'h', 'm'] : msg = "object sort, kind=%s" % kind c = a.copy(); c.sort(kind=kind) @@ -577,6 +577,30 @@ class TestMethods(TestCase): c.sort(kind=kind) assert_equal(c, a, msg) + # test datetime64 sorts. + a = np.arange(0, 100, dtype='datetime64[D]') + b = a[::-1] + for kind in ['q', 'h', 'm'] : + msg = "datetime64 sort, kind=%s" % kind + c = a.copy(); + c.sort(kind=kind) + assert_equal(c, a, msg) + c = b.copy(); + c.sort(kind=kind) + assert_equal(c, a, msg) + + # test timedelta64 sorts. + a = np.arange(0, 100, dtype='timedelta64[D]') + b = a[::-1] + for kind in ['q', 'h', 'm'] : + msg = "timedelta64 sort, kind=%s" % kind + c = a.copy(); + c.sort(kind=kind) + assert_equal(c, a, msg) + c = b.copy(); + c.sort(kind=kind) + assert_equal(c, a, msg) + # check axis handling. This should be the same for all type # specific sorts, so we only check it for one type and one kind a = np.array([[3,2],[1,0]]) @@ -591,10 +615,6 @@ class TestMethods(TestCase): d = a.copy() d.sort() assert_equal(d, c, "test sort with default axis") - # using None is known fail at this point - # d = a.copy() - # d.sort(axis=None) - #assert_equal(d, c, "test sort with axis=None") def test_sort_order(self): @@ -681,28 +701,49 @@ class TestMethods(TestCase): assert_equal(a.copy().argsort(kind=kind), r, msg) assert_equal(b.copy().argsort(kind=kind), rr, msg) - # test object array argsort. + # test object array argsorts. a = np.empty((100,), dtype=np.object) a[:] = range(100) b = a[::-1] r = np.arange(100) rr = r[::-1] - for kind in ['q'] : + for kind in ['q', 'm', 'h'] : msg = "object argsort, kind=%s" % kind assert_equal(a.copy().argsort(kind=kind), r, msg) assert_equal(b.copy().argsort(kind=kind), rr, msg) - # test structured array sort. + # test structured array argsorts. dt = np.dtype([('f',float),('i',int)]) a = array([(i,i) for i in range(100)], dtype = dt) b = a[::-1] r = np.arange(100) rr = r[::-1] - for kind in ['q'] : + for kind in ['q', 'm', 'h'] : msg = "structured array argsort, kind=%s" % kind assert_equal(a.copy().argsort(kind=kind), r, msg) assert_equal(b.copy().argsort(kind=kind), rr, msg) + # test datetime64 argsorts. + a = np.arange(0, 100, dtype='datetime64[D]') + b = a[::-1] + r = np.arange(100) + rr = r[::-1] + for kind in ['q', 'h', 'm'] : + msg = "datetime64 argsort, kind=%s" % kind + assert_equal(a.copy().argsort(kind=kind), r, msg) + assert_equal(b.copy().argsort(kind=kind), rr, msg) + + # test timedelta64 argsorts. + a = np.arange(0, 100, dtype='timedelta64[D]') + b = a[::-1] + r = np.arange(100) + rr = r[::-1] + for kind in ['q', 'h', 'm'] : + msg = "timedelta64 argsort, kind=%s" % kind + assert_equal(a.copy().argsort(kind=kind), r, msg) + assert_equal(b.copy().argsort(kind=kind), rr, msg) + + # check axis handling. This should be the same for all type # specific argsorts, so we only check it for one type and one kind a = np.array([[3,2],[1,0]]) |