Selection Approach
That naïve solution inspects all natural values below
To save having to div/mod each of these excluded values, we can count upwards in increments of these numbers, making sure to only count the multiples of three or five.
def gen_multiples(n: int) -> Iterator[int]:
"""Generate all integers between 1..n that are divisible by either 3 or 5."""
for i3 in range(3,n,3):
yield i3
for i5 in range(5,n,15):
# skip each third multiple of 5,
yield i5
i5 += 5
if i5 < n:
# ...careful not to exceed n.
yield i5
sum(gen_multiples(100))
sum := 0
for i := 3; i < n; i += 3 {
sum += i
}
for j := 5; j < n; j += 15 {
sum += j
if j+5 < n {
sum += j + 5
}
}
% Copyright (c) 2024 Kevin Damm
%
% Permission is hereby granted, free of charge, to any person obtaining a copy
% of this software and associated documentation files (the "Software"), to deal
% in the Software without restriction, including without limitation the rights
% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
% copies of the Software, and to permit persons to whom the Software is
% furnished to do so, subject to the following conditions:
%
% The above copyright notice and this permission notice shall be included in all
% copies or substantial portions of the Software.
%
% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
% SOFTWARE.
%
% github:kevindamm/projecteuler/golang/p0001.select.prolog
% Sum is the sum of multiples of {3, 5} below N.
sum_multiples(N, Sum) :-
sum_by(3, N, Sum3),
sum_by(5, N, Sum5),
sum_by(15, N, Sum15),
Sum is Sum3 + Sum5 - Sum15.
sum_by(Increment, N, Sum) :-
sum_by(Increment, 0, N, 0, Sum).
% Accumulate each multilpe of Inc less than N.
sum_by(Increment, I, N, Accumulator, Sum) :-
I < N,
!,
SubSum is Accumulator + I,
NextI is I + Increment,
sum_by(Increment, NextI, N, SubSum, Sum).
% Resolve total sum when incremented past the limit.
sum_by(Inc, I, N, Acc, Acc) :- I >= N.
main :-
sum_multiples(1000, Sum),
write(Sum).
This skips a lot of unnecessary computation by only looking at the numbers that we know will be included in the sum. You could simplify the second loop to look at every five-multiple and skip when the number also divides
The order that these numbers are added together is not strictly increasing. Since that doesn't affect the final result, and it's a little easier to read this way anyway, I've left it as two separate for-loops. An example of interleaving the series so that the numbers are in sorted order can be found in the github repo for this site.