I wouldn't call this a programming solution to the puzzle; it's more of a program that happens to print the correct result. Basically, you've already solved the problem, and are using the computer as a fancy calculator. You might as well do the work on a two-dollar calculator that you can buy at the corner store.
There are, of course, situations where you have to apply a certain amount of ingenuity in order to translate the problem into an algorithm to be implemented. For example, when factoring numbers, it's common to only try odd factors, knowing that 2 is a special case. At some point, though, you have to decide how much work to do in the programmer's brain vs. how much work to give to the CPU. How to make the tradeoff will be guided by your desire for fast runtime performance vs. the elegance of the code.
In this case, a modern computer can solve this problem by "brute force" in almost the same amount of time as your "smart" summation using the inclusion-exclusion principle. Here's how a brute-force solution would look like:
#include <stdio.h>
int main() {
int sum = 0;
for (int i = 0; i < 1000; i++) {
if ((i % 3 == 0) || (i % 5 == 0)) {
sum += i;
}
}
printf("sum is: %d\n", sum);
return 0;
}
It expresses the problem exactly as stated. There is no room for programming error, no mysterious constants, no formulas. If the requirements change (for example, to find the sum of all multiples of 3 or 7 below 10000), it's trivial to adjust the program. The engineering trade-off definitely favours simplicity over runtime performance. (If it takes you more than one second to write the cleverer code, you've already lost!)
As I said, though, there are times when human ingenuity has to be built into the program, usually because a brute-force solution cannot yield acceptable performance. In those situations, your job as a programmer is to express the shortcuts while sacrificing as little code maintainability as possible.
One trick is to let the preprocessor and compiler do the work. The proposal below is much more readable and flexible than your original. In fact, if you inspect the assembler output, you'll see that this solution generates identical code to your original solution. Both of them embed the answer right into the executable as a constant!
#include <stdio.h>
#define COUNT_MULTIPLES(m, limit) (((limit) - 1) / (m))
#define MIN_MULTIPLE(m, limit) (m)
#define MAX_MULTIPLE(m, limit) (COUNT_MULTIPLES(m, limit) * (m))
#define SUM_MULTIPLES(m, limit) (COUNT_MULTIPLES(m, limit) * \
(MIN_MULTIPLE(m, limit) + MAX_MULTIPLE(m, limit)) / 2)
int main() {
int limit = 1000;
int sum_mult_3 = SUM_MULTIPLES(3, limit);
int sum_mult_5 = SUM_MULTIPLES(5, limit);
int sum_mult_15 = SUM_MULTIPLES(15, limit);
/* Sum using the inclusion-exclusion principle */
int sum = sum_mult_3 + sum_mult_5 - sum_mult_15;
printf("sum is: %d\n", sum);
return 0;
}
Everything is named to enhance understanding. Repetition of code (the summation formula) is eliminated. Best of all, no magic numbers!
Update: Actually, you don't have to use preprocessor macros to get good performance. If you write the macros as functions instead, clang -O2
and gcc -O2
will both do the entire calculation at compile time, exactly as with preprocessor macros.
Considering the problems with macros (lack of type safety and re-evaluation of arguments), I think that changing them to functions is a better solution.
/* Compilation with optimization level -O2 or better is suggested */
#include <stdio.h>
int count_multiples(m, limit) {
return (limit - 1) / m;
}
int min_multiple(m, limit) {
return m;
}
int max_multiple(m, limit) {
return count_multiples(m, limit) * m;
}
int sum_multiples(m, limit) {
return count_multiples(m, limit) * (min_multiple(m, limit) + max_multiple(m, limit)) / 2;
}
int main() {
int limit = 1000;
int sum_mult_3 = sum_multiples(3, limit);
int sum_mult_5 = sum_multiples(5, limit);
int sum_mult_15 = sum_multiples(15, limit);
/* Sum using the inclusion-exclusion principle */
int sum = sum_mult_3 + sum_mult_5 - sum_mult_15;
printf("sum is: %d\n", sum);
return 0;
}