From 3bb45980c834652717077ad5165503dbf0843830 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C5=A0oltis?= Date: Fri, 29 Sep 2023 11:47:42 +0200 Subject: [PATCH] Split sorting.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michal Ĺ oltis --- pysortlib/__init__.py | 3 +- pysortlib/sorting.py | 224 ++++++++++++----------------------- pysortlib/sorting_linear.py | 75 ++++++++++++ pysortlib/utils.py | 7 ++ tests/test_sorting.py | 72 +++-------- tests/test_sorting_linear.py | 33 ++++++ 6 files changed, 204 insertions(+), 210 deletions(-) create mode 100644 pysortlib/sorting_linear.py create mode 100644 tests/test_sorting_linear.py diff --git a/pysortlib/__init__.py b/pysortlib/__init__.py index c3013a1..2599827 100644 --- a/pysortlib/__init__.py +++ b/pysortlib/__init__.py @@ -2,12 +2,11 @@ from .sorting import ( bubble_sort, - counting_sort, heap_sort, insert_sort, merge_sort, quick_sort, - radix_sort, selection_sort, shell_sort, ) +from .sorting_linear import counting_sort, radix_sort diff --git a/pysortlib/sorting.py b/pysortlib/sorting.py index 58d4902..38aeca7 100644 --- a/pysortlib/sorting.py +++ b/pysortlib/sorting.py @@ -1,12 +1,10 @@ from __future__ import annotations -from itertools import chain, repeat - from pysortlib.utils import swap -def insert_sort(array: list[int]) -> None: - """Sorts an array of integers using the insertion sort algorithm. +def bubble_sort(array: list[int]) -> None: + """Sorts an array of integers using the bubble sort algorithm. Time complexity: O(n^2), where n is the length of the array. Extra space complexity: O(1) @@ -17,19 +15,61 @@ def insert_sort(array: list[int]) -> None: :param array: array of integers :return: None """ - for i in range(1, len(array)): - current = array[i] - j = i + length = len(array) + for i in range(length): + for j in range(length - i - 1): + if array[j] > array[j + 1]: + swap(array, j, j + 1) - while j > 0 and array[j - 1] > current: - array[j] = array[j - 1] - j -= 1 - array[j] = current +def _left_child(index: int) -> int: + return 2 * index + 1 -def bubble_sort(array: list[int]) -> None: - """Sorts an array of integers using the bubble sort algorithm. +def _right_child(index: int) -> int: + return 2 * index + 2 + + +def _heapify(array: list[int], size: int, index: int) -> None: + largest = index + left = _left_child(index) + right = _right_child(index) + + if left < size and array[left] > array[largest]: + largest = left + if right < size and array[right] > array[largest]: + largest = right + if largest != index: + array[index], array[largest] = array[largest], array[index] + _heapify(array, size, largest) + + +def _build_heap(array: list[int]) -> None: + for i in reversed(range(len(array) // 2)): + _heapify(array, len(array), i) + + +def heap_sort(array: list[int]) -> None: + """Sorts an array of integers using the heap sort algorithm. + + Time complexity: O(n*log(n)), where n is the length of the array. + Extra space complexity: O(1). + + Stable: No (the relative order of equal elements is not preserved). + In-place: Yes (the input array is modified). + + :param array: array of integers + :return: None + """ + _build_heap(array) + + for index in reversed(range(len(array))): + array[index], array[0] = array[0], array[index] + _heapify(array, index, 0) + + +def insert_sort(array: list[int]) -> None: + """Sorts an array of integers using the insertion sort algorithm. Time complexity: O(n^2), where n is the length of the array. Extra space complexity: O(1) @@ -40,11 +80,15 @@ def bubble_sort(array: list[int]) -> None: :param array: array of integers :return: None """ - length = len(array) - for i in range(length): - for j in range(length - i - 1): - if array[j] > array[j + 1]: - swap(array, j, j + 1) + for i in range(1, len(array)): + current = array[i] + j = i + + while j > 0 and array[j - 1] > current: + array[j] = array[j - 1] + j -= 1 + + array[j] = current def _merge(array_1: list[int], array_2: list[int]) -> list[int]: @@ -90,28 +134,6 @@ def merge_sort(array: list[int]) -> list[int]: return _merge(left_array_sorted, right_array_sorted) -def selection_sort(array: list[int]) -> None: - """Sorts an array of integers using the selection sort algorithm. - - Time complexity: O(n^2), where n is the length of the array. - Extra space complexity: O(1) - - Stable: No (the relative order of equal elements is not preserved). - In-place: Yes (the input array is modified). - - :param array: array of integers - :return: None - """ - length = len(array) - for i in range(length - 1): - minimum = i - for j in range(i + 1, length): - if array[j] < array[minimum]: - minimum = j - - swap(array, i, minimum) - - def _partition(array: list[int], start: int, end: int) -> int: pivot = array[end] pivot_index = start - 1 @@ -119,7 +141,7 @@ def _partition(array: list[int], start: int, end: int) -> int: for i in range(start, end): if array[i] <= pivot: pivot_index += 1 - array[pivot_index], array[i] = array[i], array[pivot_index] + swap(array, pivot_index, i) swap(array, pivot_index + 1, end) return pivot_index + 1 @@ -145,38 +167,11 @@ def quick_sort(array: list[int], left: int, right: int) -> None: quick_sort(array, middle + 1, right) -def _left_child(index: int) -> int: - return 2 * index + 1 - - -def _right_child(index: int) -> int: - return 2 * index + 2 - - -def _heapify(array: list[int], size: int, index: int) -> None: - largest = index - left = _left_child(index) - right = _right_child(index) - - if left < size and array[left] > array[largest]: - largest = left - if right < size and array[right] > array[largest]: - largest = right - if largest != index: - array[index], array[largest] = array[largest], array[index] - _heapify(array, size, largest) - - -def _build_heap(array: list[int]) -> None: - for i in reversed(range(len(array) // 2)): - _heapify(array, len(array), i) - - -def heap_sort(array: list[int]) -> None: - """Sorts an array of integers using the heap sort algorithm. +def selection_sort(array: list[int]) -> None: + """Sorts an array of integers using the selection sort algorithm. - Time complexity: O(n*log(n)), where n is the length of the array. - Extra space complexity: O(1). + Time complexity: O(n^2), where n is the length of the array. + Extra space complexity: O(1) Stable: No (the relative order of equal elements is not preserved). In-place: Yes (the input array is modified). @@ -184,11 +179,14 @@ def heap_sort(array: list[int]) -> None: :param array: array of integers :return: None """ - _build_heap(array) + length = len(array) + for i in range(length - 1): + minimum = i + for j in range(i + 1, length): + if array[j] < array[minimum]: + minimum = j - for index in reversed(range(len(array))): - array[index], array[0] = array[0], array[index] - _heapify(array, index, 0) + swap(array, i, minimum) def shell_sort(array: list[int]) -> None: @@ -218,77 +216,3 @@ def shell_sort(array: list[int]) -> None: array[j] = current gap //= 2 - - -def counting_sort(array: list[int], *, max_value: int) -> list[int]: - """Sorts an array of integers using the counting sort algorithm. - - Time complexity: O(n+k), where: - - n is the length of the array - - k is the maximum value in the array - - Extra space complexity: O(n+k), where: - - n is the length of the array - - k is the maximum value in the array - - Stable: Yes (the relative order of equal elements is preserved). - In-place: No (the input array is not modified). - - :param array: array of integers - :param max_value: maximum value in the array - :raises: ValueError: if the array contains negative integers - :return: sorted array of integers - """ - if any(num < 0 for num in array): - msg = "The array should contain only non-negative integers" - raise ValueError(msg) - - count_of_each_integer = list(repeat(0, max_value + 1)) - for num in array: - count_of_each_integer[num] += 1 - - for i in range(1, max_value + 1): - count_of_each_integer[i] += count_of_each_integer[i - 1] - - result = list(repeat(0, len(array))) - for num in reversed(array): - count_of_each_integer[num] -= 1 - result[count_of_each_integer[num]] = num - - return result - - -def radix_sort(array: list[int], *, max_digits: int, base: int = 10) -> list[int]: - """Sorts an array of integers using the radix sort algorithm. - - Time complexity: O(d*(n+k)), where: - - d is the number of digits in the largest number - - n is the length of the array - - k is the number of possible digits (the base) - - Extra space complexity: O(n+k), where: - - n is the length of the array - - k is the number of possible digits (the base) - - Stable: Yes (the relative order of equal elements is preserved). - In-place: No (the input array is not modified). - - :param array: array of integers - :param max_digits: number of digits in the largest number - :param base: base of the numbers, default is 10 - :raises: ValueError: if the array contains negative integers - :return: sorted array of integers - """ - if any(num < 0 for num in array): - msg = "The array should contain only non-negative integers" - raise ValueError(msg) - - for i in range(max_digits): - bins: list[list[int]] = [[] for _ in range(base)] - for num in array: - digit = (num // base**i) % base - bins[digit].append(num) - - array = list(chain.from_iterable(bins)) - - return array diff --git a/pysortlib/sorting_linear.py b/pysortlib/sorting_linear.py new file mode 100644 index 0000000..79994ea --- /dev/null +++ b/pysortlib/sorting_linear.py @@ -0,0 +1,75 @@ +from __future__ import annotations + +from itertools import chain, repeat + +from .utils import check_negative_integers + + +def counting_sort(array: list[int], *, max_value: int) -> list[int]: + """Sorts an array of integers using the counting sort algorithm. + + Time complexity: O(n+k), where: + - n is the length of the array + - k is the maximum value in the array + + Extra space complexity: O(n+k), where: + - n is the length of the array + - k is the maximum value in the array + + Stable: Yes (the relative order of equal elements is preserved). + In-place: No (the input array is not modified). + + :param array: array of integers + :param max_value: maximum value in the array + :raises: ValueError: if the array contains negative integers + :return: sorted array of integers + """ + check_negative_integers(array) + + count_of_each_integer = list(repeat(0, max_value + 1)) + for num in array: + count_of_each_integer[num] += 1 + + for i in range(1, max_value + 1): + count_of_each_integer[i] += count_of_each_integer[i - 1] + + result = list(repeat(0, len(array))) + for num in reversed(array): + count_of_each_integer[num] -= 1 + result[count_of_each_integer[num]] = num + + return result + + +def radix_sort(array: list[int], *, max_digits: int, base: int = 10) -> list[int]: + """Sorts an array of integers using the radix sort algorithm. + + Time complexity: O(d*(n+k)), where: + - d is the number of digits in the largest number + - n is the length of the array + - k is the number of possible digits (the base) + + Extra space complexity: O(n+k), where: + - n is the length of the array + - k is the number of possible digits (the base) + + Stable: Yes (the relative order of equal elements is preserved). + In-place: No (the input array is not modified). + + :param array: array of integers + :param max_digits: number of digits in the largest number + :param base: base of the numbers, default is 10 + :raises: ValueError: if the array contains negative integers + :return: sorted array of integers + """ + check_negative_integers(array) + + for i in range(max_digits): + bins: list[list[int]] = [[] for _ in range(base)] + for num in array: + digit = (num // base**i) % base + bins[digit].append(num) + + array = list(chain.from_iterable(bins)) + + return array diff --git a/pysortlib/utils.py b/pysortlib/utils.py index 3458475..139304c 100644 --- a/pysortlib/utils.py +++ b/pysortlib/utils.py @@ -4,3 +4,10 @@ def swap(array: list[int], index_1: int, index_2: int) -> None: """Swap two elements in the array.""" array[index_1], array[index_2] = array[index_2], array[index_1] + + +def check_negative_integers(array: list[int]) -> None: + """Check if the array contains only non-negative integers.""" + if any(num < 0 for num in array): + msg = "The array should contain only non-negative integers" + raise ValueError(msg) diff --git a/tests/test_sorting.py b/tests/test_sorting.py index 86d93cc..6b6bf80 100644 --- a/tests/test_sorting.py +++ b/tests/test_sorting.py @@ -1,33 +1,36 @@ from __future__ import annotations +from typing import Callable + import pytest from hypothesis import given from hypothesis.strategies import integers, lists from pysortlib.sorting import ( bubble_sort, - counting_sort, heap_sort, insert_sort, merge_sort, quick_sort, - radix_sort, selection_sort, shell_sort, ) +@pytest.mark.parametrize( + "func", + [ + bubble_sort, + heap_sort, + insert_sort, + selection_sort, + shell_sort, + ], +) @given(lists(integers())) -def test_insert_sort(array: list[int]) -> None: - sorted_copy = sorted(array) - insert_sort(array) - assert array == sorted_copy - - -@given(lists(integers())) -def test_bubble_sort(array: list[int]) -> None: +def test_sorting_common(func: Callable[[list[int]], None], array: list[int]) -> None: sorted_copy = sorted(array) - bubble_sort(array) + func(array) assert array == sorted_copy @@ -37,55 +40,8 @@ def test_merge_sort(array: list[int]) -> None: assert result == sorted(array) -@given(lists(integers())) -def test_selection_sort(array: list[int]) -> None: - sorted_copy = sorted(array) - selection_sort(array) - assert array == sorted_copy - - @given(lists(integers())) def test_quick_sort(array: list[int]) -> None: sorted_copy = sorted(array) quick_sort(array, 0, len(array) - 1) assert array == sorted_copy - - -@given(lists(integers())) -def test_heap_sort(array: list[int]) -> None: - sorted_copy = sorted(array) - heap_sort(array) - assert array == sorted_copy - - -@given(lists(integers())) -def test_shell_sort(array: list[int]) -> None: - sorted_copy = sorted(array) - shell_sort(array) - assert array == sorted_copy - - -@given(lists(integers(min_value=0, max_value=10))) -def test_counting_sort(array: list[int]) -> None: - result = counting_sort(array, max_value=10) - assert result == sorted(array) - - -@pytest.mark.parametrize("array", [[-1, 0], [0, -1]]) -def test_counting_sort_with_negative_integers(array: list[int]) -> None: - with pytest.raises(ValueError) as exc: - counting_sort(array, max_value=1) - assert str(exc.value) == "The array should contain only non-negative integers" - - -@given(lists(integers(min_value=0, max_value=999))) -def test_radix_sort(array: list[int]) -> None: - result = radix_sort(array, max_digits=3) # 999 -> max_digits = 3 - assert result == sorted(array) - - -@pytest.mark.parametrize("array", [[-1, 0], [0, -1]]) -def test_radix_sort_with_negative_integers(array: list[int]) -> None: - with pytest.raises(ValueError) as exc: - radix_sort(array, max_digits=1) - assert str(exc.value) == "The array should contain only non-negative integers" diff --git a/tests/test_sorting_linear.py b/tests/test_sorting_linear.py new file mode 100644 index 0000000..d5e195d --- /dev/null +++ b/tests/test_sorting_linear.py @@ -0,0 +1,33 @@ +from __future__ import annotations + +import pytest +from hypothesis import given +from hypothesis.strategies import integers, lists + +from pysortlib.sorting_linear import counting_sort, radix_sort + + +@given(lists(integers(min_value=0, max_value=10))) +def test_counting_sort(array: list[int]) -> None: + result = counting_sort(array, max_value=10) + assert result == sorted(array) + + +@pytest.mark.parametrize("array", [[-1, 0], [0, -1]]) +def test_counting_sort_with_negative_integers(array: list[int]) -> None: + with pytest.raises(ValueError) as exc: + counting_sort(array, max_value=1) + assert str(exc.value) == "The array should contain only non-negative integers" + + +@given(lists(integers(min_value=0, max_value=999))) +def test_radix_sort(array: list[int]) -> None: + result = radix_sort(array, max_digits=3) + assert result == sorted(array) + + +@pytest.mark.parametrize("array", [[-1, 0], [0, -1]]) +def test_radix_sort_with_negative_integers(array: list[int]) -> None: + with pytest.raises(ValueError) as exc: + radix_sort(array, max_digits=1) + assert str(exc.value) == "The array should contain only non-negative integers"