Skip to content

Commit f48c1d2

Browse files
committed
Merge branch 'master' of github.com:a3ro-dev/Python
2 parents 4c6bd9d + 3ab11af commit f48c1d2

1 file changed

Lines changed: 44 additions & 0 deletions

File tree

sorts/power_sort.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,22 @@ def _find_run(
3737
"""
3838
Detect a run (ascending or descending sequence) starting at 'start'.
3939
40+
4041
If the run is descending, reverse it in-place to make it ascending.
4142
Returns the end index (exclusive) of the detected run.
4243
44+
4345
Args:
4446
arr: The list to search in
4547
start: Starting index of the run
4648
end: End index (exclusive) of the search range
4749
key: Optional key function for comparisons
4850
51+
4952
Returns:
5053
End index (exclusive) of the detected run
5154
55+
5256
>>> arr = [3, 2, 1, 4, 5, 6]
5357
>>> _find_run(arr, 0, 6)
5458
3
@@ -63,9 +67,11 @@ def _find_run(
6367
if start >= end - 1:
6468
return start + 1
6569

70+
6671
key_func = key if key else lambda x: x
6772
run_end = start + 1
6873

74+
6975
# Check if run is ascending or descending
7076
if key_func(arr[run_end]) < key_func(arr[start]):
7177
# Descending run
@@ -78,28 +84,33 @@ def _find_run(
7884
while run_end < end and key_func(arr[run_end]) >= key_func(arr[run_end - 1]):
7985
run_end += 1
8086

87+
8188
return run_end
8289

8390

8491
def _node_power(n: int, b1: int, n1: int, b2: int, n2: int) -> int:
8592
"""
8693
Calculate the node power for two adjacent runs.
8794
95+
8896
This determines the merge priority in the stack. The power is the smallest
8997
integer p such that floor(a * 2^p) != floor(b * 2^p), where:
9098
- a = (b1 + n1/2) / n
9199
- b = (b2 + n2/2) / n
92100
101+
93102
Args:
94103
n: Total length of the array
95104
b1: Start index of first run
96105
n1: Length of first run
97106
b2: Start index of second run
98107
n2: Length of second run
99108
109+
100110
Returns:
101111
The calculated node power
102112
113+
103114
>>> _node_power(100, 0, 25, 25, 25)
104115
2
105116
>>> _node_power(100, 0, 50, 50, 50)
@@ -122,6 +133,7 @@ def _node_power(n: int, b1: int, n1: int, b2: int, n2: int) -> int:
122133
while (a * (1 << power)) // two_n == (b * (1 << power)) // two_n:
123134
power += 1
124135

136+
125137
return power
126138

127139

@@ -135,15 +147,18 @@ def _merge(
135147
"""
136148
Merge two adjacent sorted runs in-place using auxiliary space.
137149
150+
138151
Merges arr[start1:end1] with arr[end1:end2].
139152
153+
140154
Args:
141155
arr: The list containing the runs
142156
start1: Start index of first run
143157
end1: End index of first run (start of second run)
144158
end2: End index of second run
145159
key: Optional key function for comparisons
146160
161+
147162
>>> arr = [1, 3, 5, 2, 4, 6]
148163
>>> _merge(arr, 0, 3, 6)
149164
>>> arr
@@ -155,13 +170,16 @@ def _merge(
155170
"""
156171
key_func = key if key else lambda x: x
157172

173+
158174
# Copy the runs to temporary storage
159175
left = arr[start1:end1]
160176
right = arr[end1:end2]
161177

178+
162179
i = j = 0
163180
k = start1
164181

182+
165183
# Merge the two runs
166184
while i < len(left) and j < len(right):
167185
if key_func(left[i]) <= key_func(right[j]):
@@ -172,12 +190,14 @@ def _merge(
172190
j += 1
173191
k += 1
174192

193+
175194
# Copy remaining elements
176195
while i < len(left):
177196
arr[k] = left[i]
178197
i += 1
179198
k += 1
180199

200+
181201
while j < len(right):
182202
arr[k] = right[j]
183203
j += 1
@@ -193,20 +213,25 @@ def power_sort(
193213
"""
194214
Sort a list using the PowerSort algorithm.
195215
216+
196217
PowerSort is an adaptive merge sort that detects existing runs in the data
197218
and uses a power-based merging strategy for optimal performance.
198219
220+
199221
Args:
200222
collection: A mutable ordered collection with comparable items
201223
key: Optional function to extract comparison key from each element
202224
reverse: If True, sort in descending order
203225
226+
204227
Returns:
205228
The same collection ordered according to the parameters
206229
230+
207231
Time Complexity: O(n log n) worst case, O(n) for nearly sorted data
208232
Space Complexity: O(n)
209233
234+
210235
Examples:
211236
>>> power_sort([0, 5, 3, 2, 2])
212237
[0, 2, 2, 3, 5]
@@ -244,10 +269,12 @@ def power_sort(
244269
if len(collection) <= 1:
245270
return collection
246271

272+
247273
# Make a copy to avoid modifying the original if it's immutable
248274
arr = list(collection)
249275
n = len(arr)
250276

277+
251278
# Adjust key function for reverse sorting
252279
needs_final_reverse = False
253280
if reverse:
@@ -260,6 +287,7 @@ def reverse_key(x):
260287
return -val
261288
return val
262289

290+
263291
key = reverse_key
264292
needs_final_reverse = True
265293
else:
@@ -275,29 +303,34 @@ def reverse_cmp(x):
275303
# Stack to hold runs: each entry is (start_index, length, power)
276304
stack: list[tuple[int, int, int]] = []
277305

306+
278307
start = 0
279308
while start < n:
280309
# Find the next run
281310
run_end = _find_run(arr, start, n, key)
282311
run_length = run_end - start
283312

313+
284314
# Calculate power for this run
285315
if len(stack) == 0:
286316
power = 0
287317
else:
288318
prev_start, prev_length, _ = stack[-1]
289319
power = _node_power(n, prev_start, prev_length, start, run_length)
290320

321+
291322
# Merge runs from stack based on power comparison
292323
while len(stack) > 0 and stack[-1][2] >= power:
293324
# Merge the top run with the current run
294325
prev_start, prev_length, _ = stack.pop()
295326
_merge(arr, prev_start, prev_start + prev_length, run_end, key)
296327

328+
297329
# Update current run to include the merged run
298330
start = prev_start
299331
run_length = run_end - start
300332

333+
301334
# Recalculate power
302335
if len(stack) == 0:
303336
power = 0
@@ -307,16 +340,19 @@ def reverse_cmp(x):
307340
n, prev_prev_start, prev_prev_length, start, run_length
308341
)
309342

343+
310344
# Push current run onto stack
311345
stack.append((start, run_length, power))
312346
start = run_end
313347

348+
314349
# Merge all remaining runs on the stack
315350
while len(stack) > 1:
316351
start2, length2, _ = stack.pop()
317352
start1, length1, _ = stack.pop()
318353
_merge(arr, start1, start1 + length1, start2 + length2, key)
319354

355+
320356
# Recalculate power for merged run
321357
if len(stack) == 0:
322358
power = 0
@@ -327,6 +363,7 @@ def reverse_cmp(x):
327363

328364
stack.append((start1, start2 + length2 - start1, power))
329365

366+
330367
# Handle reverse sorting for non-numeric types
331368
if (
332369
reverse
@@ -345,27 +382,34 @@ def reverse_cmp(x):
345382
if __name__ == "__main__":
346383
import doctest
347384

385+
348386
doctest.testmod()
349387

388+
350389
print("\nPowerSort Interactive Testing")
351390
print("=" * 40)
352391

392+
353393
try:
354394
user_input = input("Enter numbers separated by a comma:\n").strip()
355395
if user_input == "":
356396
unsorted = []
357397
else:
358398
unsorted = [int(item.strip()) for item in user_input.split(",")]
359399

400+
360401
print(f"\nOriginal: {unsorted}")
361402
sorted_list = power_sort(unsorted)
362403
print(f"Sorted: {sorted_list}")
363404

405+
364406
# Test reverse
365407
sorted_reverse = power_sort(unsorted, reverse=True)
366408
print(f"Reverse: {sorted_reverse}")
367409

410+
368411
except ValueError:
369412
print("Invalid input. Please enter valid integers separated by commas.")
370413
except KeyboardInterrupt:
371414
print("\n\nGoodbye!")
415+

0 commit comments

Comments
 (0)