Prolog 人工智能语言中文论坛---打造优质Prolog学习交流园地

一个供Prolog爱好者学习与交流的地方


您没有登录。 请登录注册

想写一个for谓词,可是……

浏览上一个主题 浏览下一个主题 向下  留言 [第1页/共1页]

1 想写一个for谓词,可是…… 于 周一 十月 15, 2012 3:16 pm

常常在写prolog的时候,会需要用到循环,
prolog里没有for语法还是挺不方便的,
每次重复做一件事就得用递归来写(或者是我功力不够?),
所以我试着想把prolog的循环标准化,
目前写的for谓词如下:

代码:
for(Predicate, Index_From, Index_To) :- for(Predicate, Index_From, Index_To, 1).

for(Predicate, Index_From, Index_To, Index_Step) :-
  nonvar(Predicate),
  (Index_From =< Index_To -> Predicate =.. X, replace(X, 2, Index_From, Y), Z =.. Y, Z,
  Next_Index is Index_From + Index_Step, for(Z, Next_Index, Index_To, Index_Step); true).

replace(List, Index, With, ListOut) :-
  Idx is Index - 1, length(Before,Idx),
  append(Before, [_Discard|Rest], List),
  append(Before, [With|Rest], ListOut).

当你想执行下面这段代码执行10遍时(意即从1印到10):

代码:
wri(I) :- write(I), nl.

你只要查询

代码:
?- for(wri(I), 1, 10).  % 意即C++语法:for(i=1;i<=10;i++){wri(i);} 或VBA语法:For i = 1 to 10: wri(i): Next

就可得到你想要的结果。


这固然可以满足我们的部分需求。
但有时这样不能满足我们的要求,我们可能想求List的和,比如[1,2,3,4,5]加总(假设不能用sumlist谓词),
得到1+2+3+4+5=15,
这时候如果仿照C++或VBA,你可以用一个容器变量C,
C一开始等于0,然后I = 1, C = C + I, I = 2, C = C + I……
最后C就会等于15。

但这里无法,因为对每个wri谓词来说,C都是独立的,
我还没找到一个可以不用assert与retract的方法可以达到完美的for,
有没方法可以弥补这个缺陷,让查询如for(wri(I, C), 1, 10)时,C能够"记得"每次加总后的结果?

查阅用户资料 http://prolog.longluntan.net

2 回复: 想写一个for谓词,可是…… 于 周一 十月 15, 2012 9:29 pm

大家都知道,将循环展开可以找到for逻辑
代码:

//C程序
for (i=0; i<10; i++) { s += a[i]; }
//与下列程序相同
s += a[0]; for (i=1; i<10; i++) { s += a[i]; }

%於是prolog程序如此
for(Init, Term, Step, Goal) :- Init < Term, call(G), !,
    Init1 is Init+1, for(Init1, Term, Step, Goal).
for(_, _, _, _).
在cut之前做一个步骤,假如这一步结果是false,要让它往下得到一个true,代表循环中断而正确结束。执行情况如此
代码:

?- for(0, 3, 1, (write('hello,world'), nl)).
hello,world
hello,world
hello,world
true
可是,如果要突然放一则list譬如[3.5, 4.6]进去,要它加总到一个变量Accu,
代码:

for(I, T, S, Goal, Accu) :- ...
会发现Goal内容中要参考Accu变量,但我们找不到方法将二者连起来,除非能找到一个谓词bind(Accu,Goal)将Goal的其中一个变量与Accu连在一起。不过,就我们对C程序员写作方式的理解,只要我们知道以上prolog for/4的构成,就能带着这样的知识来写各种循环程序。例如加总列表,
代码:

forLoopSum(Init, Term, Step, List, Result) :- Init < Term, nth0(Init, List, R), !,
    Init1 is Init+1, forLoopSum(Init1, Term, Step, List, A),
    Result is R+A.
forLoopSum(_, _, _, _, 0).

?- forLoopSum(0, 2, 1, [3.5, 4.6], R).
R=8.1
其实C程序没有将循环结构和它的内部结构划分乾净,循环的特色就是交缠纠结、乱,所以我们用prolog制作循环结构时,也不必要求彻底漂亮、乾净。

