Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
147 views
in Technique[技术] by (71.8m points)

Project Euler #10 Sum of Primes

I am working on Project Euler's project #10 in which I am asked to find the sum of all primes below 2,000,000. For some reason, I cannot get my code to work. I am fairly certain I am not understanding which data types to use, but neither int, long, or long long seems to be working. Any help or advice would be greatly appreciated. Here is my code. Thanks.

int main(int argc, char *argv[])
{
int primes[100] = {2, 3, 5, 7};
//array of primes that have been found
int fillcell = 4;
//cell we are looking to fill
int testnum = 8;
//number being tested to see if it's a prime
int divprime;
int sum = 17;
for(testnum = 8, fillcell = 4 ; testnum < 2000000 ; ++fillcell)
{
    std::cout << "fillcell " << fillcell << std::endl;
    for( ; primes[fillcell] == 0 ; ++testnum)
    {
        std::cout << "testnum " << testnum << std::endl;
        for(divprime = 0 ; testnum % primes[divprime] != 0 ; ++divprime)
        {
            std::cout << "divprime " << divprime << std::endl;
            if(primes[divprime] >= sqrt(testnum))
            {
                primes[fillcell] = testnum;
                sum = sum + testnum;
                std::cout << "prime found " << testnum << std::endl;
                break;
            }
        }
    }
}
std::cout << "sum" << sum << std::endl;
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Don't try to run before you have learnt the art of walking.

Don't use fixed size arrays like int primes[100] unless you have to.

One reason is that this requires you, the programmer, to determine the required size up front - for example by going to the nth Prime Page and finding out that there are 148933 primes below 2000000.

It also requires you, the programmer, to add additional checks to your code to ascertain that array accesses aren't exceeding the bounds of the array (unless you are using a language that does this for you, like Java or C#). Yet another reason is that it requires you to add code for book keeping, i.e. to track how many cells of the array are currently occupied.

Last but not least, allocating an array of 148933 integers as an automatic variable (i.e. on the stack) is likely to result in a crash because it blows the stack.

If you use std::vector<> then all these headaches go away instantly, and your code becomes much simpler.

Start with a simple plan and implement the steps with separate pieces of code. It is easier to keep on top of things if every piece of code has a simple, well-defined responsibility. Things get more difficult if you entangle everything into one big bad clump.

For example, if you store the primes you found in a vector then this allows you to look at the numbers there to see if all is well, and perhaps compare them to known lists of primes (like The First 10,000 Primes, or the primes up to 1,000,000,000,000 at primos.mat.br). You can look, but you don't have to. If you intersperse everything with output code then you always have to look at all of them. If you just add them to the sum then you cannot see them unless you debug your program and follow each and every step.

Formulate your plan as pseudocode, so that you can take it in at a glance and understand it fully. If you don't have a plan, or if you don't understand it, then the result is very likely to be cr*p.

for each candidate n between 2 and 2000000
    for each known prime p up to sqrt(n)
        if p divides n
            break
    if no divisor p was found // must be a new prime
        add n to the list of found primes

Obviously, the criterion 'if no divisor p was found' requires you to use a flag, like divisor_found, that gets initialised to false before the inner loop. Hence the first refinement:

for each candidate n between 2 and 2000000
    divisor_found := false
    for each known prime p up to sqrt(n)
        if p divides n
            divisor_found := true
            break
    if not divisor_found // must be a new prime
        add n to the list of found primes

This can be implemented without further ado. The enumeration of the candidates can be improved by skipping some numbers that cannot possibly be prime, like multiples of two:

add 2 to the list of found primes
for each odd number between 3 and 2000000
    ...

This immediately cuts your workload in half, and it is the simplest example of a 'wheel'. For problems like this, it is very practical to skip multiples of 3 as well (mod 6 wheel) by starting with 5 and incrementing by 2 and 4 in an alternating fashion.

add 2 and 3 to the list of found primes
for n = 5 to 2000000 step 6  // 6 = 2 * 3
    try n
    try n + 2

Here, the trial division done in try does not need to consider 2 or 3 as potential divisors, because your method of enumerating candidates already excludes all their multiples. Extension to skipping multiples of 5 as well is fairly straightforward.

If you do an expensive calculation like sqrt(n) during each iteration of the innermost loop then your code gets slowed down to a crawl. n doesn't change during the lifetime of the inner loop, so compute the value once in the loop header instead of needlessly repeating the computation.

Be that as it may, randomly trying different integer datatypes will get you nowhere. If values cannot become negative - as is the case here - then unsigned should be your first choice. On current systems this usually corresponds to uint32_t, which is more than enough for the piddling small numbers involved in this Euler task. You can save yourself some hassle by introducing a suitable typedef; that way you only need to change one single definition should need arise:

typedef std::uint32_t num_t;
typedef std::uint64_t sum_t;
num_t const N = 2000000;
...

std::vector<num_t> primes;
...

for (num_t n = 3; n <= N; n += 2)
   ...

sum_t sum = 0;
for (num_t p: primes)
   sum += p;

I've added a separate sum_t as well since a ballpark estimation of the sum puts it well beyond the capacity of a uint32_t.

In any case you should seriously consider using the Sieve of Eratosthenes here. It is simpler than wheeled trial division and several orders of magnitude faster - even the simplest rendering should solve this Euler task in a few milliseconds.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...