Activity 2-5b Refactoring

In this activity we will work with the PrimeGenerator class. Currently this class consists of one big method, and obviously we will want to break it down into smaller pieces. In order to do that, we’ll need to understand the algorithm a bit more.

Step 9: Initial steps

For this file we will take a slightly different approach to what we did in the previous file. We will start by turning all the local variables into fields. This will allow us to freely break the algorithm up into smaller pieces. Then we’ll see where that takes us, and hopefully eliminate some fields or turn them back into local variables.

Before we move on, we need to understand a bit more what this algorithm is doing. In order to check if a number is prime, it will have to check the multiples of primes less than it, but it does not need to worry about all such multiples: If a number is not a prime, then it will have a prime divisor that’s no more than the square root of the number. For example if we want to find out what numbers divide \(30\), we only need to worry about the prime numbers up to \(5\), since the square root of \(30\) is between \(5\) and \(6\). We don’t have to check all the primes up to \(30\), we can stop a lot sooner.

This is what the “nextPrimeSquare” check is all about: ord keeps track of the index of the next possible prime divisor: If a candidate is not prime then it will be a multiple of one of the primes up to index ord. But when we reach the square of the prime that is at location ord, then this prime also becomes a possible factor and therefore we increment ord, and add this entry to the multiples array.

Now let’s go back to our computeAndStoreNextPrime method. The next thing that happens is that the index n starts at 2 and continues until ord, and for each of those indices we increment the multiple[n] entry until it is at least as big as the candidate. The multiple is incremented by adding two times the corresponding prime. What this effectively does is compute k*p for odd numbers k only (since our target is always odd). This process stops if we discover that the candidate does equal one of these multiples, meaning that it is definitely not prime and we should move to the next candidate.

With a small cost in efficiency, we can separate those actions: We can increase the multiples to their next step, namely make them all reach at least the candidate, in one simple loop. Then we can have another loop that checks if any of those multiples equal the candidate. This will take some manual work:

Step 10: More cleanup

Let’s do a bit more cleanup before wrapping up. Remember how we had split up the work of updating the multiples and then checking if they equal the candidate? Let’s see if now that things are a bit more clear we can bring some of it back.

Excellent, now let’s order the methods according to the step down rule. Rearrange the methods in the following order:

Make a commit.

Step 11: From arrays to arraylists

Now that our code is nice and compartmentalized, let’s see if we can use dynamic array lists instead of arrays, at least instead of the multiples array. The advantage is that we would not need the ordmax field at all, and we might be able to even eliminate the ord field. Since the primes array has a predetermined length anyway, we won’t gain much from changing that (other than eliminating the primeIndex method perhaps).

In order to achieve this, the first step is to “encapsulate” access to the array elements behind getter and setter methods:

Now we will perform our transformation steps in a specific order, in order to avoid breaking our tests. So we will do the following (overview of the steps):

Ok so let’s follow these steps one at a time:

Step 12: A class for multiples

If you look through the code now, you will find some weird indexing issues. Part of the reason is that when we think of how to update the value of a multiple, we need to look up in the primes array for the correct prime to use. And since the list and the primes array are indexed differently, it’s a bit awkward.

What we will try to do here is avoid some of these problems by creating a small inner class called Multiples. It is meant to represent an entry in the multiples list, but it remembers the prime that needs to be added each time as well as its current value. The flip side to this is that we’ll need to get the values in and out of that class. But let’s try it out and see how it looks.

As before, we will do this by creating a new list to operate in parallel to the multiplesList. Then we’ll gradually migrate from using one list to using the other.

Now we will reap the benefits of this: We should be able to gradually migrate from using n going from 2 to size+2 to using index going from 0 to size. We will employ a similar idea of gradually adding index handling next to the n handling until we can drop the n.

Ok this is better, but looking at it more, is there really any reason to be passing in the index? Can’t we be passing the elements themselves? Let’s try that out:

There! Isn’t this much better?

Well ok you are right, the methods below look a bit awkward, let’s see what we can do about them.

Take a look at our candidateIsComposite method now. Doesn’t it look nice? Next up, let’s tackle the issue of getOrd. It is now used for a single purpose, to get access to primes[getOrd()] in addNextMultipleEntryIfReachedNextPrimeSquare.

As a final cleanup step, go to our PrimeGenerator constructor and perform the “Move assignment to field declaration” intention on all fields for which it is possible (all but primes and numberOfPrimes).

Also, check your method order for the Stepdown rule, and make sure it is followed.

Step 13: Transforming the primes array

Our last step in this long refactoring is the primes array. We would like to convert its use to an array list. Note that the first prime, 2, is placed at index 1 and also that the only primes used later in the algorithm (as part of the work on multiples) are the odd primes, which start at index 2. With that in mind, we will hold only the odd primes in our list. Then we will convert the result to an array when asked for.

Notice the error we are getting: java.lang.IndexOutOfBoundsException: Index 0 out-of-bounds for length 0. This happens due to a call to nextPrimeFactor. That call is trying to get to the next available prime factor, but none has been created yet!

The solution is simple: We will initialize our list with the first odd prime, and also add it to the primes array, then start our candidatePrime from 3 instead of 1.

And we’re done! We could maybe do something more with the candidate += 2 bit, but this is more than good enough. The code should be easy to read at this point.

Make sure you make a final commit of your work.