-
-
Notifications
You must be signed in to change notification settings - Fork 50.8k
Expand file tree
/
Copy pathtim_sort.py
More file actions
156 lines (120 loc) · 4.26 KB
/
Copy pathtim_sort.py
File metadata and controls
156 lines (120 loc) · 4.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
from typing import Any
def binary_search(lst: list[Any], item: Any, start: int, end: int) -> int:
""">>> binary_search([1, 3, 5], 4, 0, 2)
2
>>> binary_search([1, 3, 5], 0, 0, 2)
0
>>> binary_search([1, 3, 5], 6, 0, 2)
3
Find the insertion index for ``item`` in a sorted sublist.
It performs a recursive binary search on ``lst`` between indices
``start`` and ``end`` (inclusive) and returns the index showing
where to insert the item so the list stays sorted.
Args:
lst: A list of comparable items.
The sublist from ``start`` to ``end`` must already be sorted.
item: The value to locate an insertion index for.
start: Left-most index of the sorted sublist to search.
end: Right-most index of the sorted sublist to search.
Returns:
The index at which ``item`` should be inserted.
Complexity:
Time: ``O(log n)`` for the searched sublist.
Space: ``O(log n)`` due to recursion depth.
"""
if start == end:
return start if lst[start] > item else start + 1
if start > end:
return start
mid = (start + end) // 2
if lst[mid] < item:
return binary_search(lst, item, mid + 1, end)
elif lst[mid] > item:
return binary_search(lst, item, start, mid - 1)
else:
return mid
def insertion_sort(lst: list[Any]) -> list[Any]:
""">>> insertion_sort([3, 2, 1])
[1, 2, 3]
Return a sorted copy of ``lst`` using insertion sort.
Uses ``binary_search`` to find where to insert each item. The
input list is not modified; a new sorted list is returned.
Args:
lst: The list to sort. A new list is returned; the input list is
not modified in-place.
Returns:
A new list containing the elements of ``lst`` in ascending order.
Complexity:
Time: ``O(n^2)`` in the worst case because each insertion may
shift many elements.
Space: ``O(n)`` for the reconstructed list copies.
"""
length = len(lst)
for index in range(1, length):
value = lst[index]
pos = binary_search(lst, value, 0, index - 1)
lst = [*lst[:pos], value, *lst[pos:index], *lst[index + 1 :]]
return lst
def merge(left: list[Any], right: list[Any]) -> list[Any]:
""">>> merge([1, 4], [2, 3])
[1, 2, 3, 4]
Merge two sorted lists and return a new sorted list.
Args:
left: A list sorted in ascending order.
right: A list sorted in ascending order.
Returns:
A new list containing all elements from ``left`` and ``right`` in
ascending order.
Complexity:
Time: ``O(n + m)`` where ``n`` and ``m`` are the input lengths.
Space: ``O(n + m)`` because recursive slicing creates new lists.
"""
if not left:
return right
if not right:
return left
if left[0] < right[0]:
return [left[0], *merge(left[1:], right)]
return [right[0], *merge(left, right[1:])]
def tim_sort(lst: list[Any] | tuple[Any, ...] | str) -> list[Any]:
"""
>>> tim_sort("Python")
['P', 'h', 'n', 'o', 't', 'y']
>>> tim_sort((1.1, 1, 0, -1, -1.1))
[-1.1, -1, 0, 1, 1.1]
>>> tim_sort(list(reversed(list(range(7)))))
[0, 1, 2, 3, 4, 5, 6]
>>> tim_sort([3, 2, 1]) == insertion_sort([3, 2, 1])
True
>>> tim_sort([3, 2, 1]) == sorted([3, 2, 1])
True
Sort and return the input using a TimSort-like approach: detect
runs, sort each run with insertion sort, then merge the runs.
Complexity:
Time: ``O(n log n)`` in the common case.
Space: ``O(n)`` for the extra lists used during sorting.
"""
length = len(lst)
runs, sorted_runs = [], []
new_run = [lst[0]]
sorted_array: list[Any] = []
i = 1
while i < length:
if lst[i] < lst[i - 1]:
runs.append(new_run)
new_run = [lst[i]]
else:
new_run.append(lst[i])
i += 1
runs.append(new_run)
for run in runs:
sorted_runs.append(insertion_sort(run))
for run in sorted_runs:
sorted_array = merge(sorted_array, run)
return sorted_array
def main():
lst = [5, 9, 10, 3, -4, 5, 178, 92, 46, -18, 0, 7]
sorted_lst = tim_sort(lst)
print(sorted_lst)
if __name__ == "__main__":
main()