diff options
| author | Hameer Abbasi <hameerabbasi@yahoo.com> | 2019-05-10 19:14:35 -0700 |
|---|---|---|
| committer | Hameer Abbasi <hameerabbasi@yahoo.com> | 2019-05-11 15:03:48 -0700 |
| commit | 12fb1015511ac3804d0785bb0d1fe539385548ad (patch) | |
| tree | a7ba6590987c2e7a048347c1682f1012e10d6cfb | |
| parent | 634d66d5c5461c6c1480e97c1d3fbc4c4ea96a00 (diff) | |
| download | numpy-12fb1015511ac3804d0785bb0d1fe539385548ad.tar.gz | |
ENH: Radix sort
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | doc/release/1.17.0-notes.rst | 11 | ||||
| -rw-r--r-- | numpy/core/_add_newdocs.py | 4 | ||||
| -rw-r--r-- | numpy/core/defchararray.py | 2 | ||||
| -rw-r--r-- | numpy/core/fromnumeric.py | 16 | ||||
| -rw-r--r-- | numpy/core/setup.py | 1 | ||||
| -rw-r--r-- | numpy/core/src/common/npy_sort.h.src | 11 | ||||
| -rw-r--r-- | numpy/core/src/multiarray/arraytypes.c.src | 13 | ||||
| -rw-r--r-- | numpy/core/src/multiarray/conversion_utils.c | 9 | ||||
| -rw-r--r-- | numpy/core/src/multiarray/item_selection.c | 12 | ||||
| -rw-r--r-- | numpy/core/src/npysort/radixsort.c.src | 229 | ||||
| -rw-r--r-- | numpy/core/tests/test_multiarray.py | 35 | ||||
| -rw-r--r-- | numpy/ma/core.py | 14 |
13 files changed, 313 insertions, 45 deletions
diff --git a/.gitignore b/.gitignore index a31d6ea44..8e96d4154 100644 --- a/.gitignore +++ b/.gitignore @@ -142,6 +142,7 @@ numpy/core/src/npysort/binsearch.c numpy/core/src/npysort/heapsort.c numpy/core/src/npysort/mergesort.c numpy/core/src/npysort/quicksort.c +numpy/core/src/npysort/radixsort.c numpy/core/src/npysort/selection.c numpy/core/src/npysort/timsort.c numpy/core/src/npysort/sort.c diff --git a/doc/release/1.17.0-notes.rst b/doc/release/1.17.0-notes.rst index 71ad17673..72a45823d 100644 --- a/doc/release/1.17.0-notes.rst +++ b/doc/release/1.17.0-notes.rst @@ -110,6 +110,9 @@ random data. The algorithm is stable and requires O(n/2) working space. For details of the algorithm, refer to `CPython listsort.txt <https://github.com/python/cpython/blob/3.7/Objects/listsort.txt>`_. +In addition, for very small dtypes, radix sort is used instead of timsort. In +general, we attempt to use the fastest possible implementation. + ``np.unpackbits`` now accepts a ``count`` parameter --------------------------------------------------- ``count`` allows subsetting the number of bits that will be unpacked up-front, @@ -171,6 +174,14 @@ maintains `O(N log N)` run time complexity instead of deteriorating towards `O(N*N)` for prime lengths. Also, accuracy for real-valued FFTs with near-prime lengths has improved and is on par with complex-valued FFTs. +Performance improvements for integer sorts +------------------------------------------ + +``sort``, ``argsort``, ``ndarray.sort`` and ``ndarray.argsort`` now use radix +sort as the default stable sort for integers and booleans. This is faster than +the old default, mergesort, in the vast majority of cases. + + Further improvements to ``ctypes`` support in ``np.ctypeslib`` -------------------------------------------------------------- A new `numpy.ctypeslib.as_ctypes_type` function has been added, which can be diff --git a/numpy/core/_add_newdocs.py b/numpy/core/_add_newdocs.py index 52ab9c994..3e9d05674 100644 --- a/numpy/core/_add_newdocs.py +++ b/numpy/core/_add_newdocs.py @@ -2614,7 +2614,7 @@ add_newdoc('numpy.core.multiarray', 'ndarray', ('argmin', add_newdoc('numpy.core.multiarray', 'ndarray', ('argsort', """ - a.argsort(axis=-1, kind='quicksort', order=None) + a.argsort(axis=-1, kind=None, order=None) Returns the indices that would sort this array. @@ -3800,7 +3800,7 @@ add_newdoc('numpy.core.multiarray', 'ndarray', ('setflags', add_newdoc('numpy.core.multiarray', 'ndarray', ('sort', """ - a.sort(axis=-1, kind='quicksort', order=None) + a.sort(axis=-1, kind=None, order=None) Sort an array in-place. Refer to `numpy.sort` for full documentation. diff --git a/numpy/core/defchararray.py b/numpy/core/defchararray.py index 3fd7d14c4..d7ecce1b4 100644 --- a/numpy/core/defchararray.py +++ b/numpy/core/defchararray.py @@ -2124,7 +2124,7 @@ class chararray(ndarray): def __rmod__(self, other): return NotImplemented - def argsort(self, axis=-1, kind='quicksort', order=None): + def argsort(self, axis=-1, kind=None, order=None): """ Return the indices that sort the array lexicographically. diff --git a/numpy/core/fromnumeric.py b/numpy/core/fromnumeric.py index b4d721940..7024ac237 100644 --- a/numpy/core/fromnumeric.py +++ b/numpy/core/fromnumeric.py @@ -824,7 +824,7 @@ def _sort_dispatcher(a, axis=None, kind=None, order=None): @array_function_dispatch(_sort_dispatcher) -def sort(a, axis=-1, kind='quicksort', order=None): +def sort(a, axis=-1, kind=None, order=None): """ Return a sorted copy of an array. @@ -837,8 +837,8 @@ def sort(a, axis=-1, kind='quicksort', order=None): sorting. The default is -1, which sorts along the last axis. kind : {'quicksort', 'mergesort', 'heapsort', 'stable'}, optional Sorting algorithm. The default is 'quicksort'. Note that both 'stable' - and 'mergesort' use timsort under the covers and, in general, the - actual implementation will vary with data type. The 'mergesort' option + and 'mergesort' use timsort or radix sort under the covers and, in general, + the actual implementation will vary with data type. The 'mergesort' option is retained for backwards compatibility. .. versionchanged:: 1.15.0. @@ -914,7 +914,8 @@ def sort(a, axis=-1, kind='quicksort', order=None): 'stable' automatically choses the best stable sorting algorithm for the data type being sorted. It, along with 'mergesort' is - currently mapped to timsort. API forward compatibility currently limits the + currently mapped to timsort or radix sort depending on the + data type. API forward compatibility currently limits the ability to select the implementation and it is hardwired for the different data types. @@ -925,7 +926,8 @@ def sort(a, axis=-1, kind='quicksort', order=None): mergesort. It is now used for stable sort while quicksort is still the default sort if none is chosen. For details of timsort, refer to `CPython listsort.txt <https://github.com/python/cpython/blob/3.7/Objects/listsort.txt>`_. - + 'mergesort' and 'stable' are mapped to radix sort for integer data types. Radix sort is an + O(n) sort instead of O(n log n). Examples -------- @@ -974,7 +976,7 @@ def _argsort_dispatcher(a, axis=None, kind=None, order=None): @array_function_dispatch(_argsort_dispatcher) -def argsort(a, axis=-1, kind='quicksort', order=None): +def argsort(a, axis=-1, kind=None, order=None): """ Returns the indices that would sort an array. @@ -997,8 +999,6 @@ def argsort(a, axis=-1, kind='quicksort', order=None): .. versionchanged:: 1.15.0. The 'stable' option was added. - - order : str or list of str, optional When `a` is an array with fields defined, this argument specifies which fields to compare first, second, etc. A single field can diff --git a/numpy/core/setup.py b/numpy/core/setup.py index 45b4fb3c7..81d140d5e 100644 --- a/numpy/core/setup.py +++ b/numpy/core/setup.py @@ -710,6 +710,7 @@ def configuration(parent_package='',top_path=None): join('src', 'npysort', 'mergesort.c.src'), join('src', 'npysort', 'timsort.c.src'), join('src', 'npysort', 'heapsort.c.src'), + join('src', 'npysort', 'radixsort.c.src'), join('src', 'common', 'npy_partition.h.src'), join('src', 'npysort', 'selection.c.src'), join('src', 'common', 'npy_binsearch.h.src'), diff --git a/numpy/core/src/common/npy_sort.h.src b/numpy/core/src/common/npy_sort.h.src index 521f0fee5..16a105499 100644 --- a/numpy/core/src/common/npy_sort.h.src +++ b/numpy/core/src/common/npy_sort.h.src @@ -44,6 +44,17 @@ int atimsort_@suff@(void *vec, npy_intp *ind, npy_intp cnt, void *null); /**end repeat**/ +/**begin repeat + * + * #suff = bool, byte, ubyte, short, ushort, int, uint, long, ulong, + * longlong, ulonglong# + */ + +int radixsort_@suff@(void *vec, npy_intp cnt, void *null); +int aradixsort_@suff@(void *vec, npy_intp *ind, npy_intp cnt, void *null); + +/**end repeat**/ + /* diff --git a/numpy/core/src/multiarray/arraytypes.c.src b/numpy/core/src/multiarray/arraytypes.c.src index 49819ca4a..c2b932d8d 100644 --- a/numpy/core/src/multiarray/arraytypes.c.src +++ b/numpy/core/src/multiarray/arraytypes.c.src @@ -4405,6 +4405,7 @@ static PyArray_Descr @from@_Descr = { * npy_half, npy_float, npy_double, npy_longdouble, * npy_cfloat, npy_cdouble, npy_clongdouble, * PyObject *, npy_datetime, npy_timedelta# + * #rsort = 1*5, 0*16# * #NAME = Bool, * Byte, UByte, Short, UShort, Int, UInt, * Long, ULong, LongLong, ULongLong, @@ -4461,12 +4462,20 @@ static PyArray_ArrFuncs _Py@NAME@_ArrFuncs = { { quicksort_@suff@, heapsort_@suff@, - timsort_@suff@ + #if @rsort@ + radixsort_@suff@ + #else + timsort_@suff@ + #endif }, { aquicksort_@suff@, aheapsort_@suff@, - atimsort_@suff@ + #if @rsort@ + aradixsort_@suff@ + #else + atimsort_@suff@ + #endif }, #else { diff --git a/numpy/core/src/multiarray/conversion_utils.c b/numpy/core/src/multiarray/conversion_utils.c index 9afcfd86d..a370874a6 100644 --- a/numpy/core/src/multiarray/conversion_utils.c +++ b/numpy/core/src/multiarray/conversion_utils.c @@ -393,6 +393,11 @@ PyArray_SortkindConverter(PyObject *obj, NPY_SORTKIND *sortkind) char *str; PyObject *tmp = NULL; + if (obj == Py_None) { + *sortkind = NPY_QUICKSORT; + return NPY_SUCCEED; + } + if (PyUnicode_Check(obj)) { obj = tmp = PyUnicode_AsASCIIString(obj); if (obj == NULL) { @@ -401,6 +406,8 @@ PyArray_SortkindConverter(PyObject *obj, NPY_SORTKIND *sortkind) } *sortkind = NPY_QUICKSORT; + + str = PyBytes_AsString(obj); if (!str) { Py_XDECREF(tmp); @@ -424,7 +431,7 @@ PyArray_SortkindConverter(PyObject *obj, NPY_SORTKIND *sortkind) * That maintains backwards compatibility while * allowing other types of stable sorts to be used. */ - *sortkind = NPY_STABLESORT; + *sortkind = NPY_MERGESORT; } else if (str[0] == 's' || str[0] == 'S') { /* diff --git a/numpy/core/src/multiarray/item_selection.c b/numpy/core/src/multiarray/item_selection.c index 6065c1df4..f4d2513ca 100644 --- a/numpy/core/src/multiarray/item_selection.c +++ b/numpy/core/src/multiarray/item_selection.c @@ -1126,7 +1126,7 @@ fail: NPY_NO_EXPORT int PyArray_Sort(PyArrayObject *op, int axis, NPY_SORTKIND which) { - PyArray_SortFunc *sort; + PyArray_SortFunc *sort = NULL; int n = PyArray_NDIM(op); if (check_and_adjust_axis(&axis, n) < 0) { @@ -1143,6 +1143,7 @@ PyArray_Sort(PyArrayObject *op, int axis, NPY_SORTKIND which) } sort = PyArray_DESCR(op)->f->sort[which]; + if (sort == NULL) { if (PyArray_DESCR(op)->f->compare) { switch (which) { @@ -1284,16 +1285,11 @@ NPY_NO_EXPORT PyObject * PyArray_ArgSort(PyArrayObject *op, int axis, NPY_SORTKIND which) { PyArrayObject *op2; - PyArray_ArgSortFunc *argsort; + PyArray_ArgSortFunc *argsort = NULL; PyObject *ret; - if (which < 0 || which >= NPY_NSORTS) { - PyErr_SetString(PyExc_ValueError, - "not a valid sort kind"); - return NULL; - } - argsort = PyArray_DESCR(op)->f->argsort[which]; + if (argsort == NULL) { if (PyArray_DESCR(op)->f->compare) { switch (which) { diff --git a/numpy/core/src/npysort/radixsort.c.src b/numpy/core/src/npysort/radixsort.c.src new file mode 100644 index 000000000..c1435bd96 --- /dev/null +++ b/numpy/core/src/npysort/radixsort.c.src @@ -0,0 +1,229 @@ +#define NPY_NO_DEPRECATED_API NPY_API_VERSION + +#include "npy_sort.h" +#include "npysort_common.h" +#include <stdlib.h> + +/* + ***************************************************************************** + ** INTEGER SORTS ** + ***************************************************************************** + */ + + +/**begin repeat + * + * #TYPE = BOOL, BYTE, UBYTE, SHORT, USHORT, INT, UINT, LONG, ULONG, + * LONGLONG, ULONGLONG# + * #suff = bool, byte, ubyte, short, ushort, int, uint, long, ulong, + * longlong, ulonglong# + * #type = npy_ubyte, npy_ubyte, npy_ubyte, npy_ushort, npy_ushort, npy_uint, + * npy_uint, npy_ulong, npy_ulong, npy_ulonglong, npy_ulonglong# + * #sign = 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0# + * #floating = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0# + */ + +// Reference: https://github.com/eloj/radix-sorting#-key-derivation +#if @sign@ + // Floating-point is currently disabled. + // Floating-point tests succeed for double and float on macOS but not on Windows/Linux. + // Basic sorting tests succeed but others relying on sort fail. + // Possibly related to floating-point normalisation or multiple NaN reprs? Not sure. + #if @floating@ + // For floats, we invert the key if the sign bit is set, else we invert the sign bit. + #define KEY_OF(x) ((x) ^ (-((x) >> (sizeof(@type@) * 8 - 1)) | ((@type@)1 << (sizeof(@type@) * 8 - 1)))) + #else + // For signed ints, we flip the sign bit so the negatives are below the positives. + #define KEY_OF(x) ((x) ^ ((@type@)1 << (sizeof(@type@) * 8 - 1))) + #endif +#else + // For unsigned ints, the key is as-is + #define KEY_OF(x) (x) +#endif + +static inline npy_ubyte +nth_byte_@suff@(@type@ key, npy_intp l) { + return (key >> (l << 3)) & 0xFF; +} + +@type@* +radixsort0_@suff@(@type@ *arr, @type@ *aux, npy_intp num) +{ + npy_intp cnt[sizeof(@type@)][1 << 8] = { { 0 } }; + npy_intp i, l; + @type@ key0 = KEY_OF(arr[0]); + npy_intp ncols = 0; + npy_ubyte cols[sizeof(@type@)]; + + for (i = 0; i < num; i++) { + @type@ k = KEY_OF(arr[i]); + + for (l = 0; l < sizeof(@type@); l++) { + cnt[l][nth_byte_@suff@(k, l)]++; + } + } + + for (l = 0; l < sizeof(@type@); l++) { + if (cnt[l][nth_byte_@suff@(key0, l)] != num) { + cols[ncols++] = l; + } + } + + for (l = 0; l < ncols; l++) { + npy_intp a = 0; + for (i = 0; i < 256; i++) { + npy_intp b = cnt[cols[l]][i]; + cnt[cols[l]][i] = a; + a += b; + } + } + + for (l = 0; l < ncols; l++) { + @type@* temp; + for (i = 0; i < num; i++) { + @type@ k = KEY_OF(arr[i]); + npy_intp dst = cnt[cols[l]][nth_byte_@suff@(k, cols[l])]++; + aux[dst] = arr[i]; + } + + temp = aux; + aux = arr; + arr = temp; + } + + return arr; +} + +int +radixsort_@suff@(void *start, npy_intp num, void *NPY_UNUSED(varr)) +{ + void *sorted; + @type@ *aux; + @type@ *arr = start; + @type@ k1, k2; + npy_bool all_sorted = 1; + + if (num < 2) { + return 0; + } + + k1 = KEY_OF(arr[0]); + for (npy_intp i = 1; i < num; i++) { + k2 = KEY_OF(arr[i]); + if (k1 > k2) { + all_sorted = 0; + break; + } + k1 = k2; + } + + if (all_sorted) { + return 0; + } + + aux = malloc(num * sizeof(@type@)); + if (aux == NULL) { + return -NPY_ENOMEM; + } + + sorted = radixsort0_@suff@(start, aux, num); + if (sorted != start) { + memcpy(start, sorted, num * sizeof(@type@)); + } + + free(aux); + return 0; +} + +npy_intp* +aradixsort0_@suff@(@type@ *arr, npy_intp *aux, npy_intp *tosort, npy_intp num) +{ + npy_intp cnt[sizeof(@type@)][1 << 8] = { { 0 } }; + npy_intp i, l; + @type@ key0 = KEY_OF(arr[0]); + npy_intp ncols = 0; + npy_ubyte cols[sizeof(@type@)]; + + for (i = 0; i < num; i++) { + @type@ k = KEY_OF(arr[i]); + + for (l = 0; l < sizeof(@type@); l++) { + cnt[l][nth_byte_@suff@(k, l)]++; + } + } + + for (l = 0; l < sizeof(@type@); l++) { + if (cnt[l][nth_byte_@suff@(key0, l)] != num) { + cols[ncols++] = l; + } + } + + for (l = 0; l < ncols; l++) { + npy_intp a = 0; + for (i = 0; i < 256; i++) { + npy_intp b = cnt[cols[l]][i]; + cnt[cols[l]][i] = a; + a += b; + } + } + + for (l = 0; l < ncols; l++) { + npy_intp* temp; + for (i = 0; i < num; i++) { + @type@ k = KEY_OF(arr[tosort[i]]); + npy_intp dst = cnt[cols[l]][nth_byte_@suff@(k, cols[l])]++; + aux[dst] = tosort[i]; + } + + temp = aux; + aux = tosort; + tosort = temp; + } + + return tosort; +} + +int +aradixsort_@suff@(void *start, npy_intp* tosort, npy_intp num, void *NPY_UNUSED(varr)) +{ + npy_intp *sorted; + npy_intp *aux; + @type@ *arr = start; + @type@ k1, k2; + npy_bool all_sorted = 1; + + if (num < 2) { + return 0; + } + + k1 = KEY_OF(arr[0]); + for (npy_intp i = 1; i < num; i++) { + k2 = KEY_OF(arr[i]); + if (k1 > k2) { + all_sorted = 0; + break; + } + k1 = k2; + } + + if (all_sorted) { + return 0; + } + + aux = malloc(num * sizeof(npy_intp)); + if (aux == NULL) { + return -NPY_ENOMEM; + } + + sorted = aradixsort0_@suff@(start, aux, tosort, num); + if (sorted != tosort) { + memcpy(tosort, sorted, num * sizeof(npy_intp)); + } + + free(aux); + return 0; +} + +#undef KEY_OF + +/**end repeat**/ diff --git a/numpy/core/tests/test_multiarray.py b/numpy/core/tests/test_multiarray.py index b29daa675..f5d06588e 100644 --- a/numpy/core/tests/test_multiarray.py +++ b/numpy/core/tests/test_multiarray.py @@ -1616,16 +1616,17 @@ class TestMethods(object): # of sorted items must be greater than ~50 to check the actual # algorithm because quick and merge sort fall over to insertion # sort for small arrays. - a = np.arange(101) - b = a[::-1].copy() - for kind in self.sort_kinds: - msg = "scalar 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) + for dtype in [np.int32, np.uint32, np.float32]: + a = np.arange(101, dtype=dtype) + b = a[::-1].copy() + for kind in self.sort_kinds: + msg = "scalar sort, kind=%s, dtype=%s" % (kind, dtype) + 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 complex sorts. These use the same code as the scalars # but the compare function differs. @@ -1881,12 +1882,14 @@ class TestMethods(object): # of sorted items must be greater than ~50 to check the actual # algorithm because quick and merge sort fall over to insertion # sort for small arrays. - a = np.arange(101) - b = a[::-1].copy() - for kind in self.sort_kinds: - msg = "scalar argsort, kind=%s" % kind - assert_equal(a.copy().argsort(kind=kind), a, msg) - assert_equal(b.copy().argsort(kind=kind), b, msg) + + for dtype in [np.int32, np.uint32, np.float32]: + a = np.arange(101, dtype=dtype) + b = a[::-1].copy() + for kind in self.sort_kinds: + msg = "scalar argsort, kind=%s, dtype=%s" % (kind, dtype) + assert_equal(a.copy().argsort(kind=kind), a, msg) + assert_equal(b.copy().argsort(kind=kind), b, msg) # test complex argsorts. These use the same code as the scalars # but the compare function differs. diff --git a/numpy/ma/core.py b/numpy/ma/core.py index cd0b4e0a6..7ed099722 100644 --- a/numpy/ma/core.py +++ b/numpy/ma/core.py @@ -5354,7 +5354,7 @@ class MaskedArray(ndarray): out.__setmask__(self._mask) return out - def argsort(self, axis=np._NoValue, kind='quicksort', order=None, + def argsort(self, axis=np._NoValue, kind=None, order=None, endwith=True, fill_value=None): """ Return an ndarray of indices that sort the array along the @@ -5374,7 +5374,7 @@ class MaskedArray(ndarray): Until then, the axis should be given explicitly when ``arr.ndim > 1``, to avoid a FutureWarning. kind : {'quicksort', 'mergesort', 'heapsort', 'stable'}, optional - Sorting algorithm. + The sorting algorithm used. order : list, optional When `a` is an array with fields defined, this argument specifies which fields to compare first, second, etc. Not all fields need be @@ -5516,7 +5516,7 @@ class MaskedArray(ndarray): d = self.filled(fill_value).view(ndarray) return d.argmax(axis, out=out) - def sort(self, axis=-1, kind='quicksort', order=None, + def sort(self, axis=-1, kind=None, order=None, endwith=True, fill_value=None): """ Sort the array, in-place @@ -5529,7 +5529,7 @@ class MaskedArray(ndarray): Axis along which to sort. If None, the array is flattened before sorting. The default is -1, which sorts along the last axis. kind : {'quicksort', 'mergesort', 'heapsort', 'stable'}, optional - Sorting algorithm. Default is 'quicksort'. + The sorting algorithm used. order : list, optional When `a` is a structured array, this argument specifies which fields to compare first, second, and so on. This list does not need to @@ -5537,7 +5537,7 @@ class MaskedArray(ndarray): endwith : {True, False}, optional Whether missing values (if any) should be treated as the largest values (True) or the smallest values (False) - When the array contains unmasked values at the same extremes of the + When the array contains unmasked values sorting at the same extremes of the datatype, the ordering of these values and the masked values is undefined. fill_value : {var}, optional @@ -6734,7 +6734,7 @@ def power(a, b, third=None): argmin = _frommethod('argmin') argmax = _frommethod('argmax') -def argsort(a, axis=np._NoValue, kind='quicksort', order=None, endwith=True, fill_value=None): +def argsort(a, axis=np._NoValue, kind=None, order=None, endwith=True, fill_value=None): "Function version of the eponymous method." a = np.asanyarray(a) @@ -6749,7 +6749,7 @@ def argsort(a, axis=np._NoValue, kind='quicksort', order=None, endwith=True, fil return a.argsort(axis=axis, kind=kind, order=order) argsort.__doc__ = MaskedArray.argsort.__doc__ -def sort(a, axis=-1, kind='quicksort', order=None, endwith=True, fill_value=None): +def sort(a, axis=-1, kind=None, order=None, endwith=True, fill_value=None): "Function version of the eponymous method." a = np.array(a, copy=True, subok=True) if axis is None: |
