Skip to content

Commit 3ab11af

Browse files
[pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
1 parent 6c46eaf commit 3ab11af

1 file changed

Lines changed: 59 additions & 50 deletions

File tree

sorts/power_sort.py

Lines changed: 59 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -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

8383
def _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

331340
if __name__ == "__main__":
332341
import doctest
333-
342+
334343
doctest.testmod()
335-
344+
336345
print("\nPowerSort 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"\nOriginal: {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\nGoodbye!")
366+
print("\n\nGoodbye!")

0 commit comments

Comments
 (0)