diff --git a/CHANGELOG.md b/CHANGELOG.md index eb9824e..b88c3a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,4 +11,10 @@ # Marek Updates: - Reformatted Insertion sort docstring to match other functions (commit link: bac027abb6ec4efc8962fd2e81ed82a4f3185a00) - Reformatted Insertion sort code body to handle exceptions like other functions (commit link: bac027abb6ec4efc8962fd2e81ed82a4f3185a00) -- Reformatted test file for insertion sort (commit link: bac027abb6ec4efc8962fd2e81ed82a4f3185a00) \ No newline at end of file +- Reformatted test file for insertion sort (commit link: bac027abb6ec4efc8962fd2e81ed82a4f3185a00) + +# Sid Updates: +- Reformatted Shell sort docstring to match other functions (commit link: 8725fa1) +- Reformatted Shell sort code body to handle exceptions like other functions (commit link: 8725fa1) +- Reformatted test file for shell sort (commit link: 837e1ec) +- Updated init file for shell sort (commit link: 36f25fd) \ No newline at end of file diff --git a/src/pysorting/__init__.py b/src/pysorting/__init__.py index e297f29..8382665 100644 --- a/src/pysorting/__init__.py +++ b/src/pysorting/__init__.py @@ -4,7 +4,7 @@ from pysorting.bubblesort import bubble_sort from pysorting.quicksort import quick_sort -from pysorting.shell_sort import shell_sort +from pysorting.shellsort import shell_sort from pysorting.insertionsort import insertion_sort from pysorting.utils import ( diff --git a/src/pysorting/shellsort.py b/src/pysorting/shellsort.py new file mode 100644 index 0000000..373846b --- /dev/null +++ b/src/pysorting/shellsort.py @@ -0,0 +1,90 @@ +""" +This module implements the Shell Sort algorithm in Python. + +Shell Sort is a comparison-based sorting algorithm that generalizes the Shell Sort algorithm. +It starts by sorting pairs of elements far apart from each other and then progressively reduces the gap +between elements to be compared, ultimately achieving a sorted list. + +@author: Siddarth Subrahmanian +""" + +from .utils import (validate_list_elements, + InvalidElementTypeError, + NonUniformTypeError, + InvalidAscendingTypeError) + +def shell_sort(arr: list[float], ascending: bool = True) -> list[float]: + """ + Sorts an array using the Shell Sort algorithm. + + Shell Sort repeatedly compares elements separated by a specific gap and rearranges them in the correct order. + The gap is reduced over iterations until it becomes 1, at which point the list is fully sorted. + The sorting order can be controlled using the `ascending` parameter. + + Parameters + ---------- + arr : list[float] + The array of numeric values to be sorted. + ascending : bool, optional + If `True` (default), sorts the array in ascending order. If `False`, sorts the array in descending order. + + Returns + ------- + list[float] + A sorted array in ascending order if `ascending=True`, or in descending order if `ascending=False`. + + Raises + ------ + TypeError + If the input is not a list. + InvalidElementTypeError + If the list contains non-numeric elements or string values. + NonUniformTypeError + If the list contains more than one form of data type. + InvalidAscendingTypeError + If the `ascending` parameter is not a boolean. + + Notes + ----- + - Shell Sort is an improvement over Bubble Sort and Insertion Sort, with a time complexity of O(n^2) in the worst case. + - This algorithm is more efficient than Bubble Sort for larger datasets. + + Examples + -------- + Sorting in ascending order (default): + >>> shell_sort([5, 2, 8, 3, 1]) + [1, 2, 3, 5, 8] + + Sorting in descending order: + >>> shell_sort([3.5, 1.2, 2.8, 0.5], ascending=False) + [3.5, 2.8, 1.2, 0.5] + + >>> shell_sort([1, 2, 3, 4, 5], ascending=True) + [1, 2, 3, 4, 5] + """ + if not isinstance(arr, list): + raise TypeError("Input must be a list.") + if not all(isinstance(x, (int, float, str)) for x in arr): + raise InvalidElementTypeError() + if not validate_list_elements(arr): + raise NonUniformTypeError() + if not isinstance(ascending, bool): + raise InvalidAscendingTypeError() + + # Implementation of the Shell Sort algorithm + gap = len(arr) // 2 + + while gap > 0: + for i in range(gap, len(arr)): + temp = arr[i] + j = i + while j >= gap and ( + (ascending and arr[j - gap] > temp) or (not ascending and arr[j - gap] < temp) + ): + arr[j] = arr[j - gap] + j -= gap + arr[j] = temp + gap //= 2 + + print(f"Done sorting the array in {'ascending' if ascending else 'descending'} order.") + return arr diff --git a/tests/shellsort_test.py b/tests/shellsort_test.py new file mode 100644 index 0000000..d98723a --- /dev/null +++ b/tests/shellsort_test.py @@ -0,0 +1,90 @@ +# tests/shellsort_test.py +import pytest +from pysorting import (shell_sort, + InvalidElementTypeError, + NonUniformTypeError, + InvalidAscendingTypeError) + +def test_empty_array(): + """Test shell sort with an empty array""" + assert shell_sort([]) == [] + +def test_single_element_array(): + """Test shell sort with a single-element array""" + assert shell_sort([1]) == [1] + +def test_already_sorted_array(): + """Test shell sort with an already sorted array""" + assert shell_sort([1, 2, 3, 4, 5]) == [1, 2, 3, 4, 5] + +def test_unsorted_array(): + """Test shell sort with an unsorted array""" + assert shell_sort([5, 2, 8, 3, 1]) == [1, 2, 3, 5, 8] + +def test_array_with_duplicates(): + """Test shell sort with an array containing duplicates""" + assert shell_sort([5, 2, 8, 3, 1, 2, 5]) == [1, 2, 2, 3, 5, 5, 8] + +def test_array_with_negative_numbers(): + """Test shell sort with an array containing negative numbers""" + assert shell_sort([5, -2, 8, -3, 1]) == [-3, -2, 1, 5, 8] + +def test_reverse_sorted_list(test_data1): + """Test if a reverse sorting works properly.""" + expected = [8, 7, 6, 5, 4, 3, 2, 1] + actual = shell_sort(test_data1, ascending=False) + + assert isinstance(actual, list) + assert actual == expected + + +def test_unsorted_list(test_data1): + """Test if an unsorted list is sorted correctly.""" + expected = [1, 2, 3, 4, 5, 6, 7, 8] + actual = shell_sort(test_data1) + + assert isinstance(actual, list) + assert actual == expected + + +def test_single_element_list(test_data_single_element): + """Test if a single-element list is handled correctly.""" + expected = [5] + actual = shell_sort(test_data_single_element, ascending=False) + + assert isinstance(actual, list) + assert len(actual) == 1 + assert actual == expected + + +def test_empty_list(test_data_empty): + """Test if an empty list is handled correctly.""" + + actual = shell_sort(test_data_empty) + + assert isinstance(actual, list) + assert len(actual) == 0 + + +def test_type_error(test_data_error2): + """Test if a TypeError is raised for non-list inputs.""" + with pytest.raises(TypeError): + shell_sort(test_data_error2) + + +def test_invalid_type_error(test_invalid_error): + """Test if a TypeError is raised for non-list inputs.""" + with pytest.raises(InvalidElementTypeError): + shell_sort(test_invalid_error) + + +def test_non_uniform_error(test_nonuniform_error): + """Test if a TypeError is raised for non-list inputs.""" + with pytest.raises(NonUniformTypeError): + shell_sort(test_nonuniform_error) + + +def test_invalid_ascending_type(): + """Test if InvalidAscendingTypeError is raised when ascending parameter is not a boolean.""" + with pytest.raises(InvalidAscendingTypeError): + shell_sort([1, 2, 3], ascending="not_a_boolean") \ No newline at end of file diff --git a/tests/test_shell_sort.py b/tests/test_shell_sort.py deleted file mode 100644 index f7a9522..0000000 --- a/tests/test_shell_sort.py +++ /dev/null @@ -1,30 +0,0 @@ -# tests/test_shell_sort.py - -from pysorting.shell_sort import shell_sort -import pytest - -# tests/test_shell_sort.py - -def test_empty_array(): - """Test shell sort with an empty array""" - assert shell_sort([]) == [] - -def test_single_element_array(): - """Test shell sort with a single-element array""" - assert shell_sort([1]) == [1] - -def test_already_sorted_array(): - """Test shell sort with an already sorted array""" - assert shell_sort([1, 2, 3, 4, 5]) == [1, 2, 3, 4, 5] - -def test_unsorted_array(): - """Test shell sort with an unsorted array""" - assert shell_sort([5, 2, 8, 3, 1]) == [1, 2, 3, 5, 8] - -def test_array_with_duplicates(): - """Test shell sort with an array containing duplicates""" - assert shell_sort([5, 2, 8, 3, 1, 2, 5]) == [1, 2, 2, 3, 5, 5, 8] - -def test_array_with_negative_numbers(): - """Test shell sort with an array containing negative numbers""" - assert shell_sort([5, -2, 8, -3, 1]) == [-3, -2, 1, 5, 8] \ No newline at end of file