作业是接受两个变量,一个介于0和10,000之间的数字,以及介于1和该数字之间的循环素数的数量。
我无法通过递归将变量传递回来(我认为回溯就是所谓的。)我得到正确的数字,我很确定我的概念已经失效,我遇到的问题是当我尝试重新分配变量(?!)
时会抛出错误以下是代码:
circPrimeCompare(Below, NumCirc):-
RealNum is 0,
circPrimeCompare(1, Below, RealNum, []),
print('R: '),
print(RealNum),
nl,
(NumCirc =:= RealNum).
circPrimeCompare(_, 0, _, _).
circPrimeCompare(N, 1, _, _):- prime(N), print('aaa').
circPrimeCompare(X, Below, RealNum, L):-
( prime(X), X<Below ->
print(X),
nl,
numDigits(X, Y),
rotate(X, Y, N2),
( prime(N2)
-> RealNum2 is RealNum + 1
; RealNum2 is RealNum
),
X2 is X + 1,
( not(circPrimeCompare(X2, Below, RealNum2, L))
-> RealNum = RealNum2, print(RealNum), nl
; RealNum = RealNum2, print(RealNum), nl
),
print('RealNum2: '),
print(RealNum),
nl
;
( X<Below ->
X2 is X + 1,
RealNumPass is RealNum,
( not(circPrimeCompare(X2, Below, RealNumPass, L))
-> RealNum = RealNumPass, print(RealNum), nl
; RealNum = RealNumPass, print(RealNum), nl
),
print('RealNum: '),
print(RealNum),
nl
)
).
这是跟踪:
Fail: (26) circPrimeCompare(10, 10, 4, []) ? creep
^ Exit: (25) not(user:circPrimeCompare(10, 10, 4, [])) ? creep
Call: (25) 4=4 ? creep
Exit: (25) 4=4 ? creep
...
Exit: (24) circPrimeCompare(9, 10, 4, []) ? creep
^ Fail: (23) not(user:circPrimeCompare(9, 10, 4, [])) ? creep
Call: (23) 4=4 ? creep
Exit: (23) 4=4 ? creep
...
Exit: (22) circPrimeCompare(8, 10, 4, []) ? creep
^ Fail: (21) not(user:circPrimeCompare(8, 10, 4, [])) ? creep
**Call: (21) 3=4 ? creep
Fail: (21) 3=4 ? creep**
Redo: (21) numDigits(7, _G589) ? creep
粗体部分是扔我的东西。我真的不明白为什么这样做。是因为变量本质上只是一个用途吗?关于如何解决它的任何想法?
(是的,我知道这是非常非常糟糕的代码。在此任务之前,我从未在Prolog中写过任何内容。)
答案 0 :(得分:3)
您没有以“Prolog”的方式思考问题。特别是试图“重新分配”变量不是Prolog方式。变量在满足目标和子目标的过程中受到约束,而不是重新分配值,我们经常安排代码携带“累加器”变量,只是在计算的最后阶段将其值传递给另一个“最终”变量
回溯和递归是不同的事情。当目标(或子目标)失败时,回溯发生,并且Prolog“引擎”试图以不同的方式满足该目标。如果我们用尽不同的规则等来满足目标,那么它就失败了。 (这可能会导致Prolog引擎回溯到之前的子目标并尝试以不同的方式满足 。)
递归是指谓词本身定义的时候,即如果调用谓词可以导致同一个谓词再次作为子目标被调用。
要在Prolog中执行许多任务,我们必须在递归而不是迭代方面考虑它们,这在过程语言中是很自然的。这就是为什么我们使用“累加器”变量,这些变量在不习惯的眼睛看来是谓词中额外的和可能不必要的参数。
然而,一旦你看到这个想法在行动,你就可以通过新方式应用这个概念获得一些满足感。
让我们将给定列表中的数字加起来作为模型问题。天真的方法是写:
sum(List,Sum).
其中List
是一个数字列表,Sum
应该是我们的输出,在列表中保留规定的值总和。
基本想法很自然,你考虑列表的Head
和Tail
,如果列表不是(还)为空,你想要“重新分配”Sum = Sum + Head
,然后递归地处理Tail
(直到列表为空,我们有Sum
我们想要的)。
但这在Prolog中没有意义,因为你不能像过程语言允许的那样改变Sum
的值。这是累加器参数被添加到我们的谓词并执行相同的工作的地方,但是以Prolog喜欢的方式。
sumAux([ ],Sum,Sum).
sumAux([H|T],Accum,Sum) :-
NewAccum is Accum + H,
sumAux(T,NewAccum,Sum).
sum(List,Sum) :- sumAux(List,0,Sum).
这里我们引入了一个带有额外参数的“辅助”谓词sumAux/3
,我们通过将“累加器”中间参数设置为零来定义sum/2
这个谓词,但是传递List
和Sum
个参数。
关于它是如何工作的一点思考将会回报你的努力,很快我就会期待你将以各种新颖的方式应用这种范式(例如计算循环素数)。
答案 1 :(得分:1)
"backtracking"是概念的名称;这意味着如果事实证明这是不幸的话,那就回到自己的身边,违背一个人的决定。在Prolog程序中,我们有规则,显示哪些子目标必须被证明,主要目标(规则主管)被视为已被证明。当有几条规则时,我们可能会尝试第1条;如果后来证明它无法证明,我们回溯到最新的决策点,并尝试证明下一条规则(如果有的话)。
接下来,我们实例化我们的变量,给它们赋值。一旦为变量确定了一个值,它就无法改变,而我们仍在努力证明我们的目标。当我们说X=2
时,我们承诺这个选择。我们现在不能说X=5
,我们会成为骗子,而我们的“证明”将变得毫无价值。
Prolog的本质是,当我们向前迈向证明我们的目标,并做出关于什么是什么的各种决定时,我们将这些价值收集到什么是替换,变量和它们之间的映射价值观,这不是自相矛盾的;当我们达到最终目标时,我们的变量的这组值就是我们的答案。
但是当我们失败并且必须回溯时,我们迄今为止所做的所有实例都以相反的顺序撤消。
具体而言,NumCirc =:= RealNum
不是作业。这是一个数字比较,可能是真或假。如果确实如此,我们会说目标NumCirc =:= RealNum
成功(已经过验证)。如果它是假的,我们说目标失败,因此我们回溯。
另一个关键概念是unification:当我们说X = Y
时,我们声称它们都是相同的。当然4=4
成功,3=4
失败。
是的,Prolog本质上是set-once language(没有回溯,撤消分配,而不是更改它,使变量未分配< / em>再次)。
答案 2 :(得分:0)
正如已经指出的那样,常见的prolog习语是使用&#39; helper&#39;使用累加器播种的谓词来完成工作。此外,它有助于将问题分解为更小,更简单的部分。通常,你会拥有我称之为“公共谓词”的内容。除了强制执行约束并调用私人&#39;之外,它并没有做太多的事情。完成所有工作的辅助谓词。通常,辅助谓词与具有不同arity的公共谓词具有相同的名称(foo/3
,而不是公共谓词foo/2
。像这样:
%
% count the number of circular primes below the specified
% upper limit.
%
count_circular_primes( Max , Cnt ) :-
integer(Max) ,
Max >= 1 ,
Max =< 10000 ,
count_circular_primes( Max , 0 , Cnt )
.
这里的助手谓词count_circular_primes/3
使用一个种子值为0的累加器值。它看起来像:
count_circular_primes( 0 , Cnt , Cnt ) .
count_circular_primes( N , T , Cnt ) :-
N > 0 ,
is_circular_prime(N) ,
T1 is T + 1 ,
N1 is N - 1 ,
count_circular_primes(N1,T1,Cnt)
.
您会注意到这是所有计数发生的地方。所有真正要做的就是确定一个特定的整数是否是一个圆形素数。简单!
您还会注意到,在整个递归操作完成之前,结果变量Cnt
不会与任何内容统一。当N
达到零时,累加器T
与结果统一。
现在框架已经到位,让我们考虑如何确定单个整数是否是循环素数。这应该很简单:
这是我如何进行列表轮换。回溯将产生整个列表轮换:
rotate(Xs,Ys) :-
append(A,[B|C],Xs) , % get the prefix (A) , suffix (C) and current item (B) from Xs
append(C,A,D) , % append the suffic (C) to the prefix (A) to get the leftmost bit (B)
append([B],D,Ys) % append the current item (B) to the leftmost bit (D) to get the rotation
.
以下是它的工作原理:append/3
可用于为指定列表生成所有可能的前缀/后缀组合。如果我说append(Prefix,Suffix,[1,2,3,4])
通过回溯返回的完整解决方案集是
Prefix Suffix
========= =========
[] [1,2,3,4]
[1] [2,3,4]
[1,2] [3,4]
[1,2,3] [4]
[1,2,3,4] []
一旦您可以生成轮换,findall/3
可用于在给定特定输入的情况下将整个解决方案设置为列表。因此,鉴于上述rotate/2
谓词,我可以说:
findall( X , rotate( [1,2,3,4] , X ) , Rotations ).
Rotations
将与此列表列表统一:
[ [1,2,3,4] , [2,3,4,1] , [3,4,1,2] , [4,1,2,3] ]
一旦你有了一组旋转,你所要做的就是评估它是否代表素数。您可以使用forall/2
执行此操作,其中包括:
forall( member(Rotation,Rotations), is_prime(Rotation) )
如果您不允许使用findall/3
和forall/2
,事情会变得有点棘手,但我认为这会让您继续前进。