12
\$\begingroup\$

I attempted the Project Euler question #7 in C++ ("What is the 10 001st prime number?").

I took into account what I've been told about primes in order to create the most efficient algorithm I could in order to solve this. I would like to know if there is anything else I can do to check even fewer numbers for an even more efficient algorithm.

Also, I'm wondering if creating and using functions would be good practice in this scenario, and how I could go using functions more in coding.

#include <iostream> #include <vector> using namespace std; // What is the 10 001st prime number? int main() { int countPrimes = 2; int checkIfPrime = 6; // multiples of 6 // sees if numbers adjacent to 6 are prime bool lessOnePrime = true; bool plusOnePrime = true; vector<int> primeVector; // for holding primes to check for future primes //account for primes 2 and 3 beforehand: primeVector.push_back(2); primeVector.push_back(3); while (countPrimes < 10001) // checks until the 10001st prime has been found { for (int i = 0; primeVector[i] <= sqrt(checkIfPrime + 1); i++) //checks only prime factors up to the square root { // checks if numbers adjacent to 6 have prime factors // if so, makes their respective booleans false if ((checkIfPrime - 1) % primeVector[i] == 0) { lessOnePrime = false; } if ((checkIfPrime + 1) % primeVector[i] == 0) { plusOnePrime = false; } // if both adjacent numbers to multiples of 6 aren't prime, exits loop if (lessOnePrime == false && plusOnePrime == false) { break; } } if (lessOnePrime) { primeVector.push_back(checkIfPrime - 1); countPrimes++; // cout << "#" << primeVector.size() << " prime number:\t" << checkIfPrime - 1 << endl; } if (plusOnePrime) { primeVector.push_back(checkIfPrime + 1); countPrimes++; // cout << "#" << primeVector.size() << " prime number:\t" << checkIfPrime + 1 << endl; } lessOnePrime = true; plusOnePrime = true; checkIfPrime += 6; } cout << "The " << countPrimes << "st prime number is:\n" << primeVector[countPrimes - 1] << endl; return 0; } // end main() 
\$\endgroup\$

2 Answers 2

9
\$\begingroup\$

I would like to know if there is anything else I can do to check even fewer numbers for an even more efficient algorithm.

The general recommendation is to use a

sieve for this. The one that comes to mind is the Sieve of Eratosthenes.

That said, there are some other comments that I can make on the code as it stands.

 int checkIfPrime = 6; // multiples of 6 

Often, your code will read more naturally if you give variables noun names. This tells what you should do with the variable. I'd prefer a name like primeCandidate which describes what it is. Or should be, since 6 isn't actually a number that you want to check.

 int primeCandidate = 7; 

It can save calculations if you start with 7 rather than 6. Rather than saying primeCandidate-1 and primeCandidate+1, you can just say primeCandidate-2 and primeCandidate. Note that you need the larger number slightly more often.

 bool lessOnePrime = true; bool plusOnePrime = true; 

Since you don't use these outside the while loop nor across iterations, you should define these inside the loop instead. Of course, as we'll discuss later, these aren't actually necessary.

 vector<int> primeVector; 

This is called Hungarian notation, where you put the type in the name. This can cause difficulty in changing code later. For example, what if you wanted to change from std::vector to a type called List? Should you rename this variable as well? A better name might be primes which works regardless of type.

 while (countPrimes < 10001) // checks until the 10001st prime has been found 

You don't have to track a counter manually. You can just say

 while (primes.size() < 10001) 

The type already maintains a count for you. You might as well use it rather than duplicate it manually.

 for (int i = 0; primeVector[i] <= sqrt(checkIfPrime + 1); i++) //checks only prime factors up to the square root 

You don't need to start at 0 each time. By starting with 5 and 7 and always incrementing by 6, you ensure that it will never be divisible by 2 or 3. So you can skip the first two elements in the vector.

 for (int i = 2, n = sqrt(primeCandidate); primes[i] <= n; i++) 

You don't need to calculate the square root on each iteration. You can do it once at the beginning of the loop.

