diff --git a/pydatastructs/linear_data_structures/algorithms.py b/pydatastructs/linear_data_structures/algorithms.py index 792bce855..2f0d69724 100644 --- a/pydatastructs/linear_data_structures/algorithms.py +++ b/pydatastructs/linear_data_structures/algorithms.py @@ -30,7 +30,8 @@ 'jump_search', 'selection_sort', 'insertion_sort', - 'intro_sort' + 'intro_sort', + 'radix_sort' ] def _merge(array, sl, el, sr, er, end, comp): @@ -1843,6 +1844,111 @@ def partition(array, lower, upper): elif maxdepth == 0: heapsort(array, start=lower, end=upper) return array + +def radix_sort(array: Array, **kwargs) -> Array: + """ + Performs radix sort on the given array of non-negative integers. + Uses counting sort as a subroutine to sort by each digit position. + + Parameters + ========== + + array: Array + The array which is to be sorted. Must contain only non-negative integers. + start: int + The starting index of the portion + which is to be sorted. + Optional, by default 0 + end: int + The ending index of the portion which + is to be sorted. + Optional, by default the index + of the last position filled. + backend: pydatastructs.Backend + The backend to be used. + Optional, by default, the best available + backend is used. + + Returns + ======= + + output: Array + The sorted array. + + Examples + ======== + + >>> from pydatastructs import OneDimensionalArray, radix_sort + >>> arr = OneDimensionalArray(int,[170, 45, 75, 90, 802, 24, 2, 66]) + >>> out = radix_sort(arr) + >>> str(out) + '[2, 24, 45, 66, 75, 90, 170, 802]' + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Radix_sort + + Note + ==== + + This implementation: + 1. Only works with non-negative integers + 2. Uses LSD (Least Significant Digit) radix sort + 3. Uses base-10 digits + """ + raise_if_backend_is_not_python( + radix_sort, kwargs.get('backend', Backend.PYTHON)) + + # Find maximum number to know number of digits + max_val = 0 + start = kwargs.get('start', 0) + end = kwargs.get('end', len(array) - 1) + + for i in range(start, end + 1): + if array[i] is not None and array[i] > max_val: + max_val = array[i] + + # Do counting sort for every digit + exp = 1 + while max_val // exp > 0: + # Create output array with same type as input + output = type(array)(array._dtype, [array[i] for i in range(len(array))]) + if _check_type(output, DynamicArray): + output._modify(force=True) + + # Store count of occurrences + count = [0] * 10 + + # Count occurrences of current digit + for i in range(start, end + 1): + if array[i] is not None: + idx = (array[i] // exp) % 10 + count[idx] += 1 + + # Change count[i] to position of digit in output + for i in range(1, 10): + count[i] += count[i - 1] + + # Build output array + i = end + while i >= start: + if array[i] is not None: + idx = (array[i] // exp) % 10 + output[start + count[idx] - 1] = array[i] + count[idx] -= 1 + i -= 1 + + # Copy output array to array + for i in range(start, end + 1): + array[i] = output[i] + + exp *= 10 + + if _check_type(array, (DynamicArray, _arrays.DynamicOneDimensionalArray)): + array._modify(True) + + return array else: p = partition(array, lower, upper) diff --git a/pydatastructs/linear_data_structures/tests/test_algorithms.py b/pydatastructs/linear_data_structures/tests/test_algorithms.py index 46609544b..836a58020 100644 --- a/pydatastructs/linear_data_structures/tests/test_algorithms.py +++ b/pydatastructs/linear_data_structures/tests/test_algorithms.py @@ -5,7 +5,7 @@ cocktail_shaker_sort, quick_sort, longest_common_subsequence, is_ordered, upper_bound, lower_bound, longest_increasing_subsequence, next_permutation, prev_permutation, bubble_sort, linear_search, binary_search, jump_search, - selection_sort, insertion_sort, intro_sort, Backend) + selection_sort, insertion_sort, intro_sort, Backend, radix_sort) from pydatastructs.utils.raises_util import raises import random @@ -414,3 +414,45 @@ def test_binary_search(): def test_jump_search(): _test_common_search(jump_search) _test_common_search(jump_search, backend=Backend.CPP) + +def test_radix_sort(): + random.seed(1000) + + # Test with DynamicOneDimensionalArray + n = random.randint(10, 20) + arr = DynamicOneDimensionalArray(int, 0) + for _ in range(n): + arr.append(random.randint(1, 1000)) + for _ in range(n//3): + arr.delete(random.randint(0, n//2)) + + # Test full array sort + expected_arr = [102, 134, 228, 247, 362, 373, 448, + 480, 548, 686, 688, 696, 779] + assert radix_sort(arr)._data == expected_arr + + # Test with OneDimensionalArray + arr = OneDimensionalArray(int, [170, 45, 75, 90, 802, 24, 2, 66]) + expected_arr = [2, 24, 45, 66, 75, 90, 170, 802] + out = radix_sort(arr) + assert [out[i] for i in range(len(out))] == expected_arr + + # Test partial sort with start/end + arr = OneDimensionalArray(int, [170, 45, 75, 90, 802, 24, 2, 66]) + out = radix_sort(arr, start=2, end=5) + expected_arr = [170, 45, 75, 90, 802, 24, 2, 66] + assert [out[i] for i in range(len(out))] == expected_arr + + # Test with None values + arr = DynamicOneDimensionalArray(int, 0) + arr.append(45) + arr.append(None) + arr.append(12) + arr.append(None) + arr.append(89) + out = radix_sort(arr) + assert out[0] == 12 + assert out[1] == 45 + assert out[2] == 89 + assert out[3] is None + assert out[4] is None