所以这就是:我正在尝试计算低于两百万的所有素数之和(对于this problem),但我的程序非常慢。我知道这个算法本身就非常糟糕而且是一个蛮力的算法,但它似乎比它应该慢得多。
在这里,我将搜索限制为20,000,以便结果不会等待太长时间
我不认为这个谓词很难理解,但我还是会解释它:我计算所有素数低于20,000的列表,然后求它们。总和部分很好,素数部分非常慢。
problem_010(R) :-
p010(3, [], Primes),
sumlist([2|Primes], R).
p010(20001, Primes, Primes) :- !.
p010(Current, Primes, Result) :-
(
prime(Current, Primes)
-> append([Primes, [Current]], NewPrimes)
; NewPrimes = Primes
),
NewCurrent is Current + 2,
p010(NewCurrent, NewPrimes, Result).
prime(_, []) :- !.
prime(N, [Prime|_Primes]) :- 0 is N mod Prime, !, fail.
prime(ToTest, [_|Primes]) :- prime(ToTest, Primes).
我想了解为什么它如此缓慢。它是一个很好的实现愚蠢的暴力算法,还是有一些原因使Prolog下降?
编辑:我已经找到了一些东西,通过添加新的素数而不是让它们在列表的头部,我有更多经常在开始时出现的素数,所以它快〜3倍。仍然需要一些见解:))答案 0 :(得分:4)
首先,Prolog在这里不会失败。
如何生成素数有很聪明的方法。但是,作为一个廉价的开始只是按相反的顺序积累素数! (7.9s - > 2.6s)以这种方式,较小的那些被更快地测试。然后,考虑仅测试高达141的素数。较大的质数不能成为一个因素。
然后,您可以添加3,5,7,而不是仅通过不能被2整除的数字进行踩踏。
有人在写这个“问题”的论文。例如,参见this paper,尽管对于“真正的”算法实际上是多么复杂的讨论,22世纪前最新版本的算盘被称为Salamis tablets。
答案 1 :(得分:3)
首先,使用append/3
追加到列表的末尾非常慢。如果必须,请改用差异列表。 (就个人而言,我尽量避免append/3
)
其次,在检查素数时,您的prime/2
总是遍历整个列表。这不必要地慢。您只需检查ID,就可以找到一个积分因子,直到您要检查的数字的平方根。
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).
免责声明:我通过Google搜索找到了prime/1
和has_factor/2
的此实现。
此代码给出:
?- problem_010(R).
R = 142913828922
Yes (12.87s cpu)
这是更快的代码:
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).
这比我上面给出的代码快了一个数量级:
?- problem_010(R).
R = 142913828922
Yes (0.82s cpu)
答案 2 :(得分:3)
考虑使用例如筛子方法(“Eratosthenes的筛子”):首先使用例如numlist / 3创建列表[2,3,4,5,6,...... N]。列表中的第一个数字是素数,保留它。从列表的其余部分中消除其倍数。剩余列表中的下一个数字也是素数。再次消除它的倍数。等等。该列表将迅速缩小,最终只剩下素数。
答案 3 :(得分:2)
好的,在编辑之前问题只是算法(imho)。 正如您所注意到的,检查数字是否先用较小的素数进行分割会更有效;在有限集合中,有更多的数字被3整除,而不是32147。
另一种算法改进是停止检查素数何时大于数字的平方根。
现在,在你改变之后确实存在一些prolog问题: 你使用append / 3。 append / 3非常慢,因为你必须遍历整个列表才能将元素放在最后。 相反,你应该使用差异列表,这样可以非常快速地将元素放在尾部。
现在,差异清单是什么?而不是创建一个普通的列表[1,2,3],你创建这个[1,2,3 | T]。请注意,我们将尾部未实例化。然后,如果我们想在列表的末尾添加一个元素(或更多),我们可以简单地说T = [4 | NT]。真棒?
以下解决方案(以相反的顺序累积素数,在素数&gt; sqrt(N)时停止,要追加的差异列表)对于20k素数需要0.063,对于2m素数需要17秒,而原始代码需要3.7秒用于20k且附加/ 3版本1.3秒。
problem_010(R) :-
p010(3, Primes, Primes),
sumlist([2|Primes], R).
p010(2000001, _Primes,[]) :- !. %checking for primes till 2mil
p010(Current, Primes,PrimesTail) :-
R is sqrt(Current),
(
prime(R,Current, Primes)
-> PrimesTail = [Current|NewPrimesTail]
; NewPrimesTail = PrimesTail
),
NewCurrent is Current + 2,
p010(NewCurrent, Primes,NewPrimesTail).
prime(_,_, Tail) :- var(Tail),!.
prime(R,_N, [Prime|_Primes]):-
Prime>R.
prime(_R,N, [Prime|_Primes]) :-0 is N mod Prime, !, fail.
prime(R,ToTest, [_|Primes]) :- prime(R,ToTest, Primes).
另外,考虑在生成它们时添加数字以避免额外的o(n)因为sumlist / 2
最后,您始终可以实现在多项式时间(XD)