SlideShare a Scribd company logo
1 of 28
Download to read offline
Quicksort
Languages: Haskell, Scala, Java, Clojure, Prolog
Due credit must be paid to the genius of the designers of ALGOL 60
who included recursion in their language and enabled me to describe
my invention [Quicksort] so elegantly to the world.
Ask a professional computer scientist or programmer to list their top
10 algorithms, and you’ll find Quicksort on many lists, including mine.
… On the aesthetic side, Quicksort is just a remarkably beautiful
algorithm, with an equally beautiful running time analysis.
Programming Paradigms: Functional, Logic, Imperative, Imperative Functional
Tim Roughgarden
a whistle-stop tour of the algorithm in five languages and four paradigms
@philip_schwarz
slides by https://www.slideshare.net/pjschwarz
C. Anthony
R. Hoare
Graham Hutton
@haskellhutt
Richard Bird
Barbara Liskov @algo_class
Graham Hutton
@haskellhutt
…
This function produces a sorted version of any list of numbers.
The first equation for qsort states that the empty list is already sorted, while the second states that any non-empty list can be
sorted by inserting the first number between the two lists that result from sorting the remaining numbers that are smaller and
larger than this number.
This method of sorting is called quicksort, and is one of the best such methods known. The above implementation of quicksort is
an excellent example of the power of Haskell, being both clear and concise.
Moreover, the function qsort is also more general than might be expected, being applicable not just with numbers, but with any
type of ordered values. More precisely, the type qsort :: Ord a => [a] -> [a] states that, for any type a of ordered values, qsort is a
function that maps between lists of such values.
Haskell supports many different types of ordered values, including numbers, single characters such as ’a’, and strings of characters
such as "abcde".
Hence, for example, the function qsort could also be used to sort a list of characters, or a list of strings.
qsort :: Ord a => [a] -> [a]
qsort [] = []
qsort (x:xs) = qsort smaller ++ [x] ++ qsort larger
where
smaller = [a | a <- xs, a <= x]
larger = [b | b <- xs, b > x ]
Sorting values
Now let us consider a more sophisticated function concerning lists, which illustrates a number of other aspects of Haskell. Suppose that
we define a function called qsort by the following two equations:
The empty list is already sorted, and any non-empty list can be sorted by
placing its head between the two lists that result from sorting those
elements of its tail that are smaller and larger than the head
Graham Hutton
@haskellhutt
What I wanted to show you today, is what Haskell looks like, because it looks quite unlike any
other language that you probably have seen.
The program here is very concise, it is only five lines long, there is essentially no junk syntax
here, it would be hard to think of eliminating any of the symbols here, and this is in contrast
to what you find in most other programming languages.
If you have seen sorting algorithms like Quicksort before, maybe in C, maybe in Java, maybe in
an other language, it is probably going to be much longer than this version.
So for me, writing a program like this in Haskell catches the essence of the Quicksort algorithm.
Functional Programming in Haskell - FP 3 - Introduction
The point here at the bottom, it is quite a bold statement, but I actually do
believe it is true, this is probably the simplest implementation of
Quicksort in any programming language.
If you can show me a simpler one than this, I’ll be very interested to see it,
but I don’t really see what you can take out of this program and actually
still have the Quicksort algorithm.
qsort :: Ord a => [a] -> [a]
qsort [] = []
qsort (x:xs) =
qsort smaller ++ [x] ++ qsort larger
where
smaller = [a | a <- xs, a <= x]
larger = [b | b <- xs, b > x ]
This is probably the simplest implementation of
Quicksort in any programming language!
Functional Programming in Haskell - FP 8 – Recursive Functions
def qsort[A:Ordering](as: List[A]): List[A] = as match
case Nil => Nil
case x::xs =>
val smaller = for a <- xs if a <= x yield a
val larger = for b <- xs if b > x yield b
qsort(smaller) ++ List(x) ++ qsort(larger)
qsort :: Ord a => [a] -> [a]
qsort [] = []
qsort (x:xs) = qsort smaller ++ [x] ++ qsort larger
where
smaller = [a | a <- xs, a <= x]
larger = [b | b <- xs, b > x ]
(defn qsort [elements]
(if (empty? elements)
elements
(let [[x & xs] elements
smaller (for [a xs :when (<= a x)] a)
larger (for [b xs :when (> b x)] b)]
(concat (qsort smaller) (list x) (qsort larger)))))
Here is the Haskell qsort function again, together
with the equivalent Scala and Clojure functions.
given Ordering[RGB] with
def compare(x: RGB, y: RGB): Int =
x.ordinal compare y.ordinal
data RGB = Red | Green | Blue deriving(Eq,Ord,Show)
enum RGB :
case Red, Green, Blue
TestCase (assertEqual "sort integers" [1,2,3,3,4,5] (qsort [5,1,3,2,4,3]))
TestCase (assertEqual "sort doubles" [1.1,2.3,3.4,3.4,4.5,5.2] (qsort [5.2,1.1,3.4,2.3,4.5,3.4]))
TestCase (assertEqual "sort chars" "abccde" (qsort "acbecd"))
TestCase (assertEqual "sort strings" ["abc","efg","uvz"] (qsort ["abc","uvz","efg"]) )
TestCase (assertEqual "sort colours" [Red,Green,Blue] (qsort [Blue,Green,Red]))
assert(qsort(List(5,1,2,4,3)) == List(1,2,3,4,5))
assert(qsort(List(5.2,1.1,3.4,2.3,4.5,3.4)) == List(1.1,2.3,3.4,3.4,4.5,5.2))
assert(qsort(List(Blue,Green,Red)) == List(Red,Green,Blue))
assert(qsort("acbecd".toList) == "abccde".toList)
assert(qsort(List ("abc","uvz","efg")) == List("abc","efg","uvz"))
And here are some Haskell
and Scala tests for qsort.
qsort :: Ord a => [a] -> [a]
qsort [] = []
qsort (x:xs) = qsort smaller ++ [x] ++ qsort larger
where
smaller = filter (x >) xs
larger = filter (x <=) xs
qsort :: Ord a => [a] -> [a]
qsort [] = []
qsort (x:xs) = qsort smaller ++ [x] ++ qsort larger
where
smaller = [a | a <- xs, a <= x]
larger = [b | b <- xs, b > x ]
def qsort[A:Ordering](as: List[A]): List[A] = as match
case Nil => Nil
case x::xs =>
val smaller = for a <- xs if a <= x yield a
val larger = for b <- xs if b > x yield b
qsort(smaller) ++ List(x) ++ qsort(larger)
(defn qsort [elements]
(if (empty? elements)
elements
(let [[x & xs] elements
smaller (for [a xs :when (<= a x)] a)
larger (for [b xs :when (> b x)] b)]
(concat (qsort smaller) (list x) (qsort larger)))))
(defn qsort [elements]
(if (empty? elements)
elements
(let [[x & xs] elements
smaller (filter #(<= % x) xs)
larger (filter #(> % x) xs)]
(concat (qsort smaller) (list x) (qsort larger)))))
def qsort[A:Ordering](as: List[A]): List[A] = as match
case Nil => Nil
case x::xs =>
val smaller = xs filter (_ <= x)
val larger = xs filter (_ > x)
qsort(smaller) ++ List(x) ++ qsort(larger)
Let’s use the predefined filter function to make
the qsort functions a little bit more succinct.
qsort :: Ord a => [a] -> [a]
qsort [] = []
qsort (x:xs) = qsort smaller ++ [x] ++ qsort larger
where
smaller = filter (x >) xs
larger = filter (x <=) xs
(defn qsort [elements]
(if (empty? elements)
elements
(let [[x & xs] elements
smaller (filter #(<= % x) xs)
larger (filter #(> % x) xs)]
(concat (qsort smaller) (list x) (qsort larger)))))
def qsort[A:Ordering](as: List[A]): List[A] = as match
case Nil => Nil
case x::xs =>
val smaller = xs filter (_ <= x)
val larger = xs filter (_ > x)
qsort(smaller) ++ List(x) ++ qsort(larger)
Now let’s improve the qsort functions a bit further
by using the predefined partition function.
def qsort[A:Ordering](as: List[A]): List[A] = as match
case Nil => Nil
case x::xs =>
val (smaller,larger) = xs partition (_ <= x)
qsort(smaller) ++ List(x) ++ qsort(larger)
(defn qsort [elements]
(if (empty? elements)
elements
(let [[x & xs] elements
[smaller larger] (split-with #(<= % x) xs)]
(concat (qsort smaller) (list x) (qsort larger)))))
qsort :: Ord a => [a] -> [a]
qsort [] = []
qsort (x:xs) = qsort smaller ++ [x] ++ qsort larger
where (smaller,larger) = partition (x >) xs
@philip_schwarz
qsort( [], [] ).
qsort( [X|XS], Sorted ) :-
partition(X, XS, Smaller, Larger),
qsort(Smaller, SortedSmaller),
qsort(Larger, SortedLarger),
append(SortedSmaller, [X|SortedLarger], Sorted).
partition( _, [], [], [] ).
partition( X, [Y|YS], [Y|Smaller], Larger ) :- Y =< X, partition(X, YS, Smaller, Larger).
partition( X, [Y|YS], Smaller, [Y|Larger] ) :- Y > X, partition(X, YS, Smaller, Larger).
qsort :: Ord a => [a] -> [a]
qsort [] = []
qsort (x:xs) = qsort smaller ++ [x] ++ qsort larger
where
smaller = [a | a <- xs, a <= x]
larger = [b | b <- xs, b > x ]
Like Haskell, Prolog (the Logic Programming language) is also clear and succinct.
Let’s see how a Prolog version of Quicksort compares with the Haskell one.
def qsort[A:Ordering](xs: List[A]): List[A] =
xs.headOption.fold(Nil){ x =>
val smaller = xs.tail filter (_ <= x)
val larger = xs.tail filter (_ > x)
qsort(smaller) ++ List(x) ++ qsort(larger)
}
private static <T extends Comparable> List<T> qsort(final List<T> xs) {
return xs.stream().findFirst().map((final T x) -> {
final List<T> smaller = xs.stream().filter(n -> n.compareTo(x) < 0).collect(toList());
final List<T> larger = xs.stream().skip(1).filter(n -> n.compareTo(x) >= 0).collect(toList());
return Stream.of(qsort(smaller), List.of(x), qsort(larger))
.flatMap(Collection::stream)
.collect(Collectors.toList());
}).orElseGet(Collections::emptyList);
}
Here, we first create a variant of the Scala qsort function that uses headOption and
fold, and then we have a go at writing a somewhat similar Java function using Streams.
def qsort[A:Ordering](as: List[A]): List[A] = as match
case Nil => Nil
case x::xs =>
val smaller = xs filter (_ <= x)
val larger = xs filter (_ > x)
qsort(smaller) ++ List(x) ++ qsort(larger)
def qsort[A:Ordering](xs: List[A]): List[A] =
xs.headOption.fold(Nil){ x =>
val (smaller,larger) = xs partition (_ <= x)
qsort(smaller) ++ List(x) ++ qsort(larger)
}
private static <T extends Comparable> List<T> qsort(final List<T> xs) {
return xs.stream().findFirst().map((final T x) -> {
Map<Boolean, List<T>> partitions = xs.stream().skip(1).collect(Collectors.partitioningBy(n -> n.compareTo(x) < 0));
final List<T> smaller = partitions.get(true);
final List<T> larger = partitions.get(false);
return Stream.of(qsort(smaller), List.of(x), qsort(larger))
.flatMap(Collection::stream)
.collect(Collectors.toList());
}).orElseGet(Collections::emptyList);
}
Same as the previous slide, but here
we use partition rather than filter.
def qsort[A:Ordering](as: List[A]): List[A] = as match
case Nil => Nil
case x::xs =>
val (smaller,larger) = xs partition (_ <= x)
qsort(smaller) ++ List(x) ++ qsort(larger)
On the next slide, a brief reminder of the
definition of the Quicksort algorithm.
@philip_schwarz
Description of quicksort
Quicksort, like merge sort, applies the divide-and-conquer paradigm. Here is the
three-step divide-and-conquer process for sorting a typical subarray A [ p . . r ] :
Divide: Partition (rearrange) the array A [p . .r ] into two (possibly empty) subarrays
A [ p . . q βˆ’ 1 ] and A [ q + 1 . . r ] such that each element of A [ p . . q - 1 ] is less
than or equal to A [ q ], which is, in turn, less than or equal to each element of A [ q
+ 1 . . r ]. Compute the index q as part of this partitioning procedure.
Conquer: Sort the two subarrays A [ p . . q βˆ’ 1 ] and A [ q + 1 . . r ] by recursive calls
to quicksort.
Combine: Because the subarrays are already sorted, no work is needed to combine
them: the entire array A [ p . . r ] is now sorted.
The next slide is a reminder of some of the
salient aspects of the Quicksort algorithm.
The Upshot
The famous Quicksort algorithm has three high-level steps:
1. It chooses one element p of the input array to act as a pivot element
2. Its Partition subroutine rearranges the array so that elements smaller
than and greater than p come before it and after it, respectively
3. It recursively sorts the two subarrays on either side of the pivot
The Partition subroutine can be implemented to run in linear time and in
place, meaning with negligible additional memory. As a consequence, Quicksort
also runs in place.
The correctness of the Quicksort algorithm does not depend on how pivot
elements are chosen, but its running time does.
The worst-case scenario is a running time of Θ 𝑛2 , where 𝑛 is the length of the
input array. This occurs when the input array is already sorted, and the first element
is always used as the pivot element. The best-case scenario is a running time of
Θ 𝑛 log 𝑛 . This occurs when the median element is always used as the pivot.
In randomized Quicksort, the pivot element is always chosen uniformly at
random. Its running time can be anywhere from Θ 𝑛 log 𝑛 to Θ 𝑛2 , depending on
its random coin flips.
The average running time of randomized Quicksort is 𝛩 𝑛 π‘™π‘œπ‘” 𝑛 , only a small
constant factor worse than its best-case running time.
A comparison-based sorting algorithm is a general-purpose algorithm that accesses
the input array only by comparing pairs of elements, and never directly uses the
value of an element.
No comparison-based sorting algorithm has a worst-case asymptotic running time
better than 𝛩 𝑛 π‘™π‘œπ‘” 𝑛 .
…
Tim Roughgarden
@algo_class
ChoosePivot
Input: array A of 𝑛 distinct integers, left and right endpoints
β„“, π‘Ÿ ∈ 1,2, … , 𝑛 .
Output: an index 𝑖 ∈ β„“, β„“ + 1, … , π‘Ÿ .
Implementation:
β€’ NaΓ―ve: return β„“.
β€’ Overkill: return position of the median element of
A [β„“], … , A [π‘Ÿ] .
β€’ Randomized: return an element of β„“, β„“ + 1, … , π‘Ÿ , chosen
uniformly, at random.
On the next slide we see an imperative
(Java) implementation of Quicksort,
i.e. one that does the sorting in place.
Barbara Liskov
public class Arrays {
// OVERVIEW: …
public static void sort (int[ ] a) {
// MODIFIES: a
// EFFECTS: Sorts a[0], …, a[a.length - 1] into ascending order.
if (a == null) return;
quickSort(a, 0, a.length-1); }
private static void quickSort(int[ ] a, int low, int high) {
// REQUIRES: a is not null and 0 <= low & high > a.length
// MODIFIES: a
// EFFECTS: Sorts a[low], a[low+1], …, a[high] into ascending order.
if (low >= high) return;
int mid = partition(a, low, high);
quickSort(a, low, mid);
quickSort(a, mid + 1, high); }
private static int partition(int[ ] a, int i, int j) {
// REQUIRES: a is not null and 0 <= i < j < a.length
// MODIFIES: a
// EFFECTS: Reorders the elements in a into two contiguous groups,
// a[i],…, a[res] and a[res+1],…, a[j], such that each
// element in the second group is at least as large as each
// element of the first group. Returns res.
int x = a[i];
while (true) {
while (a[j] > x) j--;
while (a[i] < x) i++;
if (i < j) { // need to swap
int temp = a[i]; a[i] = a[j]; a[j] = temp;
j--; i++; }
else return j; }
}
}
quick sort … partitions the elements of the array into two contiguous
groups such that all the elements in the first group are no larger than
those in the second group; it continues to partition recursively until
the entire array is sorted.
To carry out these steps, we use two subsidiary
procedures: quickSort, which causes the partitioning of smaller and
smaller subparts of the array, and partition, which performs the
partitioning of a designated subpart of the array.
Note that the quickSort and partition routines are not declared to
be public; instead, their use is limited to the Arrays class.
This is appropriate because they are just helper routines and have
little utility in their own right.
Nevertheless, we have provided specifications for them; these
specifications are of interest to someone interested in understanding
how quickSort is implemented but not to a user of quickSort
Yes, that was Barbara Liskov, of Liskov Substitution Principle fame.
Now that we have seen both functional and imperative implementations of Quicksort, we go back to the
Haskell functional implementation and learn about the following:
β€’ Shortcomings of the functional implementation
β€’ How the functional implementation can be made more efficient in its use of space.
β€’ How the functional implementation can be rewritten in a hybrid imperative functional style.
Because the last of the above topics involves fairly advanced functional programming techniques, we are
simply going to have a quick, high-level look at it, to whet our appetite and motivate us to find out more.
Richard Bird
Our second sorting algorithm is a famous one called Quicksort. It can be expressed in just two lines of Haskell:
sort :: (Ord a) => [a] -> [a]
sort [] = []
sort (x:xs) = sort [y | y <- xs, y < x] ++ [x] ++ sort [y | y <- xs, x <= y]
That’s very pretty and a testament to the expressive power of Haskell. But the prettiness comes at a cost: the program can be
very inefficient in its use of space.
Before plunging into ways the code can be optimized, let’s compute T π‘ π‘œπ‘Ÿπ‘‘ . Suppose we want to sort a list of length 𝑛 + 1. The
first list comprehension can return a list of any length π‘˜ from 0 to 𝑛. The length of the result of the second list comprehension is
therefore 𝑛 βˆ’ π‘˜. Since our timing function is an estimate of the worst-case running time, we have to take the maximum of these
possibilities:
T π‘ π‘œπ‘Ÿπ‘‘ 𝑛 + 1 = π‘šπ‘Ž π‘₯ T π‘ π‘œπ‘Ÿπ‘‘ π‘˜ + T π‘ π‘œπ‘Ÿπ‘‘ 𝑛 βˆ’ π‘˜ π‘˜ ← [0. . 𝑛]] + πœƒ(𝑛).
The πœƒ(𝑛) term accounts for both the time to evaluate the two list comprehensions and the time to perform the concatenation.
Note, by the way, the use of a list comprehension in a mathematical expression rather than a Haskell one. If list comprehensions are
useful notions in programming, they are useful in mathematics too.
Although not immediately obvious, the worst case occurs when π‘˜ = 0 or π‘˜ = 𝑛. Hence
T π‘ π‘œπ‘Ÿπ‘‘ 0 = πœƒ(1)
T π‘ π‘œπ‘Ÿπ‘‘ 𝑛 + 1 = T π‘ π‘œπ‘Ÿπ‘‘ 𝑛 + πœƒ(𝑛)
With solution T π‘ π‘œπ‘Ÿπ‘‘ 𝑛 = πœƒ(𝑛2). Thus Quicksort is a quadratic algorithm in the worst case. This fact is intrinsic to the
algorithm and has nothing to do with the Haskell expression of it.
Richard Bird
Quicksort achieved its fame for two other reasons, neither of which hold in a purely functional setting.
Firstly, when Quicksort is implemented in terms of arrays rather than lists, the partitioning phase can be performed in place
without using any additional space.
Secondly, the average case performance of Quicksort, under reasonable assumptions about the input, is πœƒ(𝑛 log 𝑛) with a
smallish constant of proportionality. In a functional setting this constant is not so small and there are better ways to sort than
Quicksort.
With this warning, let us now see what we can do to optimize the algorithm without changing it in any essential way (i.e. to a
completely different sorting algorithm).
To avoid the two traversals of the list in the partitioning process, define
partition p xs = (filter p xs, filter (not . p) xs)
This is another example of tupling two definitions to save on a traversal. Since filter p can be expressed as an instance of
foldr we can appeal to the tupling law of foldr to arrive at
partition p = foldr op ([],[])
where op x (ys,zs) | p x = (x:ys,zs)
| otherwise = (ys,x:zs))
Now we can write
sort [] = []
sort (x:xs) = sort ys ++ [x] ++ sort zs
where (ys,zs) = partition (< x) xs
But this program still contains a space leak.
partition p = foldr f ([],[])
where f y (ys,zs) = if p y then (y:ys,zs) else (ys,y:zs))
Having rewritten the definition of partition, it may be thought that the program for quicksort is now as space
efficient as possible, but unfortunately it is not.
For some inputs of length 𝑛 the space required by the program is Ξ©(𝑛2).
This situation is referred to as a space leak. A space leak is a hidden loss of space efficiency. The space leak in the
above program for quicksort occurs with an input which is already sorted, but in decreasing order.
Richard Bird
In his earlier book, Richard Bird elaborates a bit on partition’s space leak.
@philip_schwarz
Richard Bird
To see why, let us write the recursive case in the equivalent form
sort (x:xs) = sort (fst p) ++ [x] ++ sort (snd p)
where p = partition (< x) xs
Suppose x:xs has length 𝑛 + 1 and is in strictly decreasing order, so x is the largest element in the list and p is a pair of lists of
length 𝑛 and 0, respectively. Evaluation of p is triggered by displaying the results of the first recursive call, but the 𝑛 units of space
occupied by the first component of p cannot be reclaimed because there is another reference to p in the second recursive call.
Between these two calls further pairs of lists are generated and retained. All in all, the total space required to evaluate sort on a
strictly decreasing list of length 𝑛 + 1 is πœƒ(𝑛2) units. In practice this means the evaluation of sort on some large inputs can abort
owing to a lack of sufficient space.
The solution is to force evaluation of partition and, equally importantly, to bind ys and zs to the components of the pair, not
to p itself.
One way of bringing about a happy outcome is to introduce two accumulating parameters. Define sortp by
sortp x xs us vs = sort (us ++ ys) ++ [x] ++ sort (vs ++ zs)
where (ys,zs) = partition (< x) xs
Then we have
sort (x:xs) = sortp x xs [] []
We now synthesise a direct recursive definition of sortp. The base case is
sortp x [] us vs = sort us ++ [x] ++ sort vs
Richard Bird
For the recursive case y:xs let us assume that y < x. Then
sortp x (y:xs) us vs
= { definition of sortp with (ys,zs) = partition (<x) xs }
sort (us ++ y:ys) ++ [x] ++ sort (vs ++ zs)
= { claim (see below) }
sort (y:us ++ ys) ++ [x] ++ sort (vs ++ zs)
= { definition of sortp }
sortp x xs (y:us) vs
The claim is that if as is any permutation of bs then sort as and sort bs returns the same result. The claim is intuitively
obvious: sorting a list depends only on the elements in the input not their order. A formal proof is omitted.
Carrying out a similar calculation in the case that x <= y and making sortp local to the definition of sort, we arrive at the
final program
sort [] = []
sort (x:xs) = sortp xs [] []
where sortp [] us vs = sort us ++ [x] ++ sort vs
sortp (y:xs) us vs = if y < x
then sortp xs (y:us) vs
else sortp xs us (y:vs)
Not quite as pretty as before, but at least the result has πœƒ(𝑛) space complexity.
We have just seen Richard Bird improve the Haskell Quicksort
program so that rather than requiring Ξ©(𝑛2) space for some
inputs, it now has a space complexity of πœƒ(𝑛).
𝑓 = O 𝑔 𝑓 is of order at most 𝑔
𝑓 = Ξ© 𝑔 𝑓 is of order at least 𝑔
𝑓 = Θ(𝑔) if 𝑓 = O(𝑔) and 𝑓 = Ξ© 𝑔 𝑓 is of order exactly 𝑔
Next, Richard Bird goes further and rewrites the Quicksort program so that it sorts arrays in place, i.e. without using any additional space.
To do this, he relies on advanced functional programming concepts like the state monad and the state thread monad.
If you are not so familiar with Haskell or functional programming, you might want to skip the next slide, and skim read the one after that.
Richard Bird
Imperative Functional Programming
10.4 The ST monad
The state-thread monad, which resides in the library Control.Monad.ST, is a different kettle of fish entirely from the state
monad, although the kettle itself looks rather similar. Like State s a, you can think of this monad as the type
type ST s a = s -> (a,s)
but with one very important difference: the type variable a cannot be instantiated to specific states, such as Seed or [Int].
Instead it is there only to name the state. Think of s as a label that identifies one particular state thread. All mutable types are
tagged with this thread, so that actions can only affect mutable values in their own state thread.
One kind of mutable value is a program variable. Unlike variables in Haskell, or mathematics for that matter, program variables in
imperative languages can change their values. They can be thought of as references to other variables, and in Haskell they are
entities of type STRef s a. The s means that the reference is local to the state thread s (and no other), and the a is the type of
value being referenced. There are operations, defined in Data.STRef, to create, read from and write to references:
newSTRef :: a -> ST s (STRef s a)
readSTRef :: STRef s a -> ST s a
writeSTRef :: STRef s a -> a -> ST s ()
Here is the beginning of Richard Bird’s explanation of how the
state-thread monad can be used to model program variables.
Richard Bird
Imperative Functional Programming
10.5 Mutable Arrays
It sometimes surprises imperative programmers who meet functional programming for the first time that the emphasis is on lists
as the fundamental data structure rather than arrays. The reason is that most uses of arrays (though not all) depend for their
efficiency on the fact that updates are destructive. Once you update the value of an array at a particular index the old array is lost.
But in functional programming, data structures are persistent and any named structure continues to exist. For instance, insert
x t may insert a new element x into a tree t, but t continues to refer to the original tree, so it had better not be overwritten.
In Haskell a mutable array is an entity of type STArray s i e. The s names the state thread, i the index type and e the element
type. Not every type can be an index; legitimate indices are members of the type Ix. Instances of this class include Int and Char,
things that can be mapped into a contiguous range of integers.
Like STRefs there are operations to create, read from and write to arrays. Without more ado, we consider an example explaining
the actions as we go along. Recall the Quicksort algorithm from Section 7.7:
sort :: (Ord a) => [a] -> [a]
sort [] = []
sort (x:xs) = sort [y | y <- xs, y < x] ++ [x] ++
sort [y | y <- xs, x <= y]
There we said that when Quicksort is implemented in terms of arrays rather than lists, the partitioning phase can be performed in
place without using any additional space. We now have the tools to write just such an algorithm.
Here is How Richard Bird prepares to rewrite the Quicksort
program using the Haskell equivalent of a mutable array.
Let’s skip the actual rewriting.
The next slide shows the rewritten Quicksort program next to the earlier Java program.
The Java program looks simpler, not just because it is not polymorphic (it only handles integers).
@philip_schwarz
public class Arrays {
// OVERVIEW: …
public static void sort (int[ ] a) {
// MODIFIES: a
// EFFECTS: Sorts a[0], …, a[a.length - 1] into ascending order.
if (a == null) return;
quickSort(a, 0, a.length-1); }
private static void quickSort(int[ ] a, int low, int high) {
// REQUIRES: a is not null and 0 <= low & high > a.length
// MODIFIES: a
// EFFECTS: Sorts a[low], a[low+1], …, a[high] into ascending order.
if (low >= high) return;
int mid = partition(a, low, high);
quickSort(a, low, mid);
quickSort(a, mid + 1, high); }
private static int partition(int[ ] a, int i, int j) {
// REQUIRES: a is not null and 0 <= i < j < a.length
// MODIFIES: a
// EFFECTS: Reorders the elements in a into two contiguous groups,
// a[i],…, a[res] and a[res+1],…, a[j], such that each
// element in the second group is at least as large as each
// element of the first group. Returns res.
int x = a[i];
while (true) {
while (a[j] > x) j--;
while (a[i] < x) i++;
if (i < j) { // need to swap
int temp = a[i]; a[i] = a[j]; a[j] = temp;
j--; i++; }
else return j; }
}
}
qsort :: Ord a => [a] -> [a]
qsort xs = runST $
do {xa <- newListArray (0,n-1) xs;
qsortST xa (0,n);
getElems xa}
where n = length xs
qsortST :: Ord a => STArray s Int a ->
(Int,Int) -> ST s ()
qsortST xa (a,b)
| a == b = return ()
| otherwise = do {m <- partition xa (a,b);
qsortST xa (a,m);
qsortST xa (m+1,b)}
partition :: Ord a => STArray s Int a ->
(Int,Int) -> ST s Int
partition xa (a,b)
= do {x <- readArray xa a;
let loop (j,k)
= if j==k
then do {swap xa a (k-1);
return (k-1)}
else do {y <- readArray xa j;
if y < x then loop (j+1,k)
else do {swap xa j (k-1);
loop (j,k-1)}}
in loop (a+1,b)}
swap :: STArray s Int a -> Int -> Int -> ST s ()
swap xa i j = do {v <- readArray xa i;
w <- readArray xa j;
writeArray xa i w;
writeArray xa j v}
That’s all.
I hope you found this slide deck useful.

More Related Content

What's hot

The Functional Programming Triad of Map, Filter and Fold
The Functional Programming Triad of Map, Filter and FoldThe Functional Programming Triad of Map, Filter and Fold
The Functional Programming Triad of Map, Filter and FoldPhilip Schwarz
Β 
Scala 3 by Example - Algebraic Data Types for Domain Driven Design - Part 2
Scala 3 by Example - Algebraic Data Types for Domain Driven Design - Part 2Scala 3 by Example - Algebraic Data Types for Domain Driven Design - Part 2
Scala 3 by Example - Algebraic Data Types for Domain Driven Design - Part 2Philip Schwarz
Β 
Refactoring: A First Example - Martin Fowler’s First Example of Refactoring, ...
Refactoring: A First Example - Martin Fowler’s First Example of Refactoring, ...Refactoring: A First Example - Martin Fowler’s First Example of Refactoring, ...
Refactoring: A First Example - Martin Fowler’s First Example of Refactoring, ...Philip Schwarz
Β 
Computer Graphics in Java and Scala - Part 1
Computer Graphics in Java and Scala - Part 1Computer Graphics in Java and Scala - Part 1
Computer Graphics in Java and Scala - Part 1Philip Schwarz
Β 
Sum and Product Types - The Fruit Salad & Fruit Snack Example - From F# to Ha...
Sum and Product Types -The Fruit Salad & Fruit Snack Example - From F# to Ha...Sum and Product Types -The Fruit Salad & Fruit Snack Example - From F# to Ha...
Sum and Product Types - The Fruit Salad & Fruit Snack Example - From F# to Ha...Philip Schwarz
Β 
Left and Right Folds - Comparison of a mathematical definition and a programm...
Left and Right Folds- Comparison of a mathematical definition and a programm...Left and Right Folds- Comparison of a mathematical definition and a programm...
Left and Right Folds - Comparison of a mathematical definition and a programm...Philip Schwarz
Β 
Applicative Functor
Applicative FunctorApplicative Functor
Applicative FunctorPhilip Schwarz
Β 
If You Think You Can Stay Away from Functional Programming, You Are Wrong
If You Think You Can Stay Away from Functional Programming, You Are WrongIf You Think You Can Stay Away from Functional Programming, You Are Wrong
If You Think You Can Stay Away from Functional Programming, You Are WrongMario Fusco
Β 
Nat, List and Option Monoids - from scratch - Combining and Folding - an example
Nat, List and Option Monoids -from scratch -Combining and Folding -an exampleNat, List and Option Monoids -from scratch -Combining and Folding -an example
Nat, List and Option Monoids - from scratch - Combining and Folding - an examplePhilip Schwarz
Β 
LinkedList vs Arraylist- an in depth look at java.util.LinkedList
LinkedList vs Arraylist- an in depth look at java.util.LinkedListLinkedList vs Arraylist- an in depth look at java.util.LinkedList
LinkedList vs Arraylist- an in depth look at java.util.LinkedListMarcus Biel
Β 
Folding Unfolded - Polyglot FP for Fun and Profit - Haskell and Scala Part 2 ...
Folding Unfolded - Polyglot FP for Fun and Profit - Haskell and Scala Part 2 ...Folding Unfolded - Polyglot FP for Fun and Profit - Haskell and Scala Part 2 ...
Folding Unfolded - Polyglot FP for Fun and Profit - Haskell and Scala Part 2 ...Philip Schwarz
Β 
Scala 3 by Example - Algebraic Data Types for Domain Driven Design - Part 1
Scala 3 by Example - Algebraic Data Types for Domain Driven Design - Part 1Scala 3 by Example - Algebraic Data Types for Domain Driven Design - Part 1
Scala 3 by Example - Algebraic Data Types for Domain Driven Design - Part 1Philip Schwarz
Β 
Scala Talk at FOSDEM 2009
Scala Talk at FOSDEM 2009Scala Talk at FOSDEM 2009
Scala Talk at FOSDEM 2009Martin Odersky
Β 
Exploring ZIO Prelude: The game changer for typeclasses in Scala
Exploring ZIO Prelude: The game changer for typeclasses in ScalaExploring ZIO Prelude: The game changer for typeclasses in Scala
Exploring ZIO Prelude: The game changer for typeclasses in ScalaJorge VΓ‘squez
Β 
Functional Domain Modeling - The ZIO 2 Way
Functional Domain Modeling - The ZIO 2 WayFunctional Domain Modeling - The ZIO 2 Way
Functional Domain Modeling - The ZIO 2 WayDebasish Ghosh
Β 
Http4s, Doobie and Circe: The Functional Web Stack
Http4s, Doobie and Circe: The Functional Web StackHttp4s, Doobie and Circe: The Functional Web Stack
Http4s, Doobie and Circe: The Functional Web StackGaryCoady
Β 
Monad Laws Must be Checked
Monad Laws Must be CheckedMonad Laws Must be Checked
Monad Laws Must be CheckedPhilip Schwarz
Β 
Asynchronous JavaScript Programming with Callbacks & Promises
Asynchronous JavaScript Programming with Callbacks & PromisesAsynchronous JavaScript Programming with Callbacks & Promises
Asynchronous JavaScript Programming with Callbacks & PromisesHΓΉng Nguyα»…n Huy
Β 

What's hot (20)

The Functional Programming Triad of Map, Filter and Fold
The Functional Programming Triad of Map, Filter and FoldThe Functional Programming Triad of Map, Filter and Fold
The Functional Programming Triad of Map, Filter and Fold
Β 
Scala 3 by Example - Algebraic Data Types for Domain Driven Design - Part 2
Scala 3 by Example - Algebraic Data Types for Domain Driven Design - Part 2Scala 3 by Example - Algebraic Data Types for Domain Driven Design - Part 2
Scala 3 by Example - Algebraic Data Types for Domain Driven Design - Part 2
Β 
Refactoring: A First Example - Martin Fowler’s First Example of Refactoring, ...
Refactoring: A First Example - Martin Fowler’s First Example of Refactoring, ...Refactoring: A First Example - Martin Fowler’s First Example of Refactoring, ...
Refactoring: A First Example - Martin Fowler’s First Example of Refactoring, ...
Β 
Computer Graphics in Java and Scala - Part 1
Computer Graphics in Java and Scala - Part 1Computer Graphics in Java and Scala - Part 1
Computer Graphics in Java and Scala - Part 1
Β 
ZIO Queue
ZIO QueueZIO Queue
ZIO Queue
Β 
Sum and Product Types - The Fruit Salad & Fruit Snack Example - From F# to Ha...
Sum and Product Types -The Fruit Salad & Fruit Snack Example - From F# to Ha...Sum and Product Types -The Fruit Salad & Fruit Snack Example - From F# to Ha...
Sum and Product Types - The Fruit Salad & Fruit Snack Example - From F# to Ha...
Β 
Left and Right Folds - Comparison of a mathematical definition and a programm...
Left and Right Folds- Comparison of a mathematical definition and a programm...Left and Right Folds- Comparison of a mathematical definition and a programm...
Left and Right Folds - Comparison of a mathematical definition and a programm...
Β 
Applicative Functor
Applicative FunctorApplicative Functor
Applicative Functor
Β 
If You Think You Can Stay Away from Functional Programming, You Are Wrong
If You Think You Can Stay Away from Functional Programming, You Are WrongIf You Think You Can Stay Away from Functional Programming, You Are Wrong
If You Think You Can Stay Away from Functional Programming, You Are Wrong
Β 
Nat, List and Option Monoids - from scratch - Combining and Folding - an example
Nat, List and Option Monoids -from scratch -Combining and Folding -an exampleNat, List and Option Monoids -from scratch -Combining and Folding -an example
Nat, List and Option Monoids - from scratch - Combining and Folding - an example
Β 
LinkedList vs Arraylist- an in depth look at java.util.LinkedList
LinkedList vs Arraylist- an in depth look at java.util.LinkedListLinkedList vs Arraylist- an in depth look at java.util.LinkedList
LinkedList vs Arraylist- an in depth look at java.util.LinkedList
Β 
Folding Unfolded - Polyglot FP for Fun and Profit - Haskell and Scala Part 2 ...
Folding Unfolded - Polyglot FP for Fun and Profit - Haskell and Scala Part 2 ...Folding Unfolded - Polyglot FP for Fun and Profit - Haskell and Scala Part 2 ...
Folding Unfolded - Polyglot FP for Fun and Profit - Haskell and Scala Part 2 ...
Β 
Scala 3 by Example - Algebraic Data Types for Domain Driven Design - Part 1
Scala 3 by Example - Algebraic Data Types for Domain Driven Design - Part 1Scala 3 by Example - Algebraic Data Types for Domain Driven Design - Part 1
Scala 3 by Example - Algebraic Data Types for Domain Driven Design - Part 1
Β 
Scala Talk at FOSDEM 2009
Scala Talk at FOSDEM 2009Scala Talk at FOSDEM 2009
Scala Talk at FOSDEM 2009
Β 
Groovy Programming Language
Groovy Programming LanguageGroovy Programming Language
Groovy Programming Language
Β 
Exploring ZIO Prelude: The game changer for typeclasses in Scala
Exploring ZIO Prelude: The game changer for typeclasses in ScalaExploring ZIO Prelude: The game changer for typeclasses in Scala
Exploring ZIO Prelude: The game changer for typeclasses in Scala
Β 
Functional Domain Modeling - The ZIO 2 Way
Functional Domain Modeling - The ZIO 2 WayFunctional Domain Modeling - The ZIO 2 Way
Functional Domain Modeling - The ZIO 2 Way
Β 
Http4s, Doobie and Circe: The Functional Web Stack
Http4s, Doobie and Circe: The Functional Web StackHttp4s, Doobie and Circe: The Functional Web Stack
Http4s, Doobie and Circe: The Functional Web Stack
Β 
Monad Laws Must be Checked
Monad Laws Must be CheckedMonad Laws Must be Checked
Monad Laws Must be Checked
Β 
Asynchronous JavaScript Programming with Callbacks & Promises
Asynchronous JavaScript Programming with Callbacks & PromisesAsynchronous JavaScript Programming with Callbacks & Promises
Asynchronous JavaScript Programming with Callbacks & Promises
Β 

Similar to Quicksort - a whistle-stop tour of the algorithm in five languages and four paradigms

Why Haskell Matters
Why Haskell MattersWhy Haskell Matters
Why Haskell Mattersromanandreg
Β 
Functional Programming by Examples using Haskell
Functional Programming by Examples using HaskellFunctional Programming by Examples using Haskell
Functional Programming by Examples using Haskellgoncharenko
Β 
(How) can we benefit from adopting scala?
(How) can we benefit from adopting scala?(How) can we benefit from adopting scala?
(How) can we benefit from adopting scala?Tomasz Wrobel
Β 
Sequence and Traverse - Part 3
Sequence and Traverse - Part 3Sequence and Traverse - Part 3
Sequence and Traverse - Part 3Philip Schwarz
Β 
Introduction to R programming
Introduction to R programmingIntroduction to R programming
Introduction to R programmingAlberto Labarga
Β 
Introduction to Functional Languages
Introduction to Functional LanguagesIntroduction to Functional Languages
Introduction to Functional Languagessuthi
Β 
Introduction to parallel and distributed computation with spark
Introduction to parallel and distributed computation with sparkIntroduction to parallel and distributed computation with spark
Introduction to parallel and distributed computation with sparkAngelo Leto
Β 
Functional programming with Scala
Functional programming with ScalaFunctional programming with Scala
Functional programming with ScalaNeelkanth Sachdeva
Β 
Real Time Big Data Management
Real Time Big Data ManagementReal Time Big Data Management
Real Time Big Data ManagementAlbert Bifet
Β 
Pune Clojure Course Outline
Pune Clojure Course OutlinePune Clojure Course Outline
Pune Clojure Course OutlineBaishampayan Ghose
Β 
Beginning Scala Svcc 2009
Beginning Scala Svcc 2009Beginning Scala Svcc 2009
Beginning Scala Svcc 2009David Pollak
Β 
Functions in advanced programming
Functions in advanced programmingFunctions in advanced programming
Functions in advanced programmingVisnuDharsini
Β 
Loops and functions in r
Loops and functions in rLoops and functions in r
Loops and functions in rmanikanta361
Β 
Scala: Functioneel programmeren in een object georiΓ«nteerde wereld
Scala: Functioneel programmeren in een object georiΓ«nteerde wereldScala: Functioneel programmeren in een object georiΓ«nteerde wereld
Scala: Functioneel programmeren in een object georiΓ«nteerde wereldWerner Hofstra
Β 
Functional Programming With Scala
Functional Programming With ScalaFunctional Programming With Scala
Functional Programming With ScalaKnoldus Inc.
Β 

Similar to Quicksort - a whistle-stop tour of the algorithm in five languages and four paradigms (20)

Why Haskell Matters
Why Haskell MattersWhy Haskell Matters
Why Haskell Matters
Β 
Functional Programming by Examples using Haskell
Functional Programming by Examples using HaskellFunctional Programming by Examples using Haskell
Functional Programming by Examples using Haskell
Β 
ScalaBlitz
ScalaBlitzScalaBlitz
ScalaBlitz
Β 
(How) can we benefit from adopting scala?
(How) can we benefit from adopting scala?(How) can we benefit from adopting scala?
(How) can we benefit from adopting scala?
Β 
Sequence and Traverse - Part 3
Sequence and Traverse - Part 3Sequence and Traverse - Part 3
Sequence and Traverse - Part 3
Β 
Introduction to R programming
Introduction to R programmingIntroduction to R programming
Introduction to R programming
Β 
Introduction to Functional Languages
Introduction to Functional LanguagesIntroduction to Functional Languages
Introduction to Functional Languages
Β 
Introduction to parallel and distributed computation with spark
Introduction to parallel and distributed computation with sparkIntroduction to parallel and distributed computation with spark
Introduction to parallel and distributed computation with spark
Β 
Spark workshop
Spark workshopSpark workshop
Spark workshop
Β 
Functional programming with Scala
Functional programming with ScalaFunctional programming with Scala
Functional programming with Scala
Β 
Real Time Big Data Management
Real Time Big Data ManagementReal Time Big Data Management
Real Time Big Data Management
Β 
Pune Clojure Course Outline
Pune Clojure Course OutlinePune Clojure Course Outline
Pune Clojure Course Outline
Β 
Scala in Places API
Scala in Places APIScala in Places API
Scala in Places API
Β 
Beginning Scala Svcc 2009
Beginning Scala Svcc 2009Beginning Scala Svcc 2009
Beginning Scala Svcc 2009
Β 
Functions in advanced programming
Functions in advanced programmingFunctions in advanced programming
Functions in advanced programming
Β 
Loops and functions in r
Loops and functions in rLoops and functions in r
Loops and functions in r
Β 
Scala: Functioneel programmeren in een object georiΓ«nteerde wereld
Scala: Functioneel programmeren in een object georiΓ«nteerde wereldScala: Functioneel programmeren in een object georiΓ«nteerde wereld
Scala: Functioneel programmeren in een object georiΓ«nteerde wereld
Β 
Functional Programming With Scala
Functional Programming With ScalaFunctional Programming With Scala
Functional Programming With Scala
Β 
A bit about Scala
A bit about ScalaA bit about Scala
A bit about Scala
Β 
Comparing JVM languages
Comparing JVM languagesComparing JVM languages
Comparing JVM languages
Β 

More from Philip Schwarz

Folding Cheat Sheet #4 - fourth in a series
Folding Cheat Sheet #4 - fourth in a seriesFolding Cheat Sheet #4 - fourth in a series
Folding Cheat Sheet #4 - fourth in a seriesPhilip Schwarz
Β 
Folding Cheat Sheet #3 - third in a series
Folding Cheat Sheet #3 - third in a seriesFolding Cheat Sheet #3 - third in a series
Folding Cheat Sheet #3 - third in a seriesPhilip Schwarz
Β 
Folding Cheat Sheet #2 - second in a series
Folding Cheat Sheet #2 - second in a seriesFolding Cheat Sheet #2 - second in a series
Folding Cheat Sheet #2 - second in a seriesPhilip Schwarz
Β 
Folding Cheat Sheet #1 - first in a series
Folding Cheat Sheet #1 - first in a seriesFolding Cheat Sheet #1 - first in a series
Folding Cheat Sheet #1 - first in a seriesPhilip Schwarz
Β 
Scala Left Fold Parallelisation - Three Approaches
Scala Left Fold Parallelisation- Three ApproachesScala Left Fold Parallelisation- Three Approaches
Scala Left Fold Parallelisation - Three ApproachesPhilip Schwarz
Β 
Tagless Final Encoding - Algebras and Interpreters and also Programs
Tagless Final Encoding - Algebras and Interpreters and also ProgramsTagless Final Encoding - Algebras and Interpreters and also Programs
Tagless Final Encoding - Algebras and Interpreters and also ProgramsPhilip Schwarz
Β 
Fusing Transformations of Strict Scala Collections with Views
Fusing Transformations of Strict Scala Collections with ViewsFusing Transformations of Strict Scala Collections with Views
Fusing Transformations of Strict Scala Collections with ViewsPhilip Schwarz
Β 
A sighting of traverse_ function in Practical FP in Scala
A sighting of traverse_ function in Practical FP in ScalaA sighting of traverse_ function in Practical FP in Scala
A sighting of traverse_ function in Practical FP in ScalaPhilip Schwarz
Β 
A sighting of traverseFilter and foldMap in Practical FP in Scala
A sighting of traverseFilter and foldMap in Practical FP in ScalaA sighting of traverseFilter and foldMap in Practical FP in Scala
A sighting of traverseFilter and foldMap in Practical FP in ScalaPhilip Schwarz
Β 
A sighting of sequence function in Practical FP in Scala
A sighting of sequence function in Practical FP in ScalaA sighting of sequence function in Practical FP in Scala
A sighting of sequence function in Practical FP in ScalaPhilip Schwarz
Β 
N-Queens Combinatorial Puzzle meets Cats
N-Queens Combinatorial Puzzle meets CatsN-Queens Combinatorial Puzzle meets Cats
N-Queens Combinatorial Puzzle meets CatsPhilip Schwarz
Β 
Kleisli composition, flatMap, join, map, unit - implementation and interrelat...
Kleisli composition, flatMap, join, map, unit - implementation and interrelat...Kleisli composition, flatMap, join, map, unit - implementation and interrelat...
Kleisli composition, flatMap, join, map, unit - implementation and interrelat...Philip Schwarz
Β 
Nat, List and Option Monoids - from scratch - Combining and Folding - an example
Nat, List and Option Monoids -from scratch -Combining and Folding -an exampleNat, List and Option Monoids -from scratch -Combining and Folding -an example
Nat, List and Option Monoids - from scratch - Combining and Folding - an examplePhilip Schwarz
Β 
The Sieve of Eratosthenes - Part II - Genuine versus Unfaithful Sieve - Haske...
The Sieve of Eratosthenes - Part II - Genuine versus Unfaithful Sieve - Haske...The Sieve of Eratosthenes - Part II - Genuine versus Unfaithful Sieve - Haske...
The Sieve of Eratosthenes - Part II - Genuine versus Unfaithful Sieve - Haske...Philip Schwarz
Β 
Jordan Peterson - The pursuit of meaning and related ethical axioms
Jordan Peterson - The pursuit of meaning and related ethical axiomsJordan Peterson - The pursuit of meaning and related ethical axioms
Jordan Peterson - The pursuit of meaning and related ethical axiomsPhilip Schwarz
Β 
Defining filter using (a) recursion (b) folding (c) folding with S, B and I c...
Defining filter using (a) recursion (b) folding (c) folding with S, B and I c...Defining filter using (a) recursion (b) folding (c) folding with S, B and I c...
Defining filter using (a) recursion (b) folding (c) folding with S, B and I c...Philip Schwarz
Β 
Defining filter using (a) recursion (b) folding with S, B and I combinators (...
Defining filter using (a) recursion (b) folding with S, B and I combinators (...Defining filter using (a) recursion (b) folding with S, B and I combinators (...
Defining filter using (a) recursion (b) folding with S, B and I combinators (...Philip Schwarz
Β 
The Sieve of Eratosthenes - Part 1 - with minor corrections
The Sieve of Eratosthenes - Part 1 - with minor correctionsThe Sieve of Eratosthenes - Part 1 - with minor corrections
The Sieve of Eratosthenes - Part 1 - with minor correctionsPhilip Schwarz
Β 
The Sieve of Eratosthenes - Part 1
The Sieve of Eratosthenes - Part 1The Sieve of Eratosthenes - Part 1
The Sieve of Eratosthenes - Part 1Philip Schwarz
Β 
Computer Graphics in Java and Scala - Part 1b
Computer Graphics in Java and Scala - Part 1bComputer Graphics in Java and Scala - Part 1b
Computer Graphics in Java and Scala - Part 1bPhilip Schwarz
Β 

More from Philip Schwarz (20)

Folding Cheat Sheet #4 - fourth in a series
Folding Cheat Sheet #4 - fourth in a seriesFolding Cheat Sheet #4 - fourth in a series
Folding Cheat Sheet #4 - fourth in a series
Β 
Folding Cheat Sheet #3 - third in a series
Folding Cheat Sheet #3 - third in a seriesFolding Cheat Sheet #3 - third in a series
Folding Cheat Sheet #3 - third in a series
Β 
Folding Cheat Sheet #2 - second in a series
Folding Cheat Sheet #2 - second in a seriesFolding Cheat Sheet #2 - second in a series
Folding Cheat Sheet #2 - second in a series
Β 
Folding Cheat Sheet #1 - first in a series
Folding Cheat Sheet #1 - first in a seriesFolding Cheat Sheet #1 - first in a series
Folding Cheat Sheet #1 - first in a series
Β 
Scala Left Fold Parallelisation - Three Approaches
Scala Left Fold Parallelisation- Three ApproachesScala Left Fold Parallelisation- Three Approaches
Scala Left Fold Parallelisation - Three Approaches
Β 
Tagless Final Encoding - Algebras and Interpreters and also Programs
Tagless Final Encoding - Algebras and Interpreters and also ProgramsTagless Final Encoding - Algebras and Interpreters and also Programs
Tagless Final Encoding - Algebras and Interpreters and also Programs
Β 
Fusing Transformations of Strict Scala Collections with Views
Fusing Transformations of Strict Scala Collections with ViewsFusing Transformations of Strict Scala Collections with Views
Fusing Transformations of Strict Scala Collections with Views
Β 
A sighting of traverse_ function in Practical FP in Scala
A sighting of traverse_ function in Practical FP in ScalaA sighting of traverse_ function in Practical FP in Scala
A sighting of traverse_ function in Practical FP in Scala
Β 
A sighting of traverseFilter and foldMap in Practical FP in Scala
A sighting of traverseFilter and foldMap in Practical FP in ScalaA sighting of traverseFilter and foldMap in Practical FP in Scala
A sighting of traverseFilter and foldMap in Practical FP in Scala
Β 
A sighting of sequence function in Practical FP in Scala
A sighting of sequence function in Practical FP in ScalaA sighting of sequence function in Practical FP in Scala
A sighting of sequence function in Practical FP in Scala
Β 
N-Queens Combinatorial Puzzle meets Cats
N-Queens Combinatorial Puzzle meets CatsN-Queens Combinatorial Puzzle meets Cats
N-Queens Combinatorial Puzzle meets Cats
Β 
Kleisli composition, flatMap, join, map, unit - implementation and interrelat...
Kleisli composition, flatMap, join, map, unit - implementation and interrelat...Kleisli composition, flatMap, join, map, unit - implementation and interrelat...
Kleisli composition, flatMap, join, map, unit - implementation and interrelat...
Β 
Nat, List and Option Monoids - from scratch - Combining and Folding - an example
Nat, List and Option Monoids -from scratch -Combining and Folding -an exampleNat, List and Option Monoids -from scratch -Combining and Folding -an example
Nat, List and Option Monoids - from scratch - Combining and Folding - an example
Β 
The Sieve of Eratosthenes - Part II - Genuine versus Unfaithful Sieve - Haske...
The Sieve of Eratosthenes - Part II - Genuine versus Unfaithful Sieve - Haske...The Sieve of Eratosthenes - Part II - Genuine versus Unfaithful Sieve - Haske...
The Sieve of Eratosthenes - Part II - Genuine versus Unfaithful Sieve - Haske...
Β 
Jordan Peterson - The pursuit of meaning and related ethical axioms
Jordan Peterson - The pursuit of meaning and related ethical axiomsJordan Peterson - The pursuit of meaning and related ethical axioms
Jordan Peterson - The pursuit of meaning and related ethical axioms
Β 
Defining filter using (a) recursion (b) folding (c) folding with S, B and I c...
Defining filter using (a) recursion (b) folding (c) folding with S, B and I c...Defining filter using (a) recursion (b) folding (c) folding with S, B and I c...
Defining filter using (a) recursion (b) folding (c) folding with S, B and I c...
Β 
Defining filter using (a) recursion (b) folding with S, B and I combinators (...
Defining filter using (a) recursion (b) folding with S, B and I combinators (...Defining filter using (a) recursion (b) folding with S, B and I combinators (...
Defining filter using (a) recursion (b) folding with S, B and I combinators (...
Β 
The Sieve of Eratosthenes - Part 1 - with minor corrections
The Sieve of Eratosthenes - Part 1 - with minor correctionsThe Sieve of Eratosthenes - Part 1 - with minor corrections
The Sieve of Eratosthenes - Part 1 - with minor corrections
Β 
The Sieve of Eratosthenes - Part 1
The Sieve of Eratosthenes - Part 1The Sieve of Eratosthenes - Part 1
The Sieve of Eratosthenes - Part 1
Β 
Computer Graphics in Java and Scala - Part 1b
Computer Graphics in Java and Scala - Part 1bComputer Graphics in Java and Scala - Part 1b
Computer Graphics in Java and Scala - Part 1b
Β 

Recently uploaded

DNT_Corporate presentation know about us
DNT_Corporate presentation know about usDNT_Corporate presentation know about us
DNT_Corporate presentation know about usDynamic Netsoft
Β 
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsUnveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsAlberto GonzΓ‘lez Trastoy
Β 
Software Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsSoftware Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsArshad QA
Β 
Diamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionDiamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionSolGuruz
Β 
Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...OnePlan Solutions
Β 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVshikhaohhpro
Β 
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...MyIntelliSource, Inc.
Β 
Active Directory Penetration Testing, cionsystems.com.pdf
Active Directory Penetration Testing, cionsystems.com.pdfActive Directory Penetration Testing, cionsystems.com.pdf
Active Directory Penetration Testing, cionsystems.com.pdfCionsystems
Β 
A Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxA Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxComplianceQuest1
Β 
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfLearn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfkalichargn70th171
Β 
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...panagenda
Β 
CHEAP Call Girls in Pushp Vihar (-DELHI )πŸ” 9953056974πŸ”(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )πŸ” 9953056974πŸ”(=)/CALL GIRLS SERVICECHEAP Call Girls in Pushp Vihar (-DELHI )πŸ” 9953056974πŸ”(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )πŸ” 9953056974πŸ”(=)/CALL GIRLS SERVICE9953056974 Low Rate Call Girls In Saket, Delhi NCR
Β 
TECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providerTECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providermohitmore19
Β 
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...kellynguyen01
Β 
Right Money Management App For Your Financial Goals
Right Money Management App For Your Financial GoalsRight Money Management App For Your Financial Goals
Right Money Management App For Your Financial GoalsJhone kinadey
Β 
5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdfWave PLM
Β 
HR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comHR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comFatema Valibhai
Β 
Test Automation Strategy for Frontend and Backend
Test Automation Strategy for Frontend and BackendTest Automation Strategy for Frontend and Backend
Test Automation Strategy for Frontend and BackendArshad QA
Β 

Recently uploaded (20)

DNT_Corporate presentation know about us
DNT_Corporate presentation know about usDNT_Corporate presentation know about us
DNT_Corporate presentation know about us
Β 
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsUnveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Β 
Software Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsSoftware Quality Assurance Interview Questions
Software Quality Assurance Interview Questions
Β 
Diamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionDiamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with Precision
Β 
Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...
Β 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTV
Β 
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Β 
Call Girls In Mukherjee Nagar πŸ“± 9999965857 🀩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
Call Girls In Mukherjee Nagar πŸ“±  9999965857  🀩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...Call Girls In Mukherjee Nagar πŸ“±  9999965857  🀩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
Call Girls In Mukherjee Nagar πŸ“± 9999965857 🀩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
Β 
Active Directory Penetration Testing, cionsystems.com.pdf
Active Directory Penetration Testing, cionsystems.com.pdfActive Directory Penetration Testing, cionsystems.com.pdf
Active Directory Penetration Testing, cionsystems.com.pdf
Β 
A Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxA Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docx
Β 
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfLearn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Β 
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
Β 
CHEAP Call Girls in Pushp Vihar (-DELHI )πŸ” 9953056974πŸ”(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )πŸ” 9953056974πŸ”(=)/CALL GIRLS SERVICECHEAP Call Girls in Pushp Vihar (-DELHI )πŸ” 9953056974πŸ”(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )πŸ” 9953056974πŸ”(=)/CALL GIRLS SERVICE
Β 
TECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providerTECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service provider
Β 
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Β 
Right Money Management App For Your Financial Goals
Right Money Management App For Your Financial GoalsRight Money Management App For Your Financial Goals
Right Money Management App For Your Financial Goals
Β 
5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf
Β 
HR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comHR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.com
Β 
Vip Call Girls Noida ➑️ Delhi ➑️ 9999965857 No Advance 24HRS Live
Vip Call Girls Noida ➑️ Delhi ➑️ 9999965857 No Advance 24HRS LiveVip Call Girls Noida ➑️ Delhi ➑️ 9999965857 No Advance 24HRS Live
Vip Call Girls Noida ➑️ Delhi ➑️ 9999965857 No Advance 24HRS Live
Β 
Test Automation Strategy for Frontend and Backend
Test Automation Strategy for Frontend and BackendTest Automation Strategy for Frontend and Backend
Test Automation Strategy for Frontend and Backend
Β 

Quicksort - a whistle-stop tour of the algorithm in five languages and four paradigms

  • 1. Quicksort Languages: Haskell, Scala, Java, Clojure, Prolog Due credit must be paid to the genius of the designers of ALGOL 60 who included recursion in their language and enabled me to describe my invention [Quicksort] so elegantly to the world. Ask a professional computer scientist or programmer to list their top 10 algorithms, and you’ll find Quicksort on many lists, including mine. … On the aesthetic side, Quicksort is just a remarkably beautiful algorithm, with an equally beautiful running time analysis. Programming Paradigms: Functional, Logic, Imperative, Imperative Functional Tim Roughgarden a whistle-stop tour of the algorithm in five languages and four paradigms @philip_schwarz slides by https://www.slideshare.net/pjschwarz C. Anthony R. Hoare Graham Hutton @haskellhutt Richard Bird Barbara Liskov @algo_class
  • 2. Graham Hutton @haskellhutt … This function produces a sorted version of any list of numbers. The first equation for qsort states that the empty list is already sorted, while the second states that any non-empty list can be sorted by inserting the first number between the two lists that result from sorting the remaining numbers that are smaller and larger than this number. This method of sorting is called quicksort, and is one of the best such methods known. The above implementation of quicksort is an excellent example of the power of Haskell, being both clear and concise. Moreover, the function qsort is also more general than might be expected, being applicable not just with numbers, but with any type of ordered values. More precisely, the type qsort :: Ord a => [a] -> [a] states that, for any type a of ordered values, qsort is a function that maps between lists of such values. Haskell supports many different types of ordered values, including numbers, single characters such as ’a’, and strings of characters such as "abcde". Hence, for example, the function qsort could also be used to sort a list of characters, or a list of strings. qsort :: Ord a => [a] -> [a] qsort [] = [] qsort (x:xs) = qsort smaller ++ [x] ++ qsort larger where smaller = [a | a <- xs, a <= x] larger = [b | b <- xs, b > x ] Sorting values Now let us consider a more sophisticated function concerning lists, which illustrates a number of other aspects of Haskell. Suppose that we define a function called qsort by the following two equations: The empty list is already sorted, and any non-empty list can be sorted by placing its head between the two lists that result from sorting those elements of its tail that are smaller and larger than the head
  • 3. Graham Hutton @haskellhutt What I wanted to show you today, is what Haskell looks like, because it looks quite unlike any other language that you probably have seen. The program here is very concise, it is only five lines long, there is essentially no junk syntax here, it would be hard to think of eliminating any of the symbols here, and this is in contrast to what you find in most other programming languages. If you have seen sorting algorithms like Quicksort before, maybe in C, maybe in Java, maybe in an other language, it is probably going to be much longer than this version. So for me, writing a program like this in Haskell catches the essence of the Quicksort algorithm. Functional Programming in Haskell - FP 3 - Introduction The point here at the bottom, it is quite a bold statement, but I actually do believe it is true, this is probably the simplest implementation of Quicksort in any programming language. If you can show me a simpler one than this, I’ll be very interested to see it, but I don’t really see what you can take out of this program and actually still have the Quicksort algorithm. qsort :: Ord a => [a] -> [a] qsort [] = [] qsort (x:xs) = qsort smaller ++ [x] ++ qsort larger where smaller = [a | a <- xs, a <= x] larger = [b | b <- xs, b > x ] This is probably the simplest implementation of Quicksort in any programming language! Functional Programming in Haskell - FP 8 – Recursive Functions
  • 4. def qsort[A:Ordering](as: List[A]): List[A] = as match case Nil => Nil case x::xs => val smaller = for a <- xs if a <= x yield a val larger = for b <- xs if b > x yield b qsort(smaller) ++ List(x) ++ qsort(larger) qsort :: Ord a => [a] -> [a] qsort [] = [] qsort (x:xs) = qsort smaller ++ [x] ++ qsort larger where smaller = [a | a <- xs, a <= x] larger = [b | b <- xs, b > x ] (defn qsort [elements] (if (empty? elements) elements (let [[x & xs] elements smaller (for [a xs :when (<= a x)] a) larger (for [b xs :when (> b x)] b)] (concat (qsort smaller) (list x) (qsort larger))))) Here is the Haskell qsort function again, together with the equivalent Scala and Clojure functions.
  • 5. given Ordering[RGB] with def compare(x: RGB, y: RGB): Int = x.ordinal compare y.ordinal data RGB = Red | Green | Blue deriving(Eq,Ord,Show) enum RGB : case Red, Green, Blue TestCase (assertEqual "sort integers" [1,2,3,3,4,5] (qsort [5,1,3,2,4,3])) TestCase (assertEqual "sort doubles" [1.1,2.3,3.4,3.4,4.5,5.2] (qsort [5.2,1.1,3.4,2.3,4.5,3.4])) TestCase (assertEqual "sort chars" "abccde" (qsort "acbecd")) TestCase (assertEqual "sort strings" ["abc","efg","uvz"] (qsort ["abc","uvz","efg"]) ) TestCase (assertEqual "sort colours" [Red,Green,Blue] (qsort [Blue,Green,Red])) assert(qsort(List(5,1,2,4,3)) == List(1,2,3,4,5)) assert(qsort(List(5.2,1.1,3.4,2.3,4.5,3.4)) == List(1.1,2.3,3.4,3.4,4.5,5.2)) assert(qsort(List(Blue,Green,Red)) == List(Red,Green,Blue)) assert(qsort("acbecd".toList) == "abccde".toList) assert(qsort(List ("abc","uvz","efg")) == List("abc","efg","uvz")) And here are some Haskell and Scala tests for qsort.
  • 6. qsort :: Ord a => [a] -> [a] qsort [] = [] qsort (x:xs) = qsort smaller ++ [x] ++ qsort larger where smaller = filter (x >) xs larger = filter (x <=) xs qsort :: Ord a => [a] -> [a] qsort [] = [] qsort (x:xs) = qsort smaller ++ [x] ++ qsort larger where smaller = [a | a <- xs, a <= x] larger = [b | b <- xs, b > x ] def qsort[A:Ordering](as: List[A]): List[A] = as match case Nil => Nil case x::xs => val smaller = for a <- xs if a <= x yield a val larger = for b <- xs if b > x yield b qsort(smaller) ++ List(x) ++ qsort(larger) (defn qsort [elements] (if (empty? elements) elements (let [[x & xs] elements smaller (for [a xs :when (<= a x)] a) larger (for [b xs :when (> b x)] b)] (concat (qsort smaller) (list x) (qsort larger))))) (defn qsort [elements] (if (empty? elements) elements (let [[x & xs] elements smaller (filter #(<= % x) xs) larger (filter #(> % x) xs)] (concat (qsort smaller) (list x) (qsort larger))))) def qsort[A:Ordering](as: List[A]): List[A] = as match case Nil => Nil case x::xs => val smaller = xs filter (_ <= x) val larger = xs filter (_ > x) qsort(smaller) ++ List(x) ++ qsort(larger) Let’s use the predefined filter function to make the qsort functions a little bit more succinct.
  • 7. qsort :: Ord a => [a] -> [a] qsort [] = [] qsort (x:xs) = qsort smaller ++ [x] ++ qsort larger where smaller = filter (x >) xs larger = filter (x <=) xs (defn qsort [elements] (if (empty? elements) elements (let [[x & xs] elements smaller (filter #(<= % x) xs) larger (filter #(> % x) xs)] (concat (qsort smaller) (list x) (qsort larger))))) def qsort[A:Ordering](as: List[A]): List[A] = as match case Nil => Nil case x::xs => val smaller = xs filter (_ <= x) val larger = xs filter (_ > x) qsort(smaller) ++ List(x) ++ qsort(larger) Now let’s improve the qsort functions a bit further by using the predefined partition function. def qsort[A:Ordering](as: List[A]): List[A] = as match case Nil => Nil case x::xs => val (smaller,larger) = xs partition (_ <= x) qsort(smaller) ++ List(x) ++ qsort(larger) (defn qsort [elements] (if (empty? elements) elements (let [[x & xs] elements [smaller larger] (split-with #(<= % x) xs)] (concat (qsort smaller) (list x) (qsort larger))))) qsort :: Ord a => [a] -> [a] qsort [] = [] qsort (x:xs) = qsort smaller ++ [x] ++ qsort larger where (smaller,larger) = partition (x >) xs @philip_schwarz
  • 8. qsort( [], [] ). qsort( [X|XS], Sorted ) :- partition(X, XS, Smaller, Larger), qsort(Smaller, SortedSmaller), qsort(Larger, SortedLarger), append(SortedSmaller, [X|SortedLarger], Sorted). partition( _, [], [], [] ). partition( X, [Y|YS], [Y|Smaller], Larger ) :- Y =< X, partition(X, YS, Smaller, Larger). partition( X, [Y|YS], Smaller, [Y|Larger] ) :- Y > X, partition(X, YS, Smaller, Larger). qsort :: Ord a => [a] -> [a] qsort [] = [] qsort (x:xs) = qsort smaller ++ [x] ++ qsort larger where smaller = [a | a <- xs, a <= x] larger = [b | b <- xs, b > x ] Like Haskell, Prolog (the Logic Programming language) is also clear and succinct. Let’s see how a Prolog version of Quicksort compares with the Haskell one.
  • 9. def qsort[A:Ordering](xs: List[A]): List[A] = xs.headOption.fold(Nil){ x => val smaller = xs.tail filter (_ <= x) val larger = xs.tail filter (_ > x) qsort(smaller) ++ List(x) ++ qsort(larger) } private static <T extends Comparable> List<T> qsort(final List<T> xs) { return xs.stream().findFirst().map((final T x) -> { final List<T> smaller = xs.stream().filter(n -> n.compareTo(x) < 0).collect(toList()); final List<T> larger = xs.stream().skip(1).filter(n -> n.compareTo(x) >= 0).collect(toList()); return Stream.of(qsort(smaller), List.of(x), qsort(larger)) .flatMap(Collection::stream) .collect(Collectors.toList()); }).orElseGet(Collections::emptyList); } Here, we first create a variant of the Scala qsort function that uses headOption and fold, and then we have a go at writing a somewhat similar Java function using Streams. def qsort[A:Ordering](as: List[A]): List[A] = as match case Nil => Nil case x::xs => val smaller = xs filter (_ <= x) val larger = xs filter (_ > x) qsort(smaller) ++ List(x) ++ qsort(larger)
  • 10. def qsort[A:Ordering](xs: List[A]): List[A] = xs.headOption.fold(Nil){ x => val (smaller,larger) = xs partition (_ <= x) qsort(smaller) ++ List(x) ++ qsort(larger) } private static <T extends Comparable> List<T> qsort(final List<T> xs) { return xs.stream().findFirst().map((final T x) -> { Map<Boolean, List<T>> partitions = xs.stream().skip(1).collect(Collectors.partitioningBy(n -> n.compareTo(x) < 0)); final List<T> smaller = partitions.get(true); final List<T> larger = partitions.get(false); return Stream.of(qsort(smaller), List.of(x), qsort(larger)) .flatMap(Collection::stream) .collect(Collectors.toList()); }).orElseGet(Collections::emptyList); } Same as the previous slide, but here we use partition rather than filter. def qsort[A:Ordering](as: List[A]): List[A] = as match case Nil => Nil case x::xs => val (smaller,larger) = xs partition (_ <= x) qsort(smaller) ++ List(x) ++ qsort(larger)
  • 11. On the next slide, a brief reminder of the definition of the Quicksort algorithm. @philip_schwarz
  • 12. Description of quicksort Quicksort, like merge sort, applies the divide-and-conquer paradigm. Here is the three-step divide-and-conquer process for sorting a typical subarray A [ p . . r ] : Divide: Partition (rearrange) the array A [p . .r ] into two (possibly empty) subarrays A [ p . . q βˆ’ 1 ] and A [ q + 1 . . r ] such that each element of A [ p . . q - 1 ] is less than or equal to A [ q ], which is, in turn, less than or equal to each element of A [ q + 1 . . r ]. Compute the index q as part of this partitioning procedure. Conquer: Sort the two subarrays A [ p . . q βˆ’ 1 ] and A [ q + 1 . . r ] by recursive calls to quicksort. Combine: Because the subarrays are already sorted, no work is needed to combine them: the entire array A [ p . . r ] is now sorted.
  • 13. The next slide is a reminder of some of the salient aspects of the Quicksort algorithm.
  • 14. The Upshot The famous Quicksort algorithm has three high-level steps: 1. It chooses one element p of the input array to act as a pivot element 2. Its Partition subroutine rearranges the array so that elements smaller than and greater than p come before it and after it, respectively 3. It recursively sorts the two subarrays on either side of the pivot The Partition subroutine can be implemented to run in linear time and in place, meaning with negligible additional memory. As a consequence, Quicksort also runs in place. The correctness of the Quicksort algorithm does not depend on how pivot elements are chosen, but its running time does. The worst-case scenario is a running time of Θ 𝑛2 , where 𝑛 is the length of the input array. This occurs when the input array is already sorted, and the first element is always used as the pivot element. The best-case scenario is a running time of Θ 𝑛 log 𝑛 . This occurs when the median element is always used as the pivot. In randomized Quicksort, the pivot element is always chosen uniformly at random. Its running time can be anywhere from Θ 𝑛 log 𝑛 to Θ 𝑛2 , depending on its random coin flips. The average running time of randomized Quicksort is 𝛩 𝑛 π‘™π‘œπ‘” 𝑛 , only a small constant factor worse than its best-case running time. A comparison-based sorting algorithm is a general-purpose algorithm that accesses the input array only by comparing pairs of elements, and never directly uses the value of an element. No comparison-based sorting algorithm has a worst-case asymptotic running time better than 𝛩 𝑛 π‘™π‘œπ‘” 𝑛 . … Tim Roughgarden @algo_class ChoosePivot Input: array A of 𝑛 distinct integers, left and right endpoints β„“, π‘Ÿ ∈ 1,2, … , 𝑛 . Output: an index 𝑖 ∈ β„“, β„“ + 1, … , π‘Ÿ . Implementation: β€’ NaΓ―ve: return β„“. β€’ Overkill: return position of the median element of A [β„“], … , A [π‘Ÿ] . β€’ Randomized: return an element of β„“, β„“ + 1, … , π‘Ÿ , chosen uniformly, at random.
  • 15. On the next slide we see an imperative (Java) implementation of Quicksort, i.e. one that does the sorting in place.
  • 16. Barbara Liskov public class Arrays { // OVERVIEW: … public static void sort (int[ ] a) { // MODIFIES: a // EFFECTS: Sorts a[0], …, a[a.length - 1] into ascending order. if (a == null) return; quickSort(a, 0, a.length-1); } private static void quickSort(int[ ] a, int low, int high) { // REQUIRES: a is not null and 0 <= low & high > a.length // MODIFIES: a // EFFECTS: Sorts a[low], a[low+1], …, a[high] into ascending order. if (low >= high) return; int mid = partition(a, low, high); quickSort(a, low, mid); quickSort(a, mid + 1, high); } private static int partition(int[ ] a, int i, int j) { // REQUIRES: a is not null and 0 <= i < j < a.length // MODIFIES: a // EFFECTS: Reorders the elements in a into two contiguous groups, // a[i],…, a[res] and a[res+1],…, a[j], such that each // element in the second group is at least as large as each // element of the first group. Returns res. int x = a[i]; while (true) { while (a[j] > x) j--; while (a[i] < x) i++; if (i < j) { // need to swap int temp = a[i]; a[i] = a[j]; a[j] = temp; j--; i++; } else return j; } } } quick sort … partitions the elements of the array into two contiguous groups such that all the elements in the first group are no larger than those in the second group; it continues to partition recursively until the entire array is sorted. To carry out these steps, we use two subsidiary procedures: quickSort, which causes the partitioning of smaller and smaller subparts of the array, and partition, which performs the partitioning of a designated subpart of the array. Note that the quickSort and partition routines are not declared to be public; instead, their use is limited to the Arrays class. This is appropriate because they are just helper routines and have little utility in their own right. Nevertheless, we have provided specifications for them; these specifications are of interest to someone interested in understanding how quickSort is implemented but not to a user of quickSort
  • 17. Yes, that was Barbara Liskov, of Liskov Substitution Principle fame. Now that we have seen both functional and imperative implementations of Quicksort, we go back to the Haskell functional implementation and learn about the following: β€’ Shortcomings of the functional implementation β€’ How the functional implementation can be made more efficient in its use of space. β€’ How the functional implementation can be rewritten in a hybrid imperative functional style. Because the last of the above topics involves fairly advanced functional programming techniques, we are simply going to have a quick, high-level look at it, to whet our appetite and motivate us to find out more.
  • 18. Richard Bird Our second sorting algorithm is a famous one called Quicksort. It can be expressed in just two lines of Haskell: sort :: (Ord a) => [a] -> [a] sort [] = [] sort (x:xs) = sort [y | y <- xs, y < x] ++ [x] ++ sort [y | y <- xs, x <= y] That’s very pretty and a testament to the expressive power of Haskell. But the prettiness comes at a cost: the program can be very inefficient in its use of space. Before plunging into ways the code can be optimized, let’s compute T π‘ π‘œπ‘Ÿπ‘‘ . Suppose we want to sort a list of length 𝑛 + 1. The first list comprehension can return a list of any length π‘˜ from 0 to 𝑛. The length of the result of the second list comprehension is therefore 𝑛 βˆ’ π‘˜. Since our timing function is an estimate of the worst-case running time, we have to take the maximum of these possibilities: T π‘ π‘œπ‘Ÿπ‘‘ 𝑛 + 1 = π‘šπ‘Ž π‘₯ T π‘ π‘œπ‘Ÿπ‘‘ π‘˜ + T π‘ π‘œπ‘Ÿπ‘‘ 𝑛 βˆ’ π‘˜ π‘˜ ← [0. . 𝑛]] + πœƒ(𝑛). The πœƒ(𝑛) term accounts for both the time to evaluate the two list comprehensions and the time to perform the concatenation. Note, by the way, the use of a list comprehension in a mathematical expression rather than a Haskell one. If list comprehensions are useful notions in programming, they are useful in mathematics too. Although not immediately obvious, the worst case occurs when π‘˜ = 0 or π‘˜ = 𝑛. Hence T π‘ π‘œπ‘Ÿπ‘‘ 0 = πœƒ(1) T π‘ π‘œπ‘Ÿπ‘‘ 𝑛 + 1 = T π‘ π‘œπ‘Ÿπ‘‘ 𝑛 + πœƒ(𝑛) With solution T π‘ π‘œπ‘Ÿπ‘‘ 𝑛 = πœƒ(𝑛2). Thus Quicksort is a quadratic algorithm in the worst case. This fact is intrinsic to the algorithm and has nothing to do with the Haskell expression of it.
  • 19. Richard Bird Quicksort achieved its fame for two other reasons, neither of which hold in a purely functional setting. Firstly, when Quicksort is implemented in terms of arrays rather than lists, the partitioning phase can be performed in place without using any additional space. Secondly, the average case performance of Quicksort, under reasonable assumptions about the input, is πœƒ(𝑛 log 𝑛) with a smallish constant of proportionality. In a functional setting this constant is not so small and there are better ways to sort than Quicksort. With this warning, let us now see what we can do to optimize the algorithm without changing it in any essential way (i.e. to a completely different sorting algorithm). To avoid the two traversals of the list in the partitioning process, define partition p xs = (filter p xs, filter (not . p) xs) This is another example of tupling two definitions to save on a traversal. Since filter p can be expressed as an instance of foldr we can appeal to the tupling law of foldr to arrive at partition p = foldr op ([],[]) where op x (ys,zs) | p x = (x:ys,zs) | otherwise = (ys,x:zs)) Now we can write sort [] = [] sort (x:xs) = sort ys ++ [x] ++ sort zs where (ys,zs) = partition (< x) xs But this program still contains a space leak.
  • 20. partition p = foldr f ([],[]) where f y (ys,zs) = if p y then (y:ys,zs) else (ys,y:zs)) Having rewritten the definition of partition, it may be thought that the program for quicksort is now as space efficient as possible, but unfortunately it is not. For some inputs of length 𝑛 the space required by the program is Ξ©(𝑛2). This situation is referred to as a space leak. A space leak is a hidden loss of space efficiency. The space leak in the above program for quicksort occurs with an input which is already sorted, but in decreasing order. Richard Bird In his earlier book, Richard Bird elaborates a bit on partition’s space leak. @philip_schwarz
  • 21. Richard Bird To see why, let us write the recursive case in the equivalent form sort (x:xs) = sort (fst p) ++ [x] ++ sort (snd p) where p = partition (< x) xs Suppose x:xs has length 𝑛 + 1 and is in strictly decreasing order, so x is the largest element in the list and p is a pair of lists of length 𝑛 and 0, respectively. Evaluation of p is triggered by displaying the results of the first recursive call, but the 𝑛 units of space occupied by the first component of p cannot be reclaimed because there is another reference to p in the second recursive call. Between these two calls further pairs of lists are generated and retained. All in all, the total space required to evaluate sort on a strictly decreasing list of length 𝑛 + 1 is πœƒ(𝑛2) units. In practice this means the evaluation of sort on some large inputs can abort owing to a lack of sufficient space. The solution is to force evaluation of partition and, equally importantly, to bind ys and zs to the components of the pair, not to p itself. One way of bringing about a happy outcome is to introduce two accumulating parameters. Define sortp by sortp x xs us vs = sort (us ++ ys) ++ [x] ++ sort (vs ++ zs) where (ys,zs) = partition (< x) xs Then we have sort (x:xs) = sortp x xs [] [] We now synthesise a direct recursive definition of sortp. The base case is sortp x [] us vs = sort us ++ [x] ++ sort vs
  • 22. Richard Bird For the recursive case y:xs let us assume that y < x. Then sortp x (y:xs) us vs = { definition of sortp with (ys,zs) = partition (<x) xs } sort (us ++ y:ys) ++ [x] ++ sort (vs ++ zs) = { claim (see below) } sort (y:us ++ ys) ++ [x] ++ sort (vs ++ zs) = { definition of sortp } sortp x xs (y:us) vs The claim is that if as is any permutation of bs then sort as and sort bs returns the same result. The claim is intuitively obvious: sorting a list depends only on the elements in the input not their order. A formal proof is omitted. Carrying out a similar calculation in the case that x <= y and making sortp local to the definition of sort, we arrive at the final program sort [] = [] sort (x:xs) = sortp xs [] [] where sortp [] us vs = sort us ++ [x] ++ sort vs sortp (y:xs) us vs = if y < x then sortp xs (y:us) vs else sortp xs us (y:vs) Not quite as pretty as before, but at least the result has πœƒ(𝑛) space complexity.
  • 23. We have just seen Richard Bird improve the Haskell Quicksort program so that rather than requiring Ξ©(𝑛2) space for some inputs, it now has a space complexity of πœƒ(𝑛). 𝑓 = O 𝑔 𝑓 is of order at most 𝑔 𝑓 = Ξ© 𝑔 𝑓 is of order at least 𝑔 𝑓 = Θ(𝑔) if 𝑓 = O(𝑔) and 𝑓 = Ξ© 𝑔 𝑓 is of order exactly 𝑔 Next, Richard Bird goes further and rewrites the Quicksort program so that it sorts arrays in place, i.e. without using any additional space. To do this, he relies on advanced functional programming concepts like the state monad and the state thread monad. If you are not so familiar with Haskell or functional programming, you might want to skip the next slide, and skim read the one after that.
  • 24. Richard Bird Imperative Functional Programming 10.4 The ST monad The state-thread monad, which resides in the library Control.Monad.ST, is a different kettle of fish entirely from the state monad, although the kettle itself looks rather similar. Like State s a, you can think of this monad as the type type ST s a = s -> (a,s) but with one very important difference: the type variable a cannot be instantiated to specific states, such as Seed or [Int]. Instead it is there only to name the state. Think of s as a label that identifies one particular state thread. All mutable types are tagged with this thread, so that actions can only affect mutable values in their own state thread. One kind of mutable value is a program variable. Unlike variables in Haskell, or mathematics for that matter, program variables in imperative languages can change their values. They can be thought of as references to other variables, and in Haskell they are entities of type STRef s a. The s means that the reference is local to the state thread s (and no other), and the a is the type of value being referenced. There are operations, defined in Data.STRef, to create, read from and write to references: newSTRef :: a -> ST s (STRef s a) readSTRef :: STRef s a -> ST s a writeSTRef :: STRef s a -> a -> ST s () Here is the beginning of Richard Bird’s explanation of how the state-thread monad can be used to model program variables.
  • 25. Richard Bird Imperative Functional Programming 10.5 Mutable Arrays It sometimes surprises imperative programmers who meet functional programming for the first time that the emphasis is on lists as the fundamental data structure rather than arrays. The reason is that most uses of arrays (though not all) depend for their efficiency on the fact that updates are destructive. Once you update the value of an array at a particular index the old array is lost. But in functional programming, data structures are persistent and any named structure continues to exist. For instance, insert x t may insert a new element x into a tree t, but t continues to refer to the original tree, so it had better not be overwritten. In Haskell a mutable array is an entity of type STArray s i e. The s names the state thread, i the index type and e the element type. Not every type can be an index; legitimate indices are members of the type Ix. Instances of this class include Int and Char, things that can be mapped into a contiguous range of integers. Like STRefs there are operations to create, read from and write to arrays. Without more ado, we consider an example explaining the actions as we go along. Recall the Quicksort algorithm from Section 7.7: sort :: (Ord a) => [a] -> [a] sort [] = [] sort (x:xs) = sort [y | y <- xs, y < x] ++ [x] ++ sort [y | y <- xs, x <= y] There we said that when Quicksort is implemented in terms of arrays rather than lists, the partitioning phase can be performed in place without using any additional space. We now have the tools to write just such an algorithm. Here is How Richard Bird prepares to rewrite the Quicksort program using the Haskell equivalent of a mutable array.
  • 26. Let’s skip the actual rewriting. The next slide shows the rewritten Quicksort program next to the earlier Java program. The Java program looks simpler, not just because it is not polymorphic (it only handles integers). @philip_schwarz
  • 27. public class Arrays { // OVERVIEW: … public static void sort (int[ ] a) { // MODIFIES: a // EFFECTS: Sorts a[0], …, a[a.length - 1] into ascending order. if (a == null) return; quickSort(a, 0, a.length-1); } private static void quickSort(int[ ] a, int low, int high) { // REQUIRES: a is not null and 0 <= low & high > a.length // MODIFIES: a // EFFECTS: Sorts a[low], a[low+1], …, a[high] into ascending order. if (low >= high) return; int mid = partition(a, low, high); quickSort(a, low, mid); quickSort(a, mid + 1, high); } private static int partition(int[ ] a, int i, int j) { // REQUIRES: a is not null and 0 <= i < j < a.length // MODIFIES: a // EFFECTS: Reorders the elements in a into two contiguous groups, // a[i],…, a[res] and a[res+1],…, a[j], such that each // element in the second group is at least as large as each // element of the first group. Returns res. int x = a[i]; while (true) { while (a[j] > x) j--; while (a[i] < x) i++; if (i < j) { // need to swap int temp = a[i]; a[i] = a[j]; a[j] = temp; j--; i++; } else return j; } } } qsort :: Ord a => [a] -> [a] qsort xs = runST $ do {xa <- newListArray (0,n-1) xs; qsortST xa (0,n); getElems xa} where n = length xs qsortST :: Ord a => STArray s Int a -> (Int,Int) -> ST s () qsortST xa (a,b) | a == b = return () | otherwise = do {m <- partition xa (a,b); qsortST xa (a,m); qsortST xa (m+1,b)} partition :: Ord a => STArray s Int a -> (Int,Int) -> ST s Int partition xa (a,b) = do {x <- readArray xa a; let loop (j,k) = if j==k then do {swap xa a (k-1); return (k-1)} else do {y <- readArray xa j; if y < x then loop (j+1,k) else do {swap xa j (k-1); loop (j,k-1)}} in loop (a+1,b)} swap :: STArray s Int a -> Int -> Int -> ST s () swap xa i j = do {v <- readArray xa i; w <- readArray xa j; writeArray xa i w; writeArray xa j v}
  • 28. That’s all. I hope you found this slide deck useful.