找出一个数字的所有自然除数(使用Prolog)

时间:2019-01-27 14:07:51

标签: prolog

我想创建一个谓词除数(X,[Y]),如果 X> 1和Y是X的所有除数的列表,从X开始,一直到1。

我现在的代码如下:

divisors(1,[1]).
divisors(X,[Y,Z|Ys]) :- 
    X>0,
    Y is X,
    Y>Z, 
    divides(X,[Z|Ys]).

divides(X,[Y,Z|Ys]) :-
    Y>Z, 
    0 is X mod Y, 
    divides(X,[Z|Ys]).
divides(X,[1]).

但是它有几个问题:

    如果询问列表,
  1. 序言将返回错误(例如?-除数(10,X)。)

  2. ?-除数(X,[Y])。其中[Y]是除数的不完整列表是真的...


由Guy Coder编辑

此答案是由OP提供的,并在下面的评论中发布。

移动到这里以便其他人可以看到。

divisors(X,R) :- 
  X > 1, 
  divisors(X,1,[],R). 

divisors(X,D,R,R):-
  D>X.

divisors(N,D0,R0,R) :-
  divisors_0(N,D0,R0,R1), 
  D is D0 + 1, 
  divisors(N,D,R1,R). 

divisors_0(N,D,R0,[D|R0]) :-
  divides(N,D). 

divisors_0(N,D,R0,R0).

divides(N,D) :-
  0 is N mod D.

Op还注意到此版本中的一些错误:

  1. 如果我问一个错误的语句(10,[1,2,3]),它不会终止。

  2. 如果我问类似(X,[10,5,2,1])的语句,则会引发错误。 (->参数未充分初始化。)

2 个答案:

答案 0 :(得分:3)

威廉(William)的answer很好,而这里的回答可能更接近您所写的内容。

divides(N,D) :-
    0 is N mod D.

divisors_0(N,D,R0,[D|R0]) :-
    divides(N,D).

divisors_0(N,D,R0,R0) :-
    \+ divides(N,D).

divisors(_,0,R,R).

divisors(N,D0,R0,R) :-
    divisors_0(N,D0,R0,R1),
    D is D0 - 1,
    divisors(N,D,R1,R).

divisors(X,R) :-
    X > 1,
    divisors(X,X,[],R), !.

示例:

?- between(1,15,N), divisors(N,Rs).
N = 2,
Rs = [1, 2] ;
N = 3,
Rs = [1, 3] ;
N = 4,
Rs = [1, 2, 4] ;
N = 5,
Rs = [1, 5] ;
N = 6,
Rs = [1, 2, 3, 6] ;
N = 7,
Rs = [1, 7] ;
N = 8,
Rs = [1, 2, 4, 8] ;
N = 9,
Rs = [1, 3, 9] ;
N = 10,
Rs = [1, 2, 5, 10] ;
N = 11,
Rs = [1, 11] ;
N = 12,
Rs = [1, 2, 3, 4, 6, 12] ;
N = 13,
Rs = [1, 13] ;
N = 14,
Rs = [1, 2, 7, 14] ;
N = 15,
Rs = [1, 3, 5, 15].

编辑

OP修改了他们的代码,请参阅有问题的更新并出现一些错误。

此版本解决了这些错误。

divisors(X,R) :-
  (
    var(X)
  ->
    false
  ;
    (
      var(R)
    ->
      X > 1,
      divisors(X,1,[],R)
    ;
      divisors_2(X,R), !
    )
  ).

divisors_2(_,[]).

divisors_2(X,[H|T]) :-
  divides(X,H),
  divisors_2(X,T).

divisors(X,D,R,R):-
  D>X.

divisors(N,D0,R0,R) :-
  divisors_0(N,D0,R0,R1),
  D is D0 + 1,
  divisors(N,D,R1,R).

divisors_0(N,D,R0,[D|R0]) :-
  divides(N,D).

divisors_0(_,_,R0,R0).

divides(N,D) :-
  0 is N mod D.

第一个错误:It doesn't terminate if I ask a wrong statement like divisors(10,[1,2,3]).

通过添加除数/ 2来固定

(
  var(R)
->
  X > 1,
  divisors(X,1,[],R)
;
  divisors_2(X,R), !
)

divisors_2(_,[]).

divisors_2(X,[H|T]) :-
  divides(X,H),
  divisors_2(X,T).

仅处理分母列表而不是生成列表。

第二个错误:It throws an error if I ask a statement like divisors(X, [10,5,2,1]). (-> Arguments are not sufficiently initialized.)

通过进一步添加到divisor/2

来解决

divisors(X,R) :-
  (
    var(X)
  ->
    false
  ;
    (
      var(R)
    ->
      X > 1,
      divisors(X,1,[],R)
    ;
      divisors_2(X,R), !
    )
  ).

检查第一个参数X是否为变量,如果是,则仅返回false。另一种选择是生成无限的答案列表。可能的话,没有要求。

答案 1 :(得分:2)

在Prolog中,使用回溯并为同一查询提出多种解决方案是很普遍的。因此,我们可以构造一个谓词,将第二个参数与所有除数统一起来,而不是构造一个除法器列表。例如:

divisor(N, D) :-
    between(1, N, D),
    0 is N mod D.

然后产生:

?- divisor(12, N).
N = 1 ;
N = 2 ;
N = 3 ;
N = 4 ;
N = 6 ;
N = 12.

上面的算法是 O(n)算法:我们扫描除数,该除数与要获得其除数的项目的值成线性关系。我们可以通过扫描到√n轻松地将其改进为 O(√n),并且每次都产生除数(当然,如果是除数),和共同除数,例如:

emitco(D, _, D).
emitco(D, C, C) :-
    dif(D, C).

divisor(N, R) :-
    UB is floor(sqrt(N)),
    between(1, UB, D),
    0 is N mod D,
    C is N / D,
    emitco(D, C, R).

这仍然会产生正确的答案,但是顺序就像一个收敛的交替序列:

?- divisor(12, N).
N = 1 ;
N = 12 ;
N = 2 ;
N = 6 ;
N = 3 ;
N = 4.

?- divisor(16, N).
N = 1 ;
N = 16 ;
N = 2 ;
N = 8 ;
N = 4 ;
false.

我们可以使用findall/3 [swi-doc]setof/3 [swi-doc]获得除数的列表。 setof/3甚至可以对除数进行排序,因此我们可以根据divisors/2来实现divisor/2

divisors(N, Ds) :-
    setof(D, divisor(N, D), Ds).

例如:

?- divisors(2, N).
N = [1, 2].

?- divisors(3, N).
N = [1, 3].

?- divisors(5, N).
N = [1, 5].

?- divisors(12, N).
N = [1, 2, 3, 4, 6, 12].

?- divisors(15, N).
N = [1, 3, 5, 15].

我们可以使用reverse/2来反转结果。