而以上我的程序还有个缺点是,凡遇到一步查询失败,就往下走第二个字句,这表示中断循环。至於C程序的循环,是该在一步不能正确运行时,要嘛crash要嘛跳过。这个语意稍微复杂了,暂且不加进来讨论。额外更需要讨论的,是prolog程序有分支查询的特性,循环的主体算式要控制好,免得分支查询扰乱了原先计算的主题。

查阅用户资料 http://yauhsien.wordpress.com

3 回复: 想写一个for谓词,可是…… 于 周二 十月 16, 2012 4:52 pm

我觉得这个议题非常有趣,以前也曾努力思考过。

换一个观点,以C程序来说(即所谓imperative language),每一行句子之间存在一种无形的物件称为「状态」。

若以prolog实现for循环,求一则列表[1,2,...,10]加总,则是目的要制作以下的字句,
代码:

?- (sum(0,1,S1), sum(S1,2,S2), sum(S2,3,S3), ..., sum(S9,10,S10)).
於是,prolog实现for/*是想要获得
代码:

%for(-Environment, -Init, -Term, -Step, -sum/3, List, +Environment1)
?- for(0, 0, 10, 1, sum/3, [1,3,5], Sum).
那当写下第一则for/7字句时,大概是这样
代码:

for(Env, I, T, S, G, L, EnvN) :-
    ...,
    %G在此想做sum/3
    %要用到SWI的meta-谓词做出 nth0(I,L,V), G(Env,V,Env1)
    %然後可以传递下去
    I1 is I+1,
    for(Env1, I1, T, S, G, L, EnvN).
可问题就在G/?到底有多少个参数?以加总来说,G是sum而参数当然是3个,换是求方阵变换则有不同数量的参数。

或许可有个办法定义G/?,我直接想到定义G/n,而所需要的参数放在L以列表传递、长度应该是n-2。而for/7内容则设法将Env,L,Env1送给G作参数。

查阅用户资料 http://yauhsien.wordpress.com

4 回复: 想写一个for谓词,可是…… 于 周二 十月 16, 2012 6:48 pm

经过我不断的试验,找出了目前我能写出的最方便的形式:

代码:
for(Predicate, Index_From, Index_To) :- for(Predicate, Index_From, Index_To, 1).

for(Predicate, Index_From, Index_To, Index_Step) :-
  nonvar(Predicate),
  (Index_From =< Index_To -> Predicate =.. X, replace(X, 2, Index_From, Y), replace(Y, 3, _, M),
  Z =.. M, Z, arg(2, Z, Return), replace(Y, 4, Return, W), V =.. W,
  Next_Index is Index_From + Index_Step, for(V, Next_Index, Index_To, Index_Step); arg(3, Predicate, A), arg(2, Predicate, A)).

replace(List, Index, With, ListOut) :-
  Idx is Index - 1, length(Before,Idx),
  append(Before, [_Discard|Rest], List),
  append(Before, [With|Rest], ListOut).


假设要做1*1+2*2+3*3+......+10*10,则for的内容可写为:

代码:
wri(I, [C1], [C]) :- C1 is C + I * I.

这里要说明的是,要执行的Goal(在这里是wri)第2个参数统一定为传回值,第3个参数统一定为传入值。
C1和C其实代表同一个静态变量,只是碍于prolog的规则,这里必须以2个变量的形式出现,
C是累加到上一次的值,C1是C加上这次运算后的值。
如果需要的静态变量有2个,可写为wri(I, [C1, D1], [C, D]),依此类推。
也可以没有静态变量:wri(I, [], [])。


使用for谓词时写:

代码:
?- for(wri(I, [C1], [0]), 1, 10).  % [0]表示静态变量的初始值为0

结果为
C1 = 385.

就是我们要的答案。


如果不需要静态变量则写:

代码:
?- for(wri(I, [], []), 1, 10).

则根据本贴最一开始的例子,能得到:

1
2
3
4
5
6
7
8
9
10
true.

仍然可以满足我们的需求。


最后,也可以传入额外的参数如下例:

假设wri谓词写成:

代码:
wri(I, [C1], [C], X, Y) :- nth1(I, X, A), nth1(I, Y, B), C1 is C + A * B.  %X与Y对应项相乘再加总

那么执行查询

代码:
?- for(wri(I, [C1], [0], [1, 2, 3], [4, 5, 6]), 1, 3).

可得到:

C1 = 32.

(这正是我们想要的,因为1*4+2*5+3*6=32)

查阅用户资料 http://prolog.longluntan.net

5 回复: 想写一个for谓词,可是…… 于 周二 十月 16, 2012 8:10 pm

Mercury Liao 写道::经过我不断的试验,找出了目前我能写出的最方便的形式:

代码:
for(Predicate, Index_From, Index_To) :- for(Predicate, Index_From, Index_To, 1).

for(Predicate, Index_From, Index_To, Index_Step) :-
  nonvar(Predicate),
  (Index_From =< Index_To -> Predicate =.. X, replace(X, 2, Index_From, Y), replace(Y, 3, _, M),
  Z =.. M, Z, arg(2, Z, Return), replace(Y, 4, Return, W), V =.. W,
  Next_Index is Index_From + Index_Step, for(V, Next_Index, Index_To, Index_Step); arg(3, Predicate, A), arg(2, Predicate, A)).

replace(List, Index, With, ListOut) :-
  Idx is Index - 1, length(Before,Idx),
  append(Before, [_Discard|Rest], List),
  append(Before, [With|Rest], ListOut).
说得不错,可我临时没读懂你写的结构的一部分,但我看出一个亮点是arg/3,可将参数带入给谓词。我们都需要这个谓词来实现类似C程序的写法。

查阅用户资料 http://yauhsien.wordpress.com

6 回复: 想写一个for谓词,可是…… 于 周三 十月 17, 2012 1:57 am

yauhsien 写道::
Mercury Liao 写道::经过我不断的试验,找出了目前我能写出的最方便的形式:

代码:
for(Predicate, Index_From, Index_To) :- for(Predicate, Index_From, Index_To, 1).

for(Predicate, Index_From, Index_To, Index_Step) :-
  nonvar(Predicate),
  (Index_From =< Index_To -> Predicate =.. X, replace(X, 2, Index_From, Y), replace(Y, 3, _, M),
  Z =.. M, Z, arg(2, Z, Return), replace(Y, 4, Return, W), V =.. W,
  Next_Index is Index_From + Index_Step, for(V, Next_Index, Index_To, Index_Step); arg(3, Predicate, A), arg(2, Predicate, A)).

replace(List, Index, With, ListOut) :-
  Idx is Index - 1, length(Before,Idx),
  append(Before, [_Discard|Rest], List),
  append(Before, [With|Rest], ListOut).
说得不错,可我临时没读懂你写的结构的一部分,但我看出一个亮点是arg/3,可将参数带入给谓词。我们都需要这个谓词来实现类似C程序的写法。

没关系,我待会写一下我的程序思路。

我昨天躺床上睡之后又在想,
能否像你说的那样纳入break、continue的规则,
(C++语法是写continue吧有点忘了?)

目前想到的是for里面在呼叫Goal的时候无论成功与否都继续执行,
那我们在编写Goal的时候用false应该就可以等于continue的功能(结束本次迭代直接进入下一次迭代)。
而break就比较麻烦了,目前想到的方样可能得增加一个约定的变量,
比如统一规定for的第4个参数为Break参数,
(前3个参数都已经保留给特定的意思了,第1个是I,第2个是静态变量传回值,第3个是静态变量初始值),
每次迭代完成后回到for,for会检查Break是否绑定为1,是则中止for跳出。

不知道这样行不行或是有没有更好的做法。

查阅用户资料 http://prolog.longluntan.net

7 回复: 想写一个for谓词,可是…… 于 周三 十月 17, 2012 4:13 am

yauhsien 写道::
Mercury Liao 写道::经过我不断的试验,找出了目前我能写出的最方便的形式:

代码:
for(Predicate, Index_From, Index_To) :- for(Predicate, Index_From, Index_To, 1).

for(Predicate, Index_From, Index_To, Index_Step) :-
  nonvar(Predicate),
  (Index_From =< Index_To -> Predicate =.. X, replace(X, 2, Index_From, Y), replace(Y, 3, _, M),
  Z =.. M, Z, arg(2, Z, Return), replace(Y, 4, Return, W), V =.. W,
  Next_Index is Index_From + Index_Step, for(V, Next_Index, Index_To, Index_Step); arg(3, Predicate, A), arg(2, Predicate, A)).

replace(List, Index, With, ListOut) :-
  Idx is Index - 1, length(Before,Idx),
  append(Before, [_Discard|Rest], List),
  append(Before, [With|Rest], ListOut).
说得不错,可我临时没读懂你写的结构的一部分,但我看出一个亮点是arg/3,可将参数带入给谓词。我们都需要这个谓词来实现类似C程序的写法。

其实思路挺简单的,
Predicate =.. X 是把Goal以List形式表达并指定给X,
replace(X, 2, Index_From, Y) 是把X的第2个参数替换成I的值,输出结果为Y。注意X的第1个参数是Goal的名称,第2个才是I,
replace(Y, 3, _, M) 是把Y的第3个参数设为变量。因为原本变量是[C1],但执行完Goal之后C1会被绑定,但这不是我们想要的,我们想要的其实是整个循环跑完后再绑定C1,所以这里先替换掉,以另一个变量来代替,变量叫什么不重要所以我用的昵名变量(但这里为了要讲思路,姑且叫它做Ni)。
接下来也比较好理解,就是把替换完的M变回谓词表述形式Z,然后执行一遍Z,
执行完后Ni已经绑定为执行一次迭代后的值了,
arg(2, Z, Return) 把Ni取出给Return,
replace(Y, 4, Return, W) 把Y的第4个参数替换成Return(Y的第4个参数其实就是wri(I, [C1], [C])的第3个参数),因为下一次的计算是要在前一次的计算结果,也就是C1(如前所述,我们已换掉了,其实是Ni)上继续做的。

后面的应该都很简单了,就是进行下一次迭代。
arg(3, Predicate, A), arg(2, Predicate, A)) 是在I已到达上限时做的事,先把C取出来给A,把A指定给C1回传。

查阅用户资料 http://prolog.longluntan.net

8 回复: 想写一个for谓词,可是…… 于 周三 十月 17, 2012 4:25 am

前面写的形式只有在Index_Step为正的时候才对,下面做了修正,
使Index_Step为负时也能适用。

代码:
for(Predicate, Index_From, Index_To) :- for(Predicate, Index_From, Index_To, 1).

for(Predicate, Index_From, Index_To, Index_Step) :-
  nonvar(Predicate),
  ((Index_Step > 0 -> Index_From =< Index_To; Index_From >= Index_To) -> Predicate =.. X, replace(X, 2, Index_From, Y), replace(Y, 3, _, M),
  Z =.. M, Z, arg(2, Z, Return), replace(Y, 4, Return, W), V =.. W,
  Next_Index is Index_From + Index_Step, for(V, Next_Index, Index_To, Index_Step); arg(3, Predicate, A), arg(2, Predicate, A)).

replace(List, Index, With, ListOut) :-
  Idx is Index - 1, length(Before,Idx),
  append(Before, [_Discard|Rest], List),
  append(Before, [With|Rest], ListOut).

查阅用户资料 http://prolog.longluntan.net

9 回复: 想写一个for谓词,可是…… 于 周三 十月 17, 2012 1:40 pm

Mercury Liao 写道::常常在写prolog的时候,会需要用到循环,
prolog里没有for语法还是挺不方便的,
每次重复做一件事就得用递归来写(或者是我功力不够?),
所以我试着想把prolog的循环标准化,
目前写的for谓词如下:

代码:
for(Predicate, Index_From, Index_To) :- for(Predicate, Index_From, Index_To, 1).

for(Predicate, Index_From, Index_To, Index_Step) :-
  nonvar(Predicate),
  (Index_From =< Index_To -> Predicate =.. X, replace(X, 2, Index_From, Y), Z =.. Y, Z,
  Next_Index is Index_From + Index_Step, for(Z, Next_Index, Index_To, Index_Step); true).

replace(List, Index, With, ListOut) :-
  Idx is Index - 1, length(Before,Idx),
  append(Before, [_Discard|Rest], List),
  append(Before, [With|Rest], ListOut).

当你想执行下面这段代码执行10遍时(意即从1印到10):

代码:
wri(I) :- write(I), nl.

你只要查询

代码:
?- for(wri(I), 1, 10).  % 意即C++语法:for(i=1;i<=10;i++){wri(i);} 或VBA语法:For i = 1 to 10: wri(i): Next

就可得到你想要的结果。


这固然可以满足我们的部分需求。
但有时这样不能满足我们的要求,我们可能想求List的和,比如[1,2,3,4,5]加总(假设不能用sumlist谓词),
得到1+2+3+4+5=15,
这时候如果仿照C++或VBA,你可以用一个容器变量C,
C一开始等于0,然后I = 1, C = C + I, I = 2, C = C + I……
最后C就会等于15。

但这里无法,因为对每个wri谓词来说,C都是独立的,
我还没找到一个可以不用assert与retract的方法可以达到完美的for,
有没方法可以弥补这个缺陷,让查询如for(wri(I, C), 1, 10)时,C能够"记得"每次加总后的结果?

这种n=n+1的想法让我纠结了很久呢,因为玩全排列的时候经常要用到计数,苦于一直找不到方法,堪称入门第一盆冷水。。。。。。。。。。。。。。
后来计数都用你教assert与retract的方法,也很好用
assert(count(0)),count(N),retract(count(0)),NN is N+1,write(NN),asserta(count(NN)),(NN>1->retract(count(N));true),每当用到时,就复制粘贴 bounce

查阅用户资料

10 回复: 想写一个for谓词,可是…… 于 周三 十月 17, 2012 2:14 pm

用prolog写C程序的句子,我较喜欢句式长得非常像,所以我的写法为
代码:

for(Init, Term, Step, N, Op, List, Result) :-
    Init < Term, nth0(Init, List, H), !,
    apply(Op, [N, H, R]),
    I1 is Init+Step,
    for(I1, Term, Step, R, Op, List, Result).
for(_, _, _, Result, _, _, Result).

sum(A, B, C) :- C is A+B.

?- for(0, 10, 1, 0, sum, [1,2,3,4,5,6,7,8,9,10], R).
R = 55.
而乘积之总和这种双重运算,则写成以下
代码:

for(Init, Term, Step, N, Op=Op1, List, List1, Result) :-
    Init < Term, nth0(Init, List, H), nth0(Init, List1, H1), !,
    apply(Op1, [H, H1, R]),
    apply(Op, [N, R, R1]),
    I1 is Init+Step,
    for(I1, Term, Step, R1, Op=Op1, List, List1, Result).
for(_, _, _, Result, _, _, _, Result).

multiply(A, B, C) :- C is A*B.

?- for(0, 3, 1, 0, sum=multiply, [1,3,5], [2,4,6], R).
R = 44.
将累积值存在一个累积变量上,最後转存到结果变量,是函数式语言通用的方法。

可缺点就是牺牲一则for/7专处理一种二元谓词,牺牲for/8专处理两种二元谓词的衔接。

查阅用户资料 http://yauhsien.wordpress.com

11 回复: 想写一个for谓词,可是…… 于 周日 十月 21, 2012 5:39 am

我已将prolog谓词整理并放进"扩展工具"里去了,有兴趣的同学可以参考:

http://prolog.longluntan.net/t47-topic

查阅用户资料 http://prolog.longluntan.net

浏览上一个主题 浏览下一个主题 返回页首  留言 [第1页/共1页]

您在这个论坛的权限:
不能在这个论坛回复主题