What is the bottleneck in this primes related predicate?

后端 未结 4 1034
旧时难觅i
旧时难觅i 2021-01-19 21:05

So here it is : I\'m trying to calculate the sum of all primes below two millions (for this problem), but my program is very slow. I do know that the algorithm in itself is

4条回答
  •  甜味超标
    2021-01-19 22:04

    First of all, appending at the end of a list using append/3 is quite slow. If you must, then use difference lists instead. (Personally, I try to avoid append/3 as much as possible)

    Secondly, your prime/2 always iterates over the whole list when checking a prime. This is unnecessarily slow. You can instead just check id you can find an integral factor up to the square root of the number you want to check.

    problem_010(R) :-
        p010(3, 2, R).
    p010(2000001, Primes, Primes) :- !.
    p010(Current, In, Result) :-
        ( prime(Current) -> Out is In+Current ; Out=In ),
        NewCurrent is Current + 2,
        p010(NewCurrent, Out, Result).
    
    prime(2).
    prime(3).
    prime(X) :-
        integer(X),
        X > 3,
        X mod 2 =\= 0,
        \+is_composite(X, 3).   % was: has_factor(X, 3)
    
    is_composite(X, F) :-       % was: has_factor(X, F) 
        X mod F =:= 0, !.
    is_composite(X, F) :- 
        F * F < X,
        F2 is F + 2,
        is_composite(X, F2).
    

    Disclaimer: I found this implementation of prime/1 and has_factor/2 by googling.

    This code gives:

    ?- problem_010(R).
    R = 142913828922
    Yes (12.87s cpu)
    

    Here is even faster code:

    problem_010(R) :-
        Max = 2000001,
        functor(Bools, [], Max),
        Sqrt is integer(floor(sqrt(Max))),
        remove_multiples(2, Sqrt, Max, Bools),
        compute_sum(2, Max, 0, R, Bools).
    
    % up to square root of Max, remove multiples by setting bool to 0
    remove_multiples(I, Sqrt, _, _) :- I > Sqrt, !.
    remove_multiples(I, Sqrt, Max, Bools) :-
        arg(I, Bools, B),
        (
            B == 0
        ->
            true % already removed: do nothing
        ;
            J is 2*I, % start at next multiple of I
            remove(J, I, Max, Bools)
        ),
        I1 is I+1,
        remove_multiples(I1, Sqrt, Max, Bools).
    
    remove(I, _, Max, _) :- I > Max, !.
    remove(I, Add, Max, Bools) :-
         arg(I, Bools, 0), % remove multiple by setting bool to 0
         J is I+Add,
         remove(J, Add, Max, Bools).
    
    % sum up places that are not zero
    compute_sum(Max, Max, R, R, _) :- !.
    compute_sum(I, Max, RI, R, Bools) :-
        arg(I, Bools, B),
        (B == 0 -> RO = RI ;  RO is RI + I ),
        I1 is I+1,
        compute_sum(I1, Max, RO, R, Bools).
    

    This runs an order of magnitude faster than the code I gave above:

    ?- problem_010(R).
    R = 142913828922
    Yes (0.82s cpu)
    

提交回复
热议问题