@@ -35,19 +35,19 @@ def _find_run(
3535) -> int :
3636 """
3737 Detect a run (ascending or descending sequence) starting at 'start'.
38-
38+
3939 If the run is descending, reverse it in-place to make it ascending.
4040 Returns the end index (exclusive) of the detected run.
41-
41+
4242 Args:
4343 arr: The list to search in
4444 start: Starting index of the run
4545 end: End index (exclusive) of the search range
4646 key: Optional key function for comparisons
47-
47+
4848 Returns:
4949 End index (exclusive) of the detected run
50-
50+
5151 >>> arr = [3, 2, 1, 4, 5, 6]
5252 >>> _find_run(arr, 0, 6)
5353 3
@@ -61,10 +61,10 @@ def _find_run(
6161 """
6262 if start >= end - 1 :
6363 return start + 1
64-
64+
6565 key_func = key if key else lambda x : x
6666 run_end = start + 1
67-
67+
6868 # Check if run is ascending or descending
6969 if key_func (arr [run_end ]) < key_func (arr [start ]):
7070 # Descending run
@@ -76,29 +76,29 @@ def _find_run(
7676 # Ascending run
7777 while run_end < end and key_func (arr [run_end ]) >= key_func (arr [run_end - 1 ]):
7878 run_end += 1
79-
79+
8080 return run_end
8181
8282
8383def _node_power (n : int , b1 : int , n1 : int , b2 : int , n2 : int ) -> int :
8484 """
8585 Calculate the node power for two adjacent runs.
86-
86+
8787 This determines the merge priority in the stack. The power is the smallest
8888 integer p such that floor(a * 2^p) != floor(b * 2^p), where:
8989 - a = (b1 + n1/2) / n
9090 - b = (b2 + n2/2) / n
91-
91+
9292 Args:
9393 n: Total length of the array
9494 b1: Start index of first run
9595 n1: Length of first run
9696 b2: Start index of second run
9797 n2: Length of second run
98-
98+
9999 Returns:
100100 The calculated node power
101-
101+
102102 >>> _node_power(100, 0, 25, 25, 25)
103103 2
104104 >>> _node_power(100, 0, 50, 50, 50)
@@ -108,16 +108,16 @@ def _node_power(n: int, b1: int, n1: int, b2: int, n2: int) -> int:
108108 # To avoid floating point, we work with a = (2*b1 + n1) / (2*n) and b = (2*b2 + n2) / (2*n)
109109 # We want smallest p where floor(a * 2^p) != floor(b * 2^p)
110110 # This is floor((2*b1 + n1) * 2^p / (2*n)) != floor((2*b2 + n2) * 2^p / (2*n))
111-
111+
112112 a = 2 * b1 + n1
113113 b = 2 * b2 + n2
114114 two_n = 2 * n
115-
115+
116116 # Find smallest power p where floor(a * 2^p / two_n) != floor(b * 2^p / two_n)
117117 power = 0
118118 while (a * (1 << power )) // two_n == (b * (1 << power )) // two_n :
119119 power += 1
120-
120+
121121 return power
122122
123123
@@ -130,16 +130,16 @@ def _merge(
130130) -> None :
131131 """
132132 Merge two adjacent sorted runs in-place using auxiliary space.
133-
133+
134134 Merges arr[start1:end1] with arr[end1:end2].
135-
135+
136136 Args:
137137 arr: The list containing the runs
138138 start1: Start index of first run
139139 end1: End index of first run (start of second run)
140140 end2: End index of second run
141141 key: Optional key function for comparisons
142-
142+
143143 >>> arr = [1, 3, 5, 2, 4, 6]
144144 >>> _merge(arr, 0, 3, 6)
145145 >>> arr
@@ -150,14 +150,14 @@ def _merge(
150150 [1, 2, 3, 5, 6, 7]
151151 """
152152 key_func = key if key else lambda x : x
153-
153+
154154 # Copy the runs to temporary storage
155155 left = arr [start1 :end1 ]
156156 right = arr [end1 :end2 ]
157-
157+
158158 i = j = 0
159159 k = start1
160-
160+
161161 # Merge the two runs
162162 while i < len (left ) and j < len (right ):
163163 if key_func (left [i ]) <= key_func (right [j ]):
@@ -167,13 +167,13 @@ def _merge(
167167 arr [k ] = right [j ]
168168 j += 1
169169 k += 1
170-
170+
171171 # Copy remaining elements
172172 while i < len (left ):
173173 arr [k ] = left [i ]
174174 i += 1
175175 k += 1
176-
176+
177177 while j < len (right ):
178178 arr [k ] = right [j ]
179179 j += 1
@@ -188,21 +188,21 @@ def power_sort(
188188) -> list :
189189 """
190190 Sort a list using the PowerSort algorithm.
191-
191+
192192 PowerSort is an adaptive merge sort that detects existing runs in the data
193193 and uses a power-based merging strategy for optimal performance.
194-
194+
195195 Args:
196196 collection: A mutable ordered collection with comparable items
197197 key: Optional function to extract comparison key from each element
198198 reverse: If True, sort in descending order
199-
199+
200200 Returns:
201201 The same collection ordered according to the parameters
202-
202+
203203 Time Complexity: O(n log n) worst case, O(n) for nearly sorted data
204204 Space Complexity: O(n)
205-
205+
206206 Examples:
207207 >>> power_sort([0, 5, 3, 2, 2])
208208 [0, 2, 2, 3, 5]
@@ -237,59 +237,66 @@ def power_sort(
237237 """
238238 if len (collection ) <= 1 :
239239 return collection
240-
240+
241241 # Make a copy to avoid modifying the original if it's immutable
242242 arr = list (collection )
243243 n = len (arr )
244-
244+
245245 # Adjust key function for reverse sorting
246246 if reverse :
247247 if key :
248248 original_key = key
249- key = lambda x : - original_key (x ) if isinstance (original_key (x ), (int , float )) else original_key (x )
249+ key = (
250+ lambda x : - original_key (x )
251+ if isinstance (original_key (x ), (int , float ))
252+ else original_key (x )
253+ )
254+
250255 # For non-numeric types, we'll need a different approach
251256 # Store original key and use negation wrapper
252257 def reverse_key (x ):
253258 val = original_key (x )
254259 # For comparable types, we can't negate, so we'll reverse at the end
255260 return val
261+
256262 key = reverse_key
257263 needs_final_reverse = True
258264 else :
259265 key = lambda x : - x if isinstance (x , (int , float )) else x
260266 needs_final_reverse = True
261267 else :
262268 needs_final_reverse = False
263-
269+
264270 # Stack to hold runs: each entry is (start_index, length, power)
265271 # Capacity is ceil(log2(n)) + 1
266272 import math
273+
267274 stack_capacity = math .ceil (math .log2 (n )) + 1 if n > 1 else 2
268275 stack : list [tuple [int , int , int ]] = []
269-
276+
270277 start = 0
271278 while start < n :
272279 # Find the next run
273280 run_end = _find_run (arr , start , n , key )
274281 run_length = run_end - start
275-
282+
276283 # Calculate power for this run
277284 if len (stack ) == 0 :
278285 power = 0
279286 else :
280287 prev_start , prev_length , _ = stack [- 1 ]
281288 power = _node_power (n , prev_start , prev_length , start , run_length )
282-
289+
283290 # Merge runs from stack based on power comparison
284291 while len (stack ) > 0 and stack [- 1 ][2 ] >= power :
285292 # Merge the top run with the current run
286293 prev_start , prev_length , prev_power = stack .pop ()
287294 _merge (arr , prev_start , prev_start + prev_length , run_end , key )
288-
295+
289296 # Update current run to include the merged run
290297 start = prev_start
291298 run_length = run_end - start
292-
299+
293300 # Recalculate power
294301 if len (stack ) == 0 :
295302 power = 0
@@ -298,60 +305,62 @@ def reverse_key(x):
298305 power = _node_power (
299306 n , prev_prev_start , prev_prev_length , start , run_length
300307 )
301-
308+
302309 # Push current run onto stack
303310 stack .append ((start , run_length , power ))
304311 start = run_end
305-
312+
306313 # Merge all remaining runs on the stack
307314 while len (stack ) > 1 :
308315 start2 , length2 , _ = stack .pop ()
309316 start1 , length1 , power1 = stack .pop ()
310317 _merge (arr , start1 , start1 + length1 , start2 + length2 , key )
311-
318+
312319 # Recalculate power for merged run
313320 if len (stack ) == 0 :
314321 power = 0
315322 else :
316323 prev_start , prev_length , _ = stack [- 1 ]
317- power = _node_power (n , prev_start , prev_length , start1 , start2 + length2 - start1 )
318-
324+ power = _node_power (
325+ n , prev_start , prev_length , start1 , start2 + length2 - start1
326+ )
327+
319328 stack .append ((start1 , start2 + length2 - start1 , power ))
320-
329+
321330 # Handle reverse sorting for non-numeric types
322331 if reverse and needs_final_reverse :
323332 # For non-numeric types, we need to reverse the final result
324333 # Check if we used numeric negation or not
325334 if key and not isinstance (arr [0 ], (int , float )):
326335 arr .reverse ()
327-
336+
328337 return arr
329338
330339
331340if __name__ == "__main__" :
332341 import doctest
333-
342+
334343 doctest .testmod ()
335-
344+
336345 print ("\n PowerSort Interactive Testing" )
337346 print ("=" * 40 )
338-
347+
339348 try :
340349 user_input = input ("Enter numbers separated by a comma:\n " ).strip ()
341350 if user_input == "" :
342351 unsorted = []
343352 else :
344353 unsorted = [int (item .strip ()) for item in user_input .split ("," )]
345-
354+
346355 print (f"\n Original: { unsorted } " )
347356 sorted_list = power_sort (unsorted )
348357 print (f"Sorted: { sorted_list } " )
349-
358+
350359 # Test reverse
351360 sorted_reverse = power_sort (unsorted , reverse = True )
352361 print (f"Reverse: { sorted_reverse } " )
353-
362+
354363 except ValueError :
355364 print ("Invalid input. Please enter valid integers separated by commas." )
356365 except KeyboardInterrupt :
357- print ("\n \n Goodbye!" )
366+ print ("\n \n Goodbye!" )
0 commit comments