@@ -90,51 +90,96 @@ def sorted(__iterable, key=None, reverse=False):
9090 """
9191 item_type = type (__iterable )
9292 _dict , _list , _tuple , _str = type ({}), type ([]), type (()), type ("" )
93-
94- def key_to_cmp ( key ) :
95- """ Convert a key function to a cmp function.
93+
94+ class _ReverseCompare :
95+ """Wrapper class to reverse comparison for a single sort key component.
9696
97- Info: https://docs.python.org/3/howto/sorting.html#the-old-way-using-the-cmp-parameter
97+ This allows us to reverse only the primary sort key while keeping
98+ the secondary index-based key in normal order for stability.
9899 """
99- def cmp (a , b ):
100- """ The `cmp` function does not exist on Python 3.x.
101-
102- Source: https://stackoverflow.com/a/22490617/8965861
103- """
104- return (a > b ) - (a < b )
105-
106- # def cmp_func(x, y):
107- # if key(x) < key(y):
108- # return -1
109- # elif key(x) > key(y):
110- # return 1
111- # else:
112- # return 0
113- # def cmp_func(x, y):
114- # return cmp(key(x), key(y))
115- # return cmp_func
116- return lambda x , y : cmp (key (x ), key (y ))
100+ def __init__ (self , obj ):
101+ self .obj = obj
102+
103+ def __lt__ (self , other ):
104+ return self .obj > other .obj
105+
106+ def __gt__ (self , other ):
107+ return self .obj < other .obj
108+
109+ def __eq__ (self , other ):
110+ return self .obj == other .obj
111+
112+ def __le__ (self , other ):
113+ return self .obj >= other .obj
114+
115+ def __ge__ (self , other ):
116+ return self .obj <= other .obj
117+
118+ def __ne__ (self , other ):
119+ return self .obj != other .obj
117120
118121 # ---> List
119122 #
120123 # When passed a list, the original `sorted` function
121124 # sorts its elements as expected.
122125 if item_type == _list :
123- elements = [elem for elem in __iterable ] # Make a copy of the original iterable
126+ # Make a copy and add original indices for stable sorting
127+ elements_with_index = [(i , elem ) for i , elem in enumerate (__iterable )]
124128
125129 if key is None :
126130 # If no key function is passed, sort the elements as they are
127- elements .sort ()
131+ # Add index as secondary sort key to ensure stability
132+ def stable_key (x ):
133+ if reverse :
134+ return (_ReverseCompare (x [1 ]), x [0 ])
135+ return (x [1 ], x [0 ])
136+
137+ # For very old Python versions without key support
138+ def stable_cmp (a , b ):
139+ result = (a [1 ] > b [1 ]) - (a [1 ] < b [1 ])
140+
141+ if reverse :
142+ result = - result
143+
144+ if result == 0 :
145+ # If keys are equal, compare indices to maintain stability (never reverse index order)
146+ result = (a [0 ] > b [0 ]) - (a [0 ] < b [0 ])
147+
148+ return result
149+
150+ try :
151+ elements_with_index .sort (key = stable_key )
152+ except TypeError :
153+ elements_with_index .sort (stable_cmp )
128154 else :
129155 # The `key` argument was introduced starting from Python 2.4
156+ # Wrap the key function to include the index for stability
157+ def stable_key_wrapper (x ):
158+ if reverse :
159+ return (_ReverseCompare (key (x [1 ])), x [0 ])
160+ return (key (x [1 ]), x [0 ])
161+
162+ # Convert the key function to a `cmp` function for older Python versions (<3.0)
163+ # Info: https://docs.python.org/3/howto/sorting.html#the-old-way-using-the-cmp-parameter
164+ #
165+ # Include index comparison for stability
166+ def stable_key_to_cmp (a , b ):
167+ ka , kb = key (a [1 ]), key (b [1 ])
168+ result = (ka > kb ) - (ka < kb )
169+ if reverse :
170+ result = - result
171+ if result == 0 :
172+ # If keys are equal, compare indices to maintain stability (never reverse index order)
173+ result = (a [0 ] > b [0 ]) - (a [0 ] < b [0 ])
174+ return result
175+
130176 try :
131- elements .sort (key = key )
177+ elements_with_index .sort (key = stable_key_wrapper )
132178 except TypeError :
133- # Convert the key function to a cmp function, since the `key` argument is not available.
134- elements .sort (key_to_cmp (key ))
179+ elements_with_index .sort (stable_key_to_cmp )
135180
136- if reverse :
137- elements . reverse ()
181+ # Extract just the elements (without indices)
182+ elements = [ elem for _ , elem in elements_with_index ]
138183
139184 value = []
140185 for element in elements :
@@ -289,6 +334,53 @@ def test_arg_reverse(self):
289334 [_TestClass (2 ), _TestClass (1 ), _TestClass (0 )],
290335 )
291336
337+ def test_stability (self ):
338+ """Test sorting a list multiple times to ensure stability.
339+
340+ Demonstrates the idiom of sorting by secondary key first, then primary key.
341+ This leverages sort stability to achieve multi-key sorting.
342+ """
343+ # List of (name, section)
344+ data = [
345+ ("Dave" , "A" ),
346+ ("Alice" , "B" ),
347+ ("Ken" , "A" ),
348+ ("Eric" , "B" ),
349+ ("Carol" , "A" ),
350+ ]
351+
352+ # First, sort by name (secondary key)
353+ data_sorted_by_name = sorted (data , key = lambda x : x [0 ])
354+
355+ self .assertEqual (
356+ data_sorted_by_name ,
357+ [
358+ ("Alice" , "B" ),
359+ ("Carol" , "A" ),
360+ ("Dave" , "A" ),
361+ ("Eric" , "B" ),
362+ ("Ken" , "A" ),
363+ ],
364+ "First sort by name should produce alphabetical order"
365+ )
366+
367+ # Then, sort by section (primary key)
368+ # Since sort is stable, within each section the name order is preserved
369+ data_sorted_by_section = sorted (data_sorted_by_name , key = lambda x : x [1 ])
370+
371+ self .assertEqual (
372+ data_sorted_by_section ,
373+ [
374+ ("Carol" , "A" ),
375+ ("Dave" , "A" ),
376+ ("Ken" , "A" ),
377+ ("Alice" , "B" ),
378+ ("Eric" , "B" ),
379+ ],
380+ "Second sort by section should group by section while preserving name order within each section"
381+ )
382+
383+
292384if __name__ == "__main__" :
293385 _globals = globals ()
294386
@@ -308,7 +400,7 @@ def test_arg_reverse(self):
308400 for test_case in test_cases
309401 for test_method in dir (test_case )
310402 if test_method .startswith ("test_" )
311- # and "showall" in test_method
403+ # and test_case.__name__.lower().find("sorted") != -1
312404 ]
313405
314406 suite = _unittest .TestSuite (tests )
0 commit comments