I would recommend using a Sieve instead of trial division each time. If you find 3 is prime, then 6, 9, 12....you will know are composite. So if you make a giant list of "primes" (All numbers 1 through N for starters) and then start knocking out multiples of primes, you'll be left with a list of real primes.
Here is an example code using a boolean array. (True indices are composite, false means they are prime. So if array[5] is false, it's prime, whereas array[1000000]
is true)
Running time is roughly 0.09 seconds on an online Java interpreter. This method is known as the Sieve of Eratosthenes.
public static boolean[] simpleSieve(int n)
{
boolean[] sieve = new boolean[n+1]; //false = prime, true = composite
sieve[0] = true; sieve[1] = true; sieve[2] = false;
for(int i = 4; i <= n; i+=2)
{ sieve[i] = true; }
int limit = (int)Math.sqrt(n)+1;
for(int i = 3; i < limit; i+=2)
{
if(!sieve[i]) //if prime (or not composite)
{
for(int j = i*i; j <= n; j+=i) //mark all multiples of i as composite
{ sieve[j] = true; }
}
}
return sieve;
}
First loop knocks out the even numbers, since 2 is the ONLY even prime.
The second loop knocks out the odd numbers, up to the square root of N. After the square root of N, there are no more multiples or composites left. Whatever is left after the square root of N is a prime entry.
So with an array saying "here is what is prime and not prime", you just need to add them up.
public static long sumOfPrimes(int n)
{
//get primes
boolean[] primes = simpleSieve(n);
long sum = 0;
for(int i = 0; i < primes.length; i++)
{
if(!primes[i])
{ sum += i; }
}
return sum;
}
And to get your result, you simply print the result of that method.
public static void main (String[] args) throws java.lang.Exception
{
System.out.println(sumOfPrimes(2_000_000));
}
This algorithm needs a tweak for Lists or some other data structure for larger values, since arrays can only have an integer for their indices. Though if you were going for finding primes up to 2,147,483,647 then this method may consume more memory than you have on hand.
But for Project Euler's solution this should still be useful.
As per David's comment, it is mentioned that using BitSet
is a more memory efficient way of storing the bit flags for primes & composites, and this allows for a higher limit of how much we can store and calculate. Though eventually the long
used for the summation of primes will overflow as you use higher values, so switching to BigInteger
would be needed there, and even larger versions require a segmented sieve and/or larger data structure.
Revised BitSet
friendly functions:
public static BitSet simpleSieve(int n)
{
BitSet sieve = new BitSet(n+1); //New BitSet
sieve.set(0); sieve.set(1); //0 & 1 aren't primes, set their bits
for(int i = 4; i <= n; i+=2) //set all multiples of 2
{ sieve.set(i); }
int limit = (int)Math.sqrt(n)+1;
for(int i = 3; i < limit; i+=2)
{
if(!sieve.get(i)) //if prime (or "not composite")
{
for(int j = i*i; j <= n; j+=i) //mark all multiples of i as composite
{ sieve.set(j); }
}
}
return sieve;
}
public static long sumOfPrimes(int n)
{
//get primes
BitSet primes = simpleSieve(n);
long sum = 0;
for(int i = 0; i < n+1; i++)
{
if(!primes.get(i))
{ sum += i; }
}
return sum;
}
2
,3
,4
(!),5
,... are prime? (the answer to that is 2 million times). How many times do you really need to? once. \$\endgroup\$j<=i
toj*j<=i
is much faster. Personally, I'd optimize it further by using wheel factorization or full Eratosthenes. \$\endgroup\$