You don't need to comment saying that you are only checking to the square root. The code says this. You might want to comment on why it is safe to only check up to the square root.

 { if ((checkIfPrime - 1) % primeVector[i] == 0) { lessOnePrime = false; } if ((checkIfPrime + 1) % primeVector[i] == 0) { plusOnePrime = false; } if (lessOnePrime == false && plusOnePrime == false) { break; } } if (lessOnePrime) { primeVector.push_back(checkIfPrime - 1); countPrimes++; // cout << "#" << primeVector.size() << " prime number:\t" << checkIfPrime - 1 << endl; } if (plusOnePrime) { primeVector.push_back(checkIfPrime + 1); countPrimes++; // cout << "#" << primeVector.size() << " prime number:\t" << checkIfPrime + 1 << endl; } lessOnePrime = true; plusOnePrime = true; 

This is more complicated than it needs to be. It would be much simpler to use two function calls instead of this for loop.

 if (isPrime(primeCandidate - 2, primes)) { primes.push_back(primeCandidate - 2); } if (isPrime(primeCandidate, primes)) { primes.push_back(primeCandidate); } 

Yes, this repeats the for loop, but it saves you the internal scaffolding. It also means that if one is a prime and the other isn't, it doesn't keep trying to divide the non-prime one by each potential factor. That loop can exit early. You'd have to profile to be sure, but I'd expect this to be faster.

The function would look something like

bool isPrime(const int candidate, const std::vector<int>& primes) { for (int i = 2, n = sqrt(candidate); primes[i] <= n; i++) { if (candidate % primes[i] == 0) { return false; } } return true; } 

I haven't tried to compile it though.

Note that it might be more efficient to use an iterator rather than an index variable. The notation would be more complicated in this case though.

I'll leave writing the actual function to you. Note that even with the overhead of writing the function, you'll still likely shorten the code a bit. You do a lot of effort just to make the single for loop work.

 checkIfPrime += 6; } 

The only change I'd make to this is the variable name. Although if you wanted to simplify the code in the loop at the cost of making this more complicated, you could change this to

 primeCandidate += increment; increment = 6 - increment; } 

Where increment would be initialized as

 int increment = 2; 

And you'd start primeCandidate at 5. You'd also only do one isPrime check per iteration of the while loop.

\$\endgroup\$
1
  • \$\begingroup\$ I've implemented some of your suggestions, and they do make the code look a lot more neat! My one question is what exactly a sieve is and situations in which one would need to use it. \$\endgroup\$ Commented Jul 10, 2015 at 1:29
5
\$\begingroup\$
  • It is a good practice to declare variables in the scope were they are used, that is innermost possible. In this case, move the lessOnePrime and plusOnePrime declarations inside the while loop:

    while (countPrimes < 10001) { bool lessOnePrime = true; bool plusOnePrime = true; 

    Notice that this way you do not need to reassign them at the end of the loop.

  • sqrt is an expensive function, especially compared to semantically equivalent primeVector[i] * primeVector[i] <= checkIfPrime.

  • You may avoid (also quite expensive) ifs:

     lessOnePrime = (checkIfPrime - 1) % primeVector[i]; plusOnePrime = (checkIfPrime + 1) % primeVector[i]; 

    It is quite possible that the compiler does it for you though.

  • To address the lack of functions concern, yes you should factor the for loop in a function (no naked loops mantra), along the lines

    void test_adjacent_prime(int multiple_of_6, std::vector<int&> known_primes, bool& lessOne, bool& plusOne); 
  • using namespace std is considered a bad practice.

\$\endgroup\$
3
  • \$\begingroup\$ Ah! I just remembered that a bool will return 0 if false and 1 if true. Thanks for the tips! I'll keep these in mind for my next challenge, along with the creating of functions. Also, what do the "&"s after the variable types do in the function parameters? \$\endgroup\$ Commented Jul 9, 2015 at 22:59
  • \$\begingroup\$ It seems replacing my ifs with what you suggested doesn't work. I assume that it's because the booleans will switch between true and false each time rather than staying at false if it's determined that the number in question is not prime. Is there any way to work around this? \$\endgroup\$ Commented Jul 9, 2015 at 23:13
  • 1
    \$\begingroup\$ @Gandhichainz a Seive is a method of finding primes. Consider finding all the primes less than 100, create a 100 element array, initialized to the index. If the index is a multiple of 2 (4,6,8....) put a -1 in that element. Now do the same thing with 3 and then 4 and so on up to index 99. Any cell that doesn't have a -1 it has a prime index (i.e. 2,3,5,7,11,13,...). Seives are great ways for finding small prime sets (like all primes less than 500), they become computationally complex and the search size grows. Also, you don't know how big your range will be to find 10001 primes \$\endgroup\$ Commented Jul 10, 2015 at 2:56

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.