forked from mikeizbicki/sorting
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsorting.py
183 lines (151 loc) · 5.47 KB
/
sorting.py
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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
#!/bin/python3
'''
Python provides built-in sort/sorted functions that use timsort internally.
You cannot use these built-in functions anywhere in this file.
Every function in this file takes a comparator `cmp` as input
which controls how the elements of the list should be compared against each other:
If cmp(a, b) returns -1, then a < b;
if cmp(a, b) returns 1, then a > b;
if cmp(a, b) returns 0, then a == b.
'''
# import random
def cmp_standard(a, b):
'''
used for sorting from lowest to highest
>>> cmp_standard(125, 322)
-1
>>> cmp_standard(523, 322)
1
'''
if a < b:
return -1
if b < a:
return 1
return 0
def cmp_reverse(a, b):
'''
used for sorting from highest to lowest
>>> cmp_reverse(125, 322)
1
>>> cmp_reverse(523, 322)
-1
'''
if a < b:
return 1
if b < a:
return -1
return 0
def cmp_last_digit(a, b):
'''
used for sorting based on the last digit only
>>> cmp_last_digit(125, 322)
1
>>> cmp_last_digit(523, 322)
1
'''
return cmp_standard(a % 10, b % 10)
def _merged(xs, ys, cmp=cmp_standard):
'''
Assumes that both xs and ys are sorted,
and returns a new list containing the elements of both xs and ys.
Runs in linear time.
NOTE:
In python, helper functions are frequently prepended with the _.
This is a signal to users of a library that these functions are for "internal use only",
and not part of the "public interface".
This _merged function could be implemented as a local function within the merge_sorted scope rather than a global function.
The downside of this is that the function can then not be tested on its own.
Typically, you should only implement a function as a local function if it cannot function on its own
(like the go functions from binary search).
If it's possible to make a function stand-alone,
then you probably should do that and write test cases for the stand-alone function.
>>> _merged([1, 3, 5], [2, 4, 6])
[1, 2, 3, 4, 5, 6]
'''
ixs = 0
iys = 0
ret = []
while ixs < len(xs) and iys < len(ys):
if cmp(xs[ixs], ys[iys]) == -1:
ret.append(xs[ixs])
ixs += 1
else:
ret.append(ys[iys])
iys += 1
while ixs < len(xs):
ret.append(xs[ixs])
ixs += 1
while iys < len(ys):
ret.append(ys[iys])
iys += 1
return ret
def merge_sorted(xs, cmp=cmp_standard):
'''
Merge sort is the standard O(n log n) sorting algorithm.
Recall that the merge sort pseudo code is:
if xs has 1 element
it is sorted, so return xs
else
divide the list into two halves left,right
sort the left
sort the right
merge the two sorted halves
You should return a sorted version of the input list xs.
You should not modify the input list xs in any way.
'''
if len(xs) <= 1:
return xs
mid = len(xs) // 2
left = xs[:mid]
right = xs[mid:]
left_sorted = merge_sorted(left, cmp=cmp)
right_sorted = merge_sorted(right, cmp=cmp)
return _merged(left_sorted, right_sorted, cmp=cmp)
def quick_sorted(xs, cmp=cmp_standard):
'''
Quicksort is like mergesort,
but it uses a different strategy to split the list.
Instead of splitting the list down the middle,
a "pivot" value is randomly selected,
and the list is split into a "less than" sublist and a "greater than" sublist.
The pseudocode is:
if xs has 1 element
it is sorted, so return xs
else
select a pivot value p
put all the values less than p in a list
put all the values greater than p in a list
put all the values equal to p in a list
sort the greater/less than lists recursively
return the concatenation of (less than, equal, greater than)
You should return a sorted version of the input list xs.
You should not modify the input list xs in any way.
'''
if len(xs) <= 1:
return xs
mid = len(xs) // 2
pivot = xs[mid]
xs_lt = [x for x in xs if cmp(x, pivot) == -1]
xs_gt = [x for x in xs if cmp(x, pivot) == 1]
xs_eq = [x for x in xs if cmp(x, pivot) == 0]
xs_lt = quick_sorted(xs_lt, cmp=cmp)
xs_gt = quick_sorted(xs_gt, cmp=cmp)
return xs_lt + xs_eq + xs_gt
def quick_sort(xs, cmp=cmp_standard):
'''
EXTRA CREDIT:
The main advantage of quick_sort is that it can be implemented "in-place".
This means that no extra lists are allocated,
or that the algorithm uses Theta(1) additional memory.
Merge sort, on the other hand, must allocate intermediate lists for the merge step,
and has a Theta(n) memory requirement.
Even though quick sort and merge sort both have the same Theta(n log n) runtime,
this more efficient memory usage typically makes quick sort faster in practice.
(We say quick sort has a lower "constant factor" in its runtime.)
The downside of implementing quick sort in this way is that it will no longer be a [stable sort](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability),
but this is typically inconsequential.
Follow the pseudocode of the Lomuto partition scheme given on wikipedia
(https://en.wikipedia.org/wiki/Quicksort#Algorithm)
to implement quick_sort as an in-place algorithm.
You should directly modify the input xs variable instead of returning a copy of the list.
'''