Repository navigation
Add mergesort option for array.sort() #10703
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| Added ``kind`` argument to ``ndarray.sort`` | ||
| ------------------------------------------- | ||
|
|
||
| ``ndarray.sort`` now accepts the ``kind`` argument in nopython mode and | ||
| supports ``"mergesort"`` in addition to the default ``"quicksort"``, matching | ||
| the ``kind`` support already available in ``ndarray.argsort``. Both methods | ||
| also accept ``"stable"`` as an alias for ``"mergesort"`` and reject any other | ||
| ``kind`` at compile time. The default value keeps the existing behaviour. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -39,6 +39,12 @@ def make_temp_array(keys, n): | |
| def sort_usecase(val): | ||
| val.sort() | ||
|
|
||
| def sort_kind_usecase(val, is_stable=False): | ||
| if is_stable: | ||
| val.sort(kind='mergesort') | ||
| else: | ||
| val.sort(kind='quicksort') | ||
|
|
||
| def argsort_usecase(val): | ||
| return val.argsort() | ||
|
|
||
|
|
@@ -846,11 +852,13 @@ def has_duplicates(self, arr): | |
| return True | ||
| return False | ||
|
|
||
| def check_sort_inplace(self, pyfunc, cfunc, val): | ||
| def check_sort_inplace(self, pyfunc, cfunc, val, kwargs=None): | ||
| if kwargs is None: | ||
| kwargs = {} | ||
| expected = copy.copy(val) | ||
| got = copy.copy(val) | ||
| pyfunc(expected) | ||
| cfunc(got) | ||
| pyfunc(expected, **kwargs) | ||
| cfunc(got, **kwargs) | ||
| self.assertPreciseEqual(got, expected) | ||
|
|
||
| def check_sort_copy(self, pyfunc, cfunc, val): | ||
|
|
@@ -900,6 +908,50 @@ def test_array_sort_complex(self): | |
| orig = np.array([complex(*x) for x in zip(real, imag)]) | ||
| self.check_sort_inplace(pyfunc, cfunc, orig) | ||
|
|
||
| def test_array_sort_kind_int(self): | ||
| def check(pyfunc, is_stable): | ||
| cfunc = jit(nopython=True)(pyfunc) | ||
| for orig in self.int_arrays(): | ||
| self.check_sort_inplace(pyfunc, cfunc, orig, | ||
| dict(is_stable=is_stable)) | ||
|
|
||
| check(sort_kind_usecase, is_stable=True) | ||
| check(sort_kind_usecase, is_stable=False) | ||
|
|
||
| def test_array_sort_kind_float(self): | ||
| def check(pyfunc, is_stable): | ||
| cfunc = jit(nopython=True)(pyfunc) | ||
| for orig in self.float_arrays(): | ||
| self.check_sort_inplace(pyfunc, cfunc, orig, | ||
| dict(is_stable=is_stable)) | ||
|
|
||
| check(sort_kind_usecase, is_stable=True) | ||
| check(sort_kind_usecase, is_stable=False) | ||
|
|
||
| def test_array_sort_kind_complex(self): | ||
| def check(pyfunc, is_stable): | ||
| cfunc = jit(nopython=True)(pyfunc) | ||
| for real in self.float_arrays(): | ||
| imag = real[::] | ||
| np.random.shuffle(imag) | ||
| orig = np.array([complex(*x) for x in zip(real, imag)]) | ||
| self.check_sort_inplace(pyfunc, cfunc, orig, | ||
| dict(is_stable=is_stable)) | ||
|
|
||
| check(sort_kind_usecase, is_stable=True) | ||
| check(sort_kind_usecase, is_stable=False) | ||
|
|
||
| def test_array_sort_kind_multidim(self): | ||
| def check(pyfunc, is_stable): | ||
| cfunc = jit(nopython=True)(pyfunc) | ||
| for shape in ((4, 5), (3, 4, 5), (2, 30)): | ||
| orig = np.random.random(shape) * 100 | ||
| self.check_sort_inplace(pyfunc, cfunc, orig, | ||
| dict(is_stable=is_stable)) | ||
|
|
||
| check(sort_kind_usecase, is_stable=True) | ||
| check(sort_kind_usecase, is_stable=False) | ||
|
|
||
| def test_np_sort_int(self): | ||
| pyfunc = np_sort_usecase | ||
| cfunc = jit(nopython=True)(pyfunc) | ||
|
|
@@ -1228,6 +1280,56 @@ def external_key(z): | |
| self.assertIn(expect, str(raises.exception)) | ||
|
|
||
|
|
||
| class TestArraySort(MemoryLeakMixin, TestCase): | ||
| """Tests specific to array.sort""" | ||
|
|
||
| def test_exceptions(self): | ||
|
|
||
| @njit | ||
| def nonliteral_kind(kind): | ||
| np.arange(5).sort(kind=kind) | ||
|
|
||
| # check non-literal kind | ||
| with self.assertRaises(errors.TypingError) as raises: | ||
| # valid spelling but not literal | ||
| nonliteral_kind('quicksort') | ||
|
|
||
| expect = '"kind" must be a string literal' | ||
| self.assertIn(expect, str(raises.exception)) | ||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you add a test here that would check valid sort kinds and invalid kind string literals ?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added |
||
| @njit | ||
| def unsupported_kwarg(): | ||
| np.arange(5).sort(foo='') | ||
|
|
||
| with self.assertRaises(errors.TypingError) as raises: | ||
| unsupported_kwarg() | ||
|
|
||
| expect = "Unsupported keywords: ['foo']" | ||
| self.assertIn(expect, str(raises.exception)) | ||
|
|
||
| @jit | ||
| def unsupported_kind(): | ||
| np.arange(5).sort(kind='heapsort') | ||
|
|
||
| with self.assertRaises(errors.TypingError) as raises: | ||
| unsupported_kind() | ||
|
|
||
| expect = "Unsupported \"kind\": 'heapsort'" | ||
| self.assertIn(expect, str(raises.exception)) | ||
|
|
||
| def test_kinds(self): | ||
| for kind in ('quicksort', 'mergesort', 'stable'): | ||
| @jit | ||
| def sort_kind(arr): | ||
| arr.sort(kind=kind) | ||
|
|
||
| for arr in (np.array([3, 1, 4, 1, 5, 9, 2, 6]), | ||
| np.array([[3, 1, 4, 1], [5, 9, 2, 6]])): | ||
| expected = np.sort(arr, kind=kind) | ||
| sort_kind(arr) | ||
| self.assertPreciseEqual(arr, expected) | ||
|
|
||
|
|
||
| class TestArrayArgsort(MemoryLeakMixin, TestCase): | ||
| """Tests specific to array.argsort""" | ||
|
|
||
|
|
@@ -1255,6 +1357,43 @@ def unsupported_kwarg(): | |
| expect = "Unsupported keywords: ['foo']" | ||
| self.assertIn(expect, str(raises.exception)) | ||
|
|
||
| @jit | ||
| def unsupported_kind(): | ||
| np.arange(5).argsort(kind='heapsort') | ||
|
|
||
| with self.assertRaises(errors.TypingError) as raises: | ||
| unsupported_kind() | ||
|
|
||
| expect = "Unsupported \"kind\": 'heapsort'" | ||
| self.assertIn(expect, str(raises.exception)) | ||
|
|
||
| def test_kinds(self): | ||
| arr = np.array([3, 1, 4, 7, 5, 9, 2, 6]) | ||
| for kind in ('quicksort', 'mergesort', 'stable'): | ||
| @jit | ||
| def argsort_kind(arr): | ||
| return arr.argsort(kind=kind) | ||
|
|
||
| expected = np.argsort(arr, kind=kind) | ||
| self.assertPreciseEqual(argsort_kind(arr), expected) | ||
|
|
||
| # ties and more than SMALL_QUICKSORT elements, so only a stable | ||
| # sort reproduces numpy's index order | ||
| arr = np.array([3, 2, 2, 1, 1, 0, 0, 0, 0, 3, | ||
| 2, 3, 2, 2, 3, 2, 2, 2, 2, 3]) | ||
| expected = np.argsort(arr, kind='stable') | ||
| for kind in ('mergesort', 'stable'): | ||
| @jit | ||
| def argsort_stable(arr): | ||
| return arr.argsort(kind=kind) | ||
|
|
||
| @jit | ||
| def np_argsort_stable(arr): | ||
| return np.argsort(arr, kind=kind) | ||
|
|
||
| self.assertPreciseEqual(argsort_stable(arr), expected) | ||
| self.assertPreciseEqual(np_argsort_stable(arr), expected) | ||
|
|
||
|
|
||
| if __name__ == '__main__': | ||
| unittest.main() | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
kindis only checked for being a string literal here, so any spelling gets through toget_sort_func(). Now there is handling forquicksortandmergesort, but anything else valid or invalid just fails with -From the numpy docs, I gather that
stableismergesortalias -So 'stable' is likely input and we should have proper handling for it. ('heapsort' too) We can reject the not implemented ones. But since we have 'mergesort', we might as well use 'stable' alias.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe you can add value validation after string literal check ?