-
Prime or not? →
Let’s look at a simple problem if figuring out if a given number is a prime number or not? And how best we can compute it.
A prime number is a whole number greater than 1, that are only divisible by itself and 1.
A naïve approach is to check if the number is divisble by all the numbers starting from 2 to itself. We start from
2
as it is our first prime number.But, can we do better? Notice that we need to check for numbers only less than or equal to the square root, say
sq
of the given numbern
to be tested. If there’s a number that dividesn
and which is greater thansq
, the result of that division would be a number less thansq
. For example let’s sayn = 26
and we want to figure out ifn
is prime. We are claiming that we need to check only until the square root of26
- rounded down to5
. Let’s pick a number that divides26
, say13
and this number on dividing26
yields2
. So when we start to check from2
to5
, we would have already figured out that it is divisible by2
and is not a prime. We just ran into another beautiful fact:there are no even primes greater than 2
That would let us optimize our loop further - if we initially checked whether
n
is not2
, we would check only every other (odd) number to see if it divides.Primes are beautiful. Hopefully, I will write more posts on them in future.
-
Counting the number of rotations of a sorted array →
Let’s look at another simple, interesting problem:
Given a sorted array, with no duplicates, that has been rotated (also called circularly sorted array), find out the number of times it has been rotated
For example,
[2 3 5 8 11 12]
is a sorted list of numbers and if we rotate it twice, we end up in[11 12 2 3 5 8]
. We are required to find the number of times it has been sorted, looking at this list. The straight forward way is to look for the minimum element of the array and the index of the mimimum element gives us the number of times it has been rotated. In this case2
is the minimum element and the index of the mimumum element is2
(indices start at0
in most of the languages we use here). The time complexity would be O(n) as we need to traverse the array looking for the mimimum element.Can we do better? Can we use a nice property of circularly sorted arrays (lists)?
For the element we are looking for, call it the pivot element, both the left and right neighbours are greater than the pivot element. This is the only element in a circularly sorted array with this property.
So, our goal is to find the pivot element. We already know that the array has been sorted - so we can use binary search to look for the pivot element. We compute the middle element of the array, call it
mid
and search in the left and right halves of the middle element. Let’s call the lower indexlow
and higher indexhigh
for the purposes of computation. So, we compute the middle element asmid = (high - low) / 2
. The neighbours of this middle element, calledprev
andnext
are computed asprev = (mid + 1) % n
wheren
is the size of the array (since it is circularly sorted we need to wrap around and hence we do thismodulo n
operation) andnext = (mid + n -1) % n
. Now, ifmid
turns out to be our pivot element, i.e. we check for pivot property, we are done - we just returnmid
as the result. Otherwise, we keep movinglow
andhigh
to decide which half of the array we want to search for the pivot element. Recollect that we know the array is (circularly) sorted and this makes our job easier. For example, if the element atmid
is less than the one athigh
we can deduce that the right half of the array is sorted, and so we adjusthigh
to bemid - 1
(i.e. make left half our focus of pivot element search).Isn’t it interesting? That’s all for now!
-
Maximum pairwise product →
Let’s take a quick look at a simple problem:
Given a list of numbers, how would you determine the maximum pairwise product of numbers in the list?
For example, if we have the list
[1; 3; 5; -2]
the maximum pairwise product is15
, which is the product of3
and5
.The straigt-forward approach is to compute all possible pairwise products and identify the maximum among the intermediate results:
Since we have two loops that iterate over the list of numbers, the time complexity is O(n2).
Can we do better? Yes, we can. The idea is to find the maximum and the second maximum elements in the list - and their product would be the maximum pairwise product.
Simple and sweet!
-
Packing consecutive repetitions in a list into sublists →
In one of our [earlier posts] we saw how to eliminate consecutive repetitions in a list. Let’s refresh our memory and take a look at the
compress
function we saw earlier:This is a straight forward recursive function. Let’s first rewrite it using auxiliary function.
I think it is pretty straight forward - if we see repetition
(x1 = x2)
we ignore the element and call the auxiliary function on the remaining part of the list. (Note that we do not eliminatex2
as we want to retain a copy of each repeating element).Today, we shall not eliminate, but collect the consecutive repeating elements into sublists. For example, if we have the list
[1;1;1;2;2;3;4;5;5;6;1]
we want to make it into this list[[1; 1; 1];[2; 2];[3];[4];[5; 5];[6];[1]]
We will again use recursive calls to auxiliary function, very creatively called
aux
. Apart from keeping track of repetitions, we also need to collect them into sublists. So, in this case, as we traverse the input list and process element by element, we will collect the repeating ones in a sublist and collect the ones that are not consecutive repetitions in another (as this will also be a part of our final result). We know that we usually return the accumulator as the result - so in this case, we may want to maintain two accumulators - onecur
to collect the consecutive repeating elements, and anotheracc
to collect the remaining.- When we encounter repetitions, we will push the repeating entries into
cur
sublist - When there are no repetitions, we will keep merging
cur
withacc
and eventually returnacc
as the result.
In both the
compress
andpack
functions that use auxiliary function, we reverse the result using OCamlList
module’s built-inrev
function.Food for thought - why is the result getting reversed? If you work out a small example, it would become apparent.
That’s all for today. Enjoy your weekend!
- When we encounter repetitions, we will push the repeating entries into
-
Recursions and merge sort →
By now, we are all pretty comfortable with recursive functions, using auxiliary functions, etc. Let’s now put these skills to write merge sort algorithm in a recursive fashion. We need the following three ingredients to prepare our recipe
split
function that would take a list and split it into two listsmerge
function that would merge a pair of sorted listsmerge_sort
function that would use the above two to build the sorted list recursively.
Let’s prepare the above one by one. First, the
split
function that takes a listlst
as input and splits them and returns a pair of lists as output. The moment we see that we need to return two lists (a pair of lists) as output, intuitively we can imagine that we would need two accumulators, let’s call ita1
anda2
. Next, we will think about the possible cases (patterns).- The base case is going to be empty list as input in which case our result would be two empty lists (the two accumulators we use).
- If we pass a list that has a single element, one of the lists we return would contain this single element and the other would be an empty list (remember, we need to return two lists as result).
- If there are multiple elements, like any reasonable list, we would pick the first element and push it into the first accumulator, the second element into the second accumulator and then recursively call the auxiliary function on the rest of the list.
So, this is what it looks like - you can match the three items above to the three patterns in the code fragment below - pretty straight forward.
Now, let’s prepare the second item in our list - the
merge
function. This is just pattern matching and even simpler than oursplit
function.Finally, let’s prepare the merge_sort function that takes a list
lst
as input. First we would split the input list into two lists using oursplit
function. Then, we would recursively callmerge_sort
on the two sub-lists (result ofsplit
function) and merge the results of these recursive calls using ourmerge
function. Again, the base case is the empty list and another possibility is an input list with a single element - both are straight forward.That was a very intuitive way to write a merge sort algorithm. Hope you all agree with me!