From bf644b2bb25bba813027aabfa7cf4cfa9abb9e60 Mon Sep 17 00:00:00 2001 From: Rokeshwaran Date: Tue, 15 Sep 2026 12:33:55 +0530 Subject: [PATCH 1/3] sorts: make recursive_insertion_sort generic over Comparable items Part of #15234 - switch rec_insertion_sort/insert_next to the Comparable/TypeVar-bound MutableSequence[T] pattern (in-place sorts bucket) per the convention discussed on #15234 - make rec_insertion_sort return the sorted collection and give n a default of len(collection), so it can be called with a single argument like the other sorts in the shared test battery - add a string doctest - register rec_insertion_sort in tests/test_sorts.py's shared SORTS battery and the non-comparable-items rejection test --- sorts/recursive_insertion_sort.py | 44 +++++++++++++++++++++++-------- tests/test_sorts.py | 3 +++ 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/sorts/recursive_insertion_sort.py b/sorts/recursive_insertion_sort.py index b1df234ebef4..a6255b53f439 100644 --- a/sorts/recursive_insertion_sort.py +++ b/sorts/recursive_insertion_sort.py @@ -4,39 +4,61 @@ from __future__ import annotations +from collections.abc import MutableSequence +from typing import Any, Protocol, TypeVar -def rec_insertion_sort(collection: list, n: int) -> None: + +class Comparable(Protocol): + def __lt__(self, other: Any, /) -> bool: ... + + +T = TypeVar("T", bound=Comparable) + + +def rec_insertion_sort[T: Comparable]( + collection: MutableSequence[T], n: int | None = None +) -> MutableSequence[T]: """ - Given a collection of numbers and its length, sorts the collections - in ascending order + Given a collection of comparable elements, sorts the collection in place + in ascending order and returns it. :param collection: A mutable collection of comparable elements - :param n: The length of collections + :param n: The number of leading elements still to be placed. Defaults to + the full length of ``collection`` so the function can be called with + a single argument. + :return: the same collection ordered by ascending >>> col = [1, 2, 1] - >>> rec_insertion_sort(col, len(col)) + >>> rec_insertion_sort(col) + [1, 1, 2] >>> col [1, 1, 2] >>> col = [2, 1, 0, -1, -2] >>> rec_insertion_sort(col, len(col)) - >>> col [-2, -1, 0, 1, 2] >>> col = [1] - >>> rec_insertion_sort(col, len(col)) - >>> col + >>> rec_insertion_sort(col) [1] + + >>> col = ['d', 'a', 'b', 'e', 'c'] + >>> rec_insertion_sort(col) == sorted(col) + True """ + if n is None: + n = len(collection) + # Checks if the entire collection has been sorted if len(collection) <= 1 or n <= 1: - return + return collection insert_next(collection, n - 1) rec_insertion_sort(collection, n - 1) + return collection -def insert_next(collection: list, index: int) -> None: +def insert_next[T: Comparable](collection: MutableSequence[T], index: int) -> None: """ Inserts the '(index-1)th' element into place @@ -71,5 +93,5 @@ def insert_next(collection: list, index: int) -> None: if __name__ == "__main__": numbers = input("Enter integers separated by spaces: ") number_list: list[int] = [int(num) for num in numbers.split()] - rec_insertion_sort(number_list, len(number_list)) + rec_insertion_sort(number_list) print(number_list) diff --git a/tests/test_sorts.py b/tests/test_sorts.py index 2c9b79aa4bfe..878c135a54d1 100644 --- a/tests/test_sorts.py +++ b/tests/test_sorts.py @@ -35,6 +35,7 @@ from sorts.odd_even_sort import odd_even_sort from sorts.patience_sort import patience_sort from sorts.quick_sort import quick_sort +from sorts.recursive_insertion_sort import rec_insertion_sort from sorts.selection_sort import selection_sort from sorts.shell_sort import shell_sort from sorts.stooge_sort import stooge_sort @@ -66,6 +67,7 @@ def test_heap_sort() -> None: odd_even_sort, patience_sort, quick_sort, + rec_insertion_sort, selection_sort, shell_sort, stooge_sort, @@ -121,6 +123,7 @@ def test_sort_matches_builtin(sort, case) -> None: gnome_sort, insertion_sort, merge_sort, + rec_insertion_sort, selection_sort, ], ids=lambda f: f.__name__, From a07324cba85faf391a88b0723e585300f0570555 Mon Sep 17 00:00:00 2001 From: Rokeshwaran Date: Wed, 16 Sep 2026 06:58:41 +0530 Subject: [PATCH 2/3] fix: keep rec_insertion_sort in-place with None return, per maintainer review - Revert n to a required parameter and drop the MutableSequence[T] return value; rec_insertion_sort stays a pure in-place sort returning None, as requested in review. - Keep the Comparable/TypeVar generalization (no PEP 695 syntax, to match the existing TypeVar style in the file). - Keep the non-int (string) doctest. - tests/test_sorts.py: rec_insertion_sort no longer fits the shared SORTS battery (which asserts on a returned value), so it's removed from that tuple and given its own parametrized in-place test, plus its own non-comparable-items rejection test. --- sorts/recursive_insertion_sort.py | 35 +++++++++++++------------------ 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/sorts/recursive_insertion_sort.py b/sorts/recursive_insertion_sort.py index a6255b53f439..7076444921c0 100644 --- a/sorts/recursive_insertion_sort.py +++ b/sorts/recursive_insertion_sort.py @@ -15,50 +15,43 @@ def __lt__(self, other: Any, /) -> bool: ... T = TypeVar("T", bound=Comparable) -def rec_insertion_sort[T: Comparable]( - collection: MutableSequence[T], n: int | None = None -) -> MutableSequence[T]: +def rec_insertion_sort(collection: MutableSequence[T], n: int) -> None: """ - Given a collection of comparable elements, sorts the collection in place - in ascending order and returns it. + Given a collection of comparable elements and its length, sorts the + collection in place in ascending order. :param collection: A mutable collection of comparable elements - :param n: The number of leading elements still to be placed. Defaults to - the full length of ``collection`` so the function can be called with - a single argument. - :return: the same collection ordered by ascending + :param n: The length of collection >>> col = [1, 2, 1] - >>> rec_insertion_sort(col) - [1, 1, 2] + >>> rec_insertion_sort(col, len(col)) >>> col [1, 1, 2] >>> col = [2, 1, 0, -1, -2] >>> rec_insertion_sort(col, len(col)) + >>> col [-2, -1, 0, 1, 2] >>> col = [1] - >>> rec_insertion_sort(col) + >>> rec_insertion_sort(col, len(col)) + >>> col [1] >>> col = ['d', 'a', 'b', 'e', 'c'] - >>> rec_insertion_sort(col) == sorted(col) - True + >>> rec_insertion_sort(col, len(col)) + >>> col + ['a', 'b', 'c', 'd', 'e'] """ - if n is None: - n = len(collection) - # Checks if the entire collection has been sorted if len(collection) <= 1 or n <= 1: - return collection + return insert_next(collection, n - 1) rec_insertion_sort(collection, n - 1) - return collection -def insert_next[T: Comparable](collection: MutableSequence[T], index: int) -> None: +def insert_next(collection: MutableSequence[T], index: int) -> None: """ Inserts the '(index-1)th' element into place @@ -93,5 +86,5 @@ def insert_next[T: Comparable](collection: MutableSequence[T], index: int) -> No if __name__ == "__main__": numbers = input("Enter integers separated by spaces: ") number_list: list[int] = [int(num) for num in numbers.split()] - rec_insertion_sort(number_list) + rec_insertion_sort(number_list, len(number_list)) print(number_list) From c32e7f3fafbc87b6a4699126e6b924da8de94826 Mon Sep 17 00:00:00 2001 From: Rokeshwaran Date: Wed, 16 Sep 2026 07:00:23 +0530 Subject: [PATCH 3/3] test: adjust test_sorts.py for rec_insertion_sort's None-returning in-place contract rec_insertion_sort no longer fits the shared SORTS battery (which asserts on a returned value), so it's removed from that tuple and given its own parametrized in-place test (checked against sorted()) plus its own non-comparable-items rejection test. --- tests/test_sorts.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/tests/test_sorts.py b/tests/test_sorts.py index 878c135a54d1..b7328d500d76 100644 --- a/tests/test_sorts.py +++ b/tests/test_sorts.py @@ -12,6 +12,9 @@ ``bead_sort`` needs non-negative integers, ``dutch_national_flag_sort`` expects 0/1/2, ``bitonic_sort`` needs a power-of-two length, ``topological_sort`` works on a graph, and ``stalin_sort``/``wiggle_sort`` deliberately do not fully sort). +``rec_insertion_sort`` is also left out of the battery: it sorts in place and +returns ``None`` rather than the sorted collection, so it is exercised +separately below. """ from dataclasses import dataclass @@ -67,7 +70,6 @@ def test_heap_sort() -> None: odd_even_sort, patience_sort, quick_sort, - rec_insertion_sort, selection_sort, shell_sort, stooge_sort, @@ -110,6 +112,14 @@ def test_sort_matches_builtin(sort, case) -> None: assert list(sort(list(case))) == sorted(case) +@pytest.mark.parametrize("case", CASES, ids=repr) +def test_rec_insertion_sort(case) -> None: + """``rec_insertion_sort`` sorts in place and returns ``None``.""" + collection = list(case) + assert rec_insertion_sort(collection, len(collection)) is None + assert collection == sorted(case) + + @pytest.mark.parametrize( "sort", [ @@ -123,7 +133,6 @@ def test_sort_matches_builtin(sort, case) -> None: gnome_sort, insertion_sort, merge_sort, - rec_insertion_sort, selection_sort, ], ids=lambda f: f.__name__, @@ -131,3 +140,8 @@ def test_sort_matches_builtin(sort, case) -> None: def test_sort_rejects_non_comparable_items(sort) -> None: with pytest.raises(TypeError): sort([1, "a"]) + + +def test_rec_insertion_sort_rejects_non_comparable_items() -> None: + with pytest.raises(TypeError): + rec_insertion_sort([1, "a"], 2)