views:

237

answers:

7

So I would call myself a fairly novice programmer as I focused mostly on hardware in my schooling and not a lot of Computer Science courses.

So I solved Problem 7 of Project Euler:

By listing the first six prime numbers: 2, 3, 5, 7, 11, and 13, we can see that the 6th prime is 13.

What is the 10001st prime number?

I managed to solve this without problem in Java, but when I ran my solution it took 8 and change seconds to run. I was wondering how this could be optimized from a programming standpoint, not a mathematical standpoint.

Is the array looping and while statements the main things eating up processing time? And how could this be optimized? Again not looking for a fancy mathematical equation..there are plenty of those in the solution thread.

SPOILER My solution is listed below.

public class PrimeNumberList {

private ArrayList<BigInteger> primesList = new ArrayList<BigInteger>();

public void fillList(int numberOfPrimes) {
    primesList.add(new BigInteger("2"));
    primesList.add(new BigInteger("3"));
    while (primesList.size() < numberOfPrimes){
        getNextPrime();
    }
}

private void getNextPrime() {
    BigInteger lastPrime = primesList.get(primesList.size()-1);
    BigInteger currentTestNumber = lastPrime;
    BigInteger modulusResult;
    boolean prime = false;
    while(!prime){
        prime = true;
        currentTestNumber = currentTestNumber.add(new BigInteger("2"));
        for (BigInteger bi : primesList){
            modulusResult = currentTestNumber.mod(bi);
            if (modulusResult.equals(BigInteger.ZERO)){
                prime = false;
                break;
            }
        }
        if(prime){
            primesList.add(currentTestNumber);
        }
    }
}

public BigInteger get(int primeTerm) {
    return primesList.get(primeTerm - 1);
}

}

+10  A: 

Since the 10001st prime number is not that big, you could start by using long instead of BigInteger. A BigInteger instance is a full-fledged Java Object and there is a lot of overhead in creating and manipulating them.

mobrule
Why use a long, when an int is enough?
jarnbjo
I was not sure how big the 10001st prime was going to be until I ran my test.
bmucklow
There's really not a lot of overhead in creating a `BigInteger`. Besides that, he's caching the prime ones for later use, so only the new number under test is being created each time through the while loop. Besides *that*, there will be later problems in ProjectEuler where `int` and even `long` won't be big enough. :)
Bill the Lizard
Actually, I stand corrected on part of that last comment. He is creating an extra `BigInteger` each time he adds 2 in the while loop. But that's still outside the for loop, where most of the time should be spent.
Bill the Lizard
Changing to int reduced it from 8+change to 2+change seconds. However I didn't know at the time that I would only need an int. I am seeing if my original solution can make one static creation of "2" to save any time. Still looking for other answers to optimize the program (not fancy math equations)
bmucklow
So a followup. Changing the snippet "currentTestNumber = currentTestNumber.add(new BigInteger("2"))" to the following: "currentTestNumber = currentTestNumber.add(BIG_INT_TWO)"does not add any time back.
bmucklow
+6  A: 

You can benchmark it for yourself, but I'd guess that the for (BigInteger bi : primesList) loop is where you're spending most of your time. You're looping through the entire list of primes. You can break out of that loop as soon as you reach a prime candidate divisor that's greater than the square root of the number you're testing for primality.

Another (very slight by comparison) improvement will be in caching new BigInteger("2") and reusing it instead of creating a new BigInteger with the same value each time through your while loop. <-- Still a good practice, but it's less significant than rounding error in this case.

Bill the Lizard
That is a good suggestion. I will add it and see how much time this gains me.
bmucklow
I added the caching that you mentioned. It added no time back. Also I am working on adding the sqrt check, but it is causing me to go into infinite loops for some reason...so I am looking at my logic.
bmucklow
@bmucklow: I just did a quick test, and it only took an extra 200 milliseconds to create 1,000,000 `BigInteger` objects on my machine, so creating 10,000 of them will just look like your rounding error. Sorry about that, my 2nd suggestion was just noise.
Bill the Lizard
+1 for the square root bound, that's where the big gain lies. Note that for integers comparing the square to the bound is somewhat faster and more robust than to compute a square root bound.
starblue
+1. I've used the square root optimization before. It is cheap and effective.
Steve Wortham
+1  A: 

Use ints. Use a fixed size array for your primesList, so that you don't have to pay for allocation of memory (or make the start size big enough for your dynamic list to make it a non-issue).

Use a normal for that counts an int instead, with the Count being outside the loop.

Cine
A: 

I notice your code tests all candidates for divisibility by two. But your prime candidates are never even. So you can skip this first test. It's a small thing, but you'll save 9999 mods.

Craig Stuntz
How can I change "(BigInteger bi : primesList)" to be a for loop that ignores the first element? That element being the '2' you speak of? I'm not sure how to code this in Java. Is there a "(BigInteger bi : <some kind of range? Starting point> in primesList)"
bmucklow
I would just avoid putting it in the list at all. You don't need it. Then change the comparison to `.size() + 1`.
Craig Stuntz
This did not appear to add any time back to the test. Using int's instead of BigInteger and only adding 3 to my 'preloaded' primeslist and changing comparator to start at size+1 and adding 'get' to be primeTerm -2 instead of primeTerm -1 did not appear to be very significant. Thanks.
bmucklow
+1  A: 

Since you're looping on while(!prime) in getNextPrime(), it's guaranteed to return a prime, so you could change your loop in fillList without calling size() each time. Probably not much of a gain, but it kinda doesn't make sense to calculate the size each time when you know it's being incremented by 1.

Also, you could try with a LinkedList instead of an ArrayList. In this particular use case, it could actually be faster.

JRL
I changed this to a for loop beginning with initial list size. I did not see any noticeable time gain, but a valid point about calling functions that I do not need to. Thanks.
bmucklow
+3  A: 

Also try the Sieve of Erathostenes with the primes represented by a BitSet, it is much faster than testing candidates separately.

starblue
+1: This is a much cleaner and elegant solution. the algorithm may be ancient, but it does work spectacularly well.
gpampara
A: 

Here is a .NET solution... My tests indicated that I received the 10001st prime in 132ms, and 100,000 prime in 4417ms.

public static IEnumerable<long> GetPrimes(int numberPrimes)
{
  List<long> primes = new List<long> { 1, 2, 3 };
  long startTest = 3;

  while (primes.Count() < numberPrimes)
  {
    startTest += 2;
    bool prime = true;
    for (int pos = 2; pos < primes.Count() && primes[pos] < Math.Sqrt(startTest); pos++)
    {
      if (startTest % primes[pos] == 0)
      {
        prime = false;
      }
    }
    if (prime)
      primes.Add(startTest);
  }
  return primes;
}
thorkia
I'd like to correct you on one thing - the number 1 is not prime.
Rubys