1- PLACEHOLDER
1+ """
2+ Tests for the general-purpose comparison sorts in ``sorts/``.
3+
4+ Every algorithm exercised here implements the same contract: given a list of
5+ mutually comparable items it returns a new list with the same items in
6+ non-decreasing order (i.e. it agrees with the built-in ``sorted``). Rather than
7+ repeat a hand-written test per file we run each sort against a shared battery of
8+ inputs with :func:`pytest.mark.parametrize`.
9+
10+ Specialised sorts that only accept a restricted domain are intentionally left
11+ out (e.g. ``counting_sort``/``radix_sort``/``pigeon_sort`` are integer-only,
12+ ``bead_sort`` needs non-negative integers, ``dutch_national_flag_sort`` expects
13+ 0/1/2, ``bitonic_sort`` needs a power-of-two length, ``topological_sort`` works
14+ on a graph, and ``stalin_sort``/``wiggle_sort`` deliberately do not fully sort).
15+ ``rec_insertion_sort`` is also left out of the battery: it sorts in place and
16+ returns ``None`` rather than the sorted collection, so it is exercised
17+ separately below.
18+ """
19+
20+ from dataclasses import dataclass
21+ from typing import NamedTuple
22+
23+ import pytest
24+
25+ from sorts .binary_insertion_sort import binary_insertion_sort
26+ from sorts .bogo_sort import bogo_sort
27+ from sorts .bubble_sort import bubble_sort_iterative , bubble_sort_recursive
28+ from sorts .circle_sort import circle_sort
29+ from sorts .cocktail_shaker_sort import cocktail_shaker_sort
30+ from sorts .comb_sort import comb_sort
31+ from sorts .cycle_sort import cycle_sort
32+ from sorts .double_sort import double_sort
33+ from sorts .exchange_sort import exchange_sort
34+ from sorts .gnome_sort import gnome_sort
35+ from sorts .heap_sort import heap_sort
36+ from sorts .insertion_sort import insertion_sort
37+ from sorts .intro_sort import heap_sort as intro_heap_sort
38+ from sorts .intro_sort import intro_sort as intro_sort_range
39+ from sorts .intro_sort import sort as intro_sort
40+ from sorts .iterative_merge_sort import iter_merge_sort
41+ from sorts .merge_insertion_sort import merge_insertion_sort
42+ from sorts .merge_sort import merge_sort
43+ from sorts .odd_even_sort import odd_even_sort
44+ from sorts .odd_even_transposition_single_threaded import odd_even_transposition
45+ from sorts .pancake_sort import pancake_sort
46+ from sorts .patience_sort import patience_sort
47+ from sorts .quick_sort import quick_sort
48+ from sorts .quick_sort_3_partition import three_way_radix_quicksort
49+ from sorts .recursive_insertion_sort import rec_insertion_sort
50+ from sorts .recursive_mergesort_array import merge
51+ from sorts .recursive_quick_sort import quick_sort as recursive_quick_sort
52+ from sorts .reverse_selection import reverse_selection_sort
53+ from sorts .reversort import reversort
54+ from sorts .selection_sort import selection_sort
55+ from sorts .shell_sort import shell_sort
56+ from sorts .shrink_shell_sort import shell_sort as shrink_shell_sort
57+ from sorts .smoothsort import smoothsort
58+ from sorts .stooge_sort import stooge_sort
59+ from sorts .strand_sort import strand_sort
60+ from sorts .tim_sort import tim_sort
61+ from sorts .unknown_sort import merge_sort as unknown_sort
62+
63+
64+ def test_heap_sort () -> None :
65+ assert heap_sort ([]) == []
66+ assert heap_sort ([1 ]) == [1 ]
67+ assert heap_sort ([5 , 2 , 5 , 1 ]) == [1 , 2 , 5 , 5 ]
68+ assert heap_sort ([1 , 2 , 3 , 4 ]) == [1 , 2 , 3 , 4 ]
69+ assert heap_sort ([5 , 4 , 3 , 2 , 1 ]) == [1 , 2 , 3 , 4 , 5 ]
70+
71+
72+ @pytest .mark .parametrize (
73+ ("start" , "end" ),
74+ [(start , end ) for start in range (7 ) for end in [None , * range (start , 7 )]],
75+ )
76+ def test_intro_heap_sort_range (start : int , end : int | None ) -> None :
77+ collection = [100 , 4 , 1 , 3 , 1 , - 100 ]
78+ expected = collection [:start ] + sorted (collection [start :end ])
79+ if end is not None :
80+ expected += collection [end :]
81+
82+ result = intro_heap_sort (collection , start , end )
83+
84+ assert result is collection
85+ assert collection == expected
86+
87+
88+ @pytest .mark .parametrize ("max_depth" , [0 , 1 ])
89+ def test_intro_sort_heap_fallback_preserves_surrounding_items (max_depth : int ) -> None :
90+ collection = [100 , * range (40 , 0 , - 1 ), - 100 ]
91+ expected = [100 , * range (1 , 41 ), - 100 ]
92+
93+ result = intro_sort_range (collection , 1 , 41 , 16 , max_depth )
94+
95+ assert result is collection
96+ assert collection == expected
97+
98+
99+ SORTS = (
100+ binary_insertion_sort ,
101+ bubble_sort_iterative ,
102+ circle_sort ,
103+ cocktail_shaker_sort ,
104+ comb_sort ,
105+ cycle_sort ,
106+ double_sort ,
107+ exchange_sort ,
108+ gnome_sort ,
109+ heap_sort ,
110+ insertion_sort ,
111+ intro_sort ,
112+ iter_merge_sort ,
113+ merge ,
114+ merge_insertion_sort ,
115+ merge_sort ,
116+ odd_even_sort ,
117+ odd_even_transposition ,
118+ pancake_sort ,
119+ patience_sort ,
120+ quick_sort ,
121+ recursive_quick_sort ,
122+ reverse_selection_sort ,
123+ reversort ,
124+ selection_sort ,
125+ shell_sort ,
126+ shrink_shell_sort ,
127+ smoothsort ,
128+ stooge_sort ,
129+ strand_sort ,
130+ three_way_radix_quicksort ,
131+ tim_sort ,
132+ unknown_sort ,
133+ )
134+
135+
136+ @dataclass (order = True )
137+ class Person :
138+ name : str = "Bob"
139+ age : int = 37
140+ cost : float = 0.0
141+
142+
143+ class Dog (NamedTuple ):
144+ name : str = "Fido"
145+ age : int = 5
146+ weight : float = 15.5
147+
148+
149+ CASES = (
150+ [],
151+ [1 ],
152+ [10 , - 10 , - 1 , 1 , 0 ],
153+ [1.1 , - 1.1 , - 1 , 1 , 0 ],
154+ list ("Python!" ),
155+ [3 , 3 , 1 , 2 , 2 , 1 ],
156+ [5 , 4 , 3 , 2 , 1 ],
157+ [1 , 2 , 3 , 4 , 5 ],
158+ [- 2 , - 2 , 0 , 0 , 7 , 7 ],
159+ [Person (cost = 100.0 ), Person (cost = - 100.0 ), Person (name = "Al" )],
160+ [Dog (weight = 15.5 ), Dog (weight = 15.1 ), Dog (name = "Buddy" )],
161+ )
162+
163+
164+ @pytest .mark .parametrize ("sort" , SORTS , ids = lambda f : f .__name__ )
165+ @pytest .mark .parametrize ("case" , CASES , ids = repr )
166+ def test_sort_matches_builtin (sort , case ) -> None :
167+ """Each sort must reproduce the ordering of the built-in ``sorted``."""
168+ assert list (sort (list (case ))) == sorted (case )
169+
170+
171+ @pytest .mark .parametrize ("case" , CASES , ids = repr )
172+ def test_rec_insertion_sort (case ) -> None :
173+ """``rec_insertion_sort`` sorts in place and returns ``None``."""
174+ collection = list (case )
175+ assert rec_insertion_sort (collection , len (collection )) is None
176+ assert collection == sorted (case )
177+
178+
179+ @pytest .mark .parametrize (
180+ "sort" ,
181+ [
182+ binary_insertion_sort ,
183+ bubble_sort_iterative ,
184+ bubble_sort_recursive ,
185+ circle_sort ,
186+ cocktail_shaker_sort ,
187+ comb_sort ,
188+ cycle_sort ,
189+ exchange_sort ,
190+ gnome_sort ,
191+ insertion_sort ,
192+ intro_sort ,
193+ iter_merge_sort ,
194+ merge ,
195+ merge_insertion_sort ,
196+ merge_sort ,
197+ odd_even_sort ,
198+ odd_even_transposition ,
199+ pancake_sort ,
200+ patience_sort ,
201+ recursive_quick_sort ,
202+ reverse_selection_sort ,
203+ reversort ,
204+ selection_sort ,
205+ shrink_shell_sort ,
206+ strand_sort ,
207+ three_way_radix_quicksort ,
208+ tim_sort ,
209+ unknown_sort ,
210+ ],
211+ ids = lambda f : f .__name__ ,
212+ )
213+ def test_sort_rejects_non_comparable_items (sort ) -> None :
214+ with pytest .raises (TypeError ):
215+ sort ([1 , "a" ])
216+
217+
218+ def test_rec_insertion_sort_rejects_non_comparable_items () -> None :
219+ with pytest .raises (TypeError ):
220+ rec_insertion_sort ([1 , "a" ], 2 )
221+
222+
223+ def test_bogo_sort_comparable_items () -> None :
224+ assert bogo_sort (["c" , "a" , "b" ]) == ["a" , "b" , "c" ]
225+ assert bogo_sort ([2.5 , - 1.0 , 0.0 ]) == [- 1.0 , 0.0 , 2.5 ]
226+
227+ with pytest .raises (TypeError ):
228+ bogo_sort ([1 , "a" ])
229+
230+
231+ def test_bitonic_sort_comparable_items () -> None :
232+ from sorts .bitonic_sort import bitonic_sort
233+
234+ strings = ["banana" , "apple" , "cherry" , "date" ]
235+ bitonic_sort (strings , 0 , len (strings ), 1 )
236+ assert strings == ["apple" , "banana" , "cherry" , "date" ]
237+
238+ numbers = [3 , 1.5 , 2 , 4.5 ]
239+ bitonic_sort (numbers , 0 , len (numbers ), 1 )
240+ assert numbers == [1.5 , 2 , 3 , 4.5 ]
241+
242+ with pytest .raises (TypeError ):
243+ bitonic_sort ([1 , "two" , 3 , "four" ], 0 , 4 , 1 )
0 commit comments