Quick jump with Python
In real life, we should always use the built-in sorting provided by Python. However, understanding the quicksort algorithm is instructive.
My goal here is to break the topic in such a way that it is easy to understand and reproduce by the reader, without returning to reference materials.
The quicksort algorithm is essentially the following:
- Select a summary point.
- Move all data points smaller (lower) than the rotation axis to a position below the rotation axis - move those that are greater than or equal to (higher) the rotation axis to a position above it.
- Apply the algorithm to areas above and below the pivot point
If the data is distributed randomly, selecting the first data point, since the bar is equivalent to random selection.
Readable example:
First, let's look at a readable example that uses comments and variable names to indicate intermediate values:
def quicksort(xs): """Given indexable and slicable iterable, return a sorted list""" if xs:
To repeat the algorithm and code described here, we move the values above the pivot point to the right and the values below the pivot point to the left, and then pass these sections to the same function for further sorting.
Golfed:
It can be golf up to 88 characters:
q=lambda x:x and q([i for i in x[1:]if i<=x[0]])+[x[0]]+q([i for i in x[1:]if i>x[0]])
To see how we get there, first take our readable example, delete comments and docstrings and find the anchor point in place:
def quicksort(xs): if xs: below = [i for i in xs[1:] if i < xs[0]] above = [i for i in xs[1:] if i >= xs[0]] return quicksort(below) + [xs[0]] + quicksort(above) else: return xs
Now find lower and higher in place:
def quicksort(xs): if xs: return (quicksort([i for i in xs[1:] if i < xs[0]] ) + [xs[0]] + quicksort([i for i in xs[1:] if i >= xs[0]])) else: return xs
Now, knowing this and returns the previous element, if false, otherwise, if it is true, it evaluates and returns the next element, we have:
def quicksort(xs): return xs and (quicksort([i for i in xs[1:] if i < xs[0]] ) + [xs[0]] + quicksort([i for i in xs[1:] if i >= xs[0]]))
Since lambdas returns a single click, and we simplified one expression (even if it becomes more unreadable), we can now use lambda:
quicksort = lambda xs: (quicksort([i for i in xs[1:] if i < xs[0]] ) + [xs[0]] + quicksort([i for i in xs[1:] if i >= xs[0]]))
And to shorten our example, reduce the names of functions and variables to one letter and eliminate spaces that are not required.
q=lambda x:x and q([i for i in x[1:]if i<=x[0]])+[x[0]]+q([i for i in x[1:]if i>x[0]])
Please note that this lambda, like most golf games, is pretty bad.
QuickSort in place using the Hoare splitting scheme
The preliminary implementation creates many unnecessary additional lists. If we can do this locally, we will avoid losing space.
The implementation below uses the Hoare partitioning scheme, which you can learn more about Wikipedia about (but we apparently deleted up to 4 redundant calculations on the partition() call, using the while-loop semantics instead of do-while and moving the narrowing steps to the end of the outer while loop.).
def quicksort(a_list): """Hoare partition scheme, see https://en.wikipedia.org/wiki/Quicksort""" def _quicksort(a_list, low, high):
Not sure if I checked it thoroughly enough:
def main(): assert quicksort([1]) == [1] assert quicksort([1,2]) == [1,2] assert quicksort([1,2,3]) == [1,2,3] assert quicksort([1,2,3,4]) == [1,2,3,4] assert quicksort([2,1,3,4]) == [1,2,3,4] assert quicksort([1,3,2,4]) == [1,2,3,4] assert quicksort([1,2,4,3]) == [1,2,3,4] assert quicksort([2,1,1,1]) == [1,1,1,2] assert quicksort([1,2,1,1]) == [1,1,1,2] assert quicksort([1,1,2,1]) == [1,1,1,2] assert quicksort([1,1,1,2]) == [1,1,1,2]
Conclusion
This algorithm is often taught in computer science courses and is asked for in an interview for an interview. It helps us think about recursion and divide and win.
Quicksort is not very practical in Python, as our built-in timsort algorithm is quite efficient, and we have recursion limits. We would expect sorting lists in place with list.sort or creating new sorted lists with sorted - both of which accept key and reverse arguments.