% Some simple test Prolog programs
% --------------------------------
% Knowledge bases
loves(vincent, mia).
loves(marcellus, mia).
loves(pumpkin, honey_bunny).
loves(honey_bunny, pumpkin).
jealous(X, Y) :-
loves(X, Z),
loves(Y, Z).
/** <examples>
?- loves(X, mia).
?- jealous(X, Y).
*/
网址:SWISH -- SWI-Prolog for SHaring
产生式规则
基本形式: A → B 或者 IF A THEN B
〈前件〉→〈后件〉 其中, 前件就是前提, 后件是结论或动作
• 前件和后件可以是由逻辑运算符AND、OR、NOT组 成的表达式
• 语义: 如果前提满足,则可得结论或者执行相应 的动作, 即后件由前件来触发。
• 所以, 前件是规则的执行条件, 后件是规则体。
P1:消去$符号改为*
P2:使用*来消掉$
P3:*与字母的位置兑换
P4:*的消除
P5:字母顺序位置的交换。
P6:增加$。
可触发规则:当一个规则的前件被综合数据库中的数据满足时,该规则称为可触发规则
被触发规则:从可触发规则中选择一个规则来执 行,被执行的规则称为被触发规则
1. A、B是已知的条件,一开始就在综合数据库中。此时只有规则1是可触发的。由于只有一个可触发规则,所以选择 规则1执行。规则1的执行结果得到C,C被加入到综合数据 库中
2. 由于有了C,使得规则2和规则3成为可触发规则(此时规 则1的前件虽然也同样可以被满足,由于该规则已经被执 行过,而且其当前的触发条件并没有改变,与他被执行时 的条件是一样的,所以规则1不在可触发规则之列)。
3. 此时可触发规则有两条,按照顺序排队策略,排在前面的 规则优先执行,所以选择规则2为被触发规则。规则2的执 行结果产生了D,D被加入到综合数据库中。
4. 依次类推,规则3、规则5和规则4先后被执行,最终产生 了F。从而F被求得,结束运行
产生式系统分类
(一)按推理方向分类
1.正向推理
利用事实与规则的前提相匹配,触发匹 配成功的规则,把其结论作为新的事实添加到总数据 库中。
2.逆向推理
3.双向推理
(二)按搜索策略分类
1.不可撤回方式 2.试探性方式 (1)回溯方式 (2)图搜索方式 产
程序实现--Prolog基本语法
一、基本语法
Terms(术语)
基本符号、数据对象 represent data objects. There are 3 types of terms三类:
-
Atoms(原子,常量):
symbolic atoms begin with lower-case letter ex. tom, bill, a1 表示一些人事物的名字(要求小写字母开头)
numeric atoms ex. 217, -32, 2.76 数字
Variables(变量):
- begin with upper-case letter or underscore 用大写字母开头或者下划线
- ex. X, U, _x1, Tom, A1
Structures(结构):
two types:
- function structure: f(t1, ..., tn) 函数结构:函数名称(函数变量/常量)
- eg.edge(3,7), f(g(1),h(2,3))
- list structure: [t1, ..., tn] where f is a symbolic atom called functor and t1, ..., tn are terms
- ex. [a,b,c], [tom,X,f(tom)], [], [[a],[b]] 列表结构
Special Notation for lists: [c1, ..., cm|T] where c1, ..., cm are the first m elements of the list and T is a LIST of the remaining elements表示剩下的T个元素我们不关注。
(II) Relation(关系):
- r(t1, ..., tn) where r is a symbolic atom and t1, ..., tn are terms.
- ex. round, father(tom,bill), student(1111,jones,freshman,4.0)
(III) A Prolog Program consists of a finite number of Facts and Rules,
where
-
Fact: relation. 事实
-
Rule: relation 那么:- 如果relation-1, ..., relation-n.规则
(IV) 查询Query:
?- relation-1, ..., relation-n. used to invoke a program.
如:从数组中删除一个元素
del(X,[X|Tail],Tail).
del(X,[Y|Tail],[Y|Tail1]) :- del(X,Tail,Tail1).
?- del(c,[a,b,c,d,e,f],Y)
Call:del(c,[a, b, c, d, e, f],_4372)匹配产生式左边递归调用右边
Call:del(c,[b, c, d, e, f],_700)
Call:del(c,[c, d, e, f],_706)匹配上了事实
Exit:del(c,[c, d, e, f],[d, e, f])逐步返回
Exit:del(c,[b, c, d, e, f],[b, d, e, f])
Exit:del(c,[a, b, c, d, e, f],[a, b, d, e, f])
Y = [a, b, d, e, f]
二、例子
例1: (Facts)
Rules & Facts
man(mia).
man(jody).
man(yolanda).
playsAirGuitar(jody).
party.
Query:
?- man(mia).
?- playsAirGuitar(jody).
?- playsAirGuitar(mia).
?- tattoed(jody).
?- party.
?- RockConcert.
例2:(Rule)
happy(yolanda).
listens2music(mia).
listens2music(yolanda):- happy(yolanda).
playsAirGuitar(mia):- listens2music(mia).
playsAirGuitar(yolanda):- listens2music(yolanda).
Query:
?- playsAirGuitar(mia).
也是从查询进入开始匹配,逆向开始推理,直到推出的能与已有事实匹配,再逐步回退
?- playsAirGuitar(yolanda).
例3:(合取,析取)
合取就直接逗号,要两条都满足才能推理,析取就分开写或者使用分号。
happy(vincent).
listens2music(butch).
playsAirGuitar(vincent):- listens2music(vincent), happy(vincent).
playsAirGuitar(butch):- happy(butch).
playsAirGuitar(butch):- listens2music(butch).
Query:
?- playsAirGuitar(vincent).
合取的逗号会在同一层调用两个知识去验证。
?- playsAirGuitar(butch).
简化一下:使用分号
happy(vincent).
listens2music(butch).
playsAirGuitar(vincent):- listens2music(vincent), happy(vincent).
playsAirGuitar(butch):- happy(butch); listens2music(butch).
例4:(疑问句query)
woman(mia).
woman(jody).
woman(yolanda).
loves(vincent, mia).
loves(marsellus, mia).
loves(pumpkin, honey_bunny).
loves(honey_bunny, pumpkin).
Query:
?- woman(X).
?- loves(marsellus,X), woman(X).
?- loves(pumpkin,X), woman(X).
查询中,逗号也是同时满足条件,先匹配一个,回退之后再匹配另一个。
例5:
loves(vincent,mia).
loves(marsellus,mia).
loves(pumpkin, honey_bunny).
loves(honey_bunny, pumpkin).
jealous(X,Y):- loves(X,Z), loves(Y,Z).
Query;
?- jealous(marsellus,W).
大写字母表示未知变量,不断回退匹配事实求出未知变量。
例6:(Unification 实例化)
vertical( line(point(X,Y), point(X,Z))).
horizontal( line(point(X,Y), point(Z,Y))).
?- vertical(line(point(1,1),point(1,3))).
?- vertical(line(point(1,1),point(3,2))).
?- horizontal(line(point(1,1),point(1,Y))).
?- horizontal(line(point(2,3),Point)). Point = point(_1704,3)
例7:(递归recursion)
isDigesting(X,Y):- justAte(X,Y).
isDigesting(X,Y):- justAte(X,Z), isDigesting(Z,Y). 规则
是递归,普通的两条规则
justAte(mosquito,blood(john)).
justAte(frog,mosquito).
justAte(stork,frog). 事实
Query:
?- isDigesting(stork,mosquito).
Call:isDigesting(stork,blood(john))始终从第一条规则开始匹配
Call:justAte(stork,blood(john))
Fail:justAte(stork,blood(john))
Redo:isDigesting(stork,blood(john))
Call:justAte(stork,_644)
Exit:justAte(stork,frog)
Call:isDigesting(frog,blood(john))
Call:justAte(frog,blood(john))
Fail:justAte(frog,blood(john))
Redo:isDigesting(frog,blood(john))
Call:justAte(frog,_646)
Exit:justAte(frog,mosquito)
Call:isDigesting(mosquito,blood(john)) 两个交集条件
Call:justAte(mosquito,blood(john))匹配上上面那条单个的条件
Exit:justAte(mosquito,blood(john))
Exit:isDigesting(mosquito,blood(john))
Exit:isDigesting(frog,blood(john))
Exit:isDigesting(stork,blood(john))
又如:
child(anna,bridget).
child(bridget,caroline).
child(caroline,donna).
child(donna,emily).
descend(X,Y):- child(X,Y).
descend(X,Y):- child(X,Z), child(Z,Y). 不是递归,普通的两条规则
?- descend(anna,donna). (有结果吗?)
又如:
child(anna,bridget).
child(bridget,caroline).
child(caroline,donna).
child(donna,emily).
descend(X,Y):- child(X,Y).
descend(X,Y):- child(X,Z), child(Z,Y).
descend(X,Y):- child(X,Z), child(Z,U), child(U,Y). 三代,也不是递归
?- descend(anna,danna). (有结果吗?)
又如:
child(anna,bridget).
child(bridget,caroline).
child(caroline,donna).
child(donna,emily).
descend(X,Y):- child(X,Y).
descend(X,Y):- child(X,Z), descend(Z,Y).递归正确写法
?- descend(anna,donna). (有结果吗?)
例子:
定义数字succ后继
numeral(0). 事实
numeral(succ(X)):- numeral(X). 规则
?- numeral(succ(succ(succ(0)))). true
逐步回退,只要最终匹配上了,那么就一定能够推导过来。
?- numeral(X). X = 0
例子:
加法
add(0,X,X).
add(succ(X),Y,succ(Z)):- add(X,Y,Z). 把第一个的succ加到第二个上变成第三个
?- add(0,succ(succ(succ(0))), Result). Result = succ(succ(succ(0)))
?- add(succ(0),succ(succ(succ(0))), Result).
Call:add(succ(0),succ(succ(succ(0))),_4288)匹配上了左边的规则,然后递归
Call:add(0,succ(succ(succ(0))),_672)匹配上了事实,得到Z
Exit:add(0,succ(succ(succ(0))),succ(succ(succ(0))))
Exit:add(succ(0),succ(succ(succ(0))),succ(succ(succ(succ(0)))))
Result = succ(succ(succ(succ(0))))
?- add(succ(succ(0)),succ(succ(succ(0))), Result). 匹配三次然后回退
Call:add(succ(succ(0)),succ(succ(succ(0))),_4500)
Call:add(succ(0),succ(succ(succ(0))),_676)
Call:add(0,succ(succ(succ(0))),_680)
Exit:add(0,succ(succ(succ(0))),succ(succ(succ(0))))
Exit:add(succ(0),succ(succ(succ(0))),succ(succ(succ(succ(0)))))
Exit:add(succ(succ(0)),succ(succ(succ(0))),succ(succ(succ(succ(succ(0))))))
Result = succ(succ(succ(succ(succ(0)))))
例8:(列表List)
列表头head一个元素和列表尾tail未分配的所有元素:
?- [Head|Tail] = [mia, vincent, jules, yolanda].
Head = mia,
Tail = [vincent, jules, yolanda]
?- [X|Y] = [ ].空列表不能用|来界定第一个元素
false
?- [X,Y|Tail] = [[ ], dead(z), [2, [b,c]], [], Z, [2, [b,c]]] .
Tail = [[2, [b, c]], [], Z, [2, [b, c]]],
X = [],
Y = dead(z)
?- [X1,X2,X3,X4|Tail] = [mia, vincent, marsellus, jody, yolanda].
Tail = [yolanda],
X1 = mia,
X2 = vincent,
X3 = marsellus,
X4 = jody
?- [ _,X2, _,X4|_ ] = [mia, vincent, marsellus, jody, yolanda]. 下划线位置的元素不关心,占位
X2 = vincent,
X4 = jody
成员判断
member(X,[X|T]).
member(X,[H|T]):- member(X,T). 递归不断往后查
?- member(yolanda,[yolanda,trudy,vincent,jules]).
?- member(zed,[yolanda,trudy,vincent,jules]).
?- member(X,[yolanda,trudy,vincent,jules]). X = yolanda
member(trudy,[yolanda,trudy,vincent,jules]).
Call:member(trudy,[yolanda, trudy, vincent, jules])
Call:member(trudy,[trudy, vincent, jules])
Exit:member(trudy,[trudy, vincent, jules])
Exit:member(trudy,[yolanda, trudy, vincent, jules])
1true
等长a序列和b序列:
a2b([],[]).
a2b([a|L1],[b|L2]):- a2b(L1,L2).
?- a2b([a,a,a,a],[b,b,b]). false
?- a2b([a,t,a,a],[b,b,b,c]). false,这里的a,b是小写指的是具体元素,而不是泛指的未知数。
上面那条规则改成
a2b([X |L1],[Y |L2]):- a2b(L1,L2).
则此查询为正确。
?- a2b([a,a,a,a,a], X).
X = [b, b, b, b, b]
Call:a2b([a, a, a, a, a],_4246)
Call:a2b([a, a, a, a],_682)
Call:a2b([a, a, a],_688)
Call:a2b([a, a],_700)
Call:a2b([a],_712)
Call:a2b([],_706)
Exit:a2b([],[])
Exit:a2b([a],[b])
Exit:a2b([a, a],[b, b])
Exit:a2b([a, a, a],[b, b, b])
Exit:a2b([a, a, a, a],[b, b, b, b])
Exit:a2b([a, a, a, a, a],[b, b, b, b, b])
X = [b, b, b, b, b]
不断递归,直到情况能够满足事实,再回退
a2b([a,a,a,a],[b,b,b,b]).
true
例9 算术1
?- 10 is 5+5.
?- 4 is 2+3.
?- X is 3 * 4.
?- R is mod(7,2).
又如:
?- X = 3 + 2.
只有is才会计算。
例9 算术2
等于=:=
不等于 =\=
小于等于=<
大于等于>=
?- 2 < 4+1.
?- 4+3 > 5+5.
?- 4 = 4.
TRUE
?- 2+2 = 4.
FALSE
?- 2+2 =:= 4.
TRUE
在prolog中,单个等号是不具备数据运算功能的。
例如:
addThreeAndDouble(X, Y):- Y is (X+3) * 2.
?- addThreeAndDouble(1,X).
?- addThreeAndDouble(2,X).
最后:
?- is(X,+(3,2)).
开始做题
如何统计List的长度?
len([],0).
len([_|L],N):- len(L,X), N is X + 1.
Len([3,4,5,6,7,8,9],X)
给定List,如何寻找最大的数
析取,两种情况
accMax([H|T],A,Max):- H > A, accMax(T,H,Max).
accMax([H|T],A,Max):- H =< A, accMax(T,A,Max).始终选择较大的一个放在中间位置上
accMax([],A,A).
遍历完了就把第二个位置上的值给MAX
?- accMax([1,0,5,4],0,Max).
或者添加:max([H|T],Max):- accMax(T,H,Max).
?- max([1,0,5,4], Max).
?- max([-3, -1, -5, -4], Max).
例10:字符串?
如:怎么拼接两个字符串
append([ ], L, L).
append([H|L1], L2, [H|L3]):- append(L1, L2, L3).
?- append([a,b,c],[1,2,3], R).
Call:append([a, b, c],[1, 2, 3],_4316)
Call:append([b, c],[1, 2, 3],_698)
Call:append([c],[1, 2, 3],_704)
Call:append([],[1, 2, 3],_710)
Exit:append([],[1, 2, 3],[1, 2, 3])
Exit:append([c],[1, 2, 3],[c, 1, 2, 3])
Exit:append([b, c],[1, 2, 3],[b, c, 1, 2, 3])
Exit:append([a, b, c],[1, 2, 3],[a, b, c, 1, 2, 3])
R = [a, b, c, 1, 2, 3]
相关应用:
分裂
?- append(X,Y, [a,b,c,d]).
第一次匹配上了的规则路线,第二次redo不会再走
前缀:
prefix(P,L):- append(P,_,L).
后缀:
suffix(S,L):- append(_,S,L).
子集:
sublist(Sub,List):- suffix(Suffix,List), prefix(Sub,Suffix).
挑战:
如何翻转一个字符串?
append([ ], L, L).
append([H|L1], L2, [H|L3]):- append(L1, L2, L3).
naiveReverse([],[]).
naiveReverse([H|T],R):- naiveReverse(T,RT), append(RT,[H],R).
append是把字符串拼接
这里,[RT]和[H]逆着拼接就可以实现字符串翻转
可以更高效一些吗?
accReverse([ ],L,L).
accReverse([H|T],Acc,Rev):- accReverse(T,[H|Acc],Rev).
reverse(L1,L2):- accReverse(L1,[ ],L2).
trace, (reverse([a,b,c,d],X)).
Call:reverse([a, b, c, d],_4602)
Call:accReverse([a, b, c, d],[],_474)
Call:accReverse([b, c, d],[a],_474)
Call:accReverse([c, d],[b, a],_474)
Call:accReverse([d],[c, b, a],_474)
Call:accReverse([],[d, c, b, a],_474)中间那个位置存储的倒序的中间过程
Exit:accReverse([],[d, c, b, a],[d, c, b, a])只有当第一个字符串被读完了,才拷贝到最终位置上
Exit:accReverse([d],[c, b, a],[d, c, b, a])
Exit:accReverse([c, d],[b, a],[d, c, b, a])
Exit:accReverse([b, c, d],[a],[d, c, b, a])
Exit:accReverse([a, b, c, d],[],[d, c, b, a])
Exit:reverse([a, b, c, d],[d, c, b, a])
X = [d, c, b, a]
错误示范:
accReverse([],X).
accReverse([H|T],Acc):- accReverse(T,[H|Acc]).没有第三个变量在满足条件的时候保存,它会随着循环回退返回初始状态。
始末状态始终一致,未知部位除外。我们需要第三个在循环中不变的变量,在达到某个条件时(比如第一个列表为空了),将第二个位置的值拷贝过去,这样在回退过程中,第二个位置的值逐步恢复,第三个位置的不变。
call:从左往右。
exit:回退,从右往左。