27、Prolog编程:练习解答、子句形式转换与可移植性探讨

Prolog编程:练习解答、子句形式转换与可移植性探讨

1. 部分练习解答

1.1 家庭关系定义

以下是一些家庭关系的可能定义:

is_mother(Mum) :- mother(Mum, Child).
is_father(Dad) :- father(Dad, Child).
is_son(Son):- parent(Par, Son), male(Son).
sister_of(Sis, Pers) :-
    parent(Par, Sis), parent(Par, Pers), 
    female(Sis), diff(Sis, Pers).
granpa_of(Gpa, X) :- parent(Par, X), father(Gpa, Par).
sibling(Sl,S2) :-
    parent(Par, Sl), parent(Par, S2), diff(Sl, S2).

这里使用了谓词 diff 来避免系统得出某人是自己的姐妹或兄弟姐妹的结论,但现阶段无法定义 diff

1.2 字符处理程序

以下程序会无限读取当前输入文件中的字符,并将其中的 a 替换为 b 后输出:

go :- repeat, get_char(C), deal_with(C), fail.
deal_with(a) :- !, put_char(b).
deal_with(X) :- put(X).

deal_with 的第一条规则中,“cut”( ! )是必不可少的。

1.3 生成勾股数的程序

以下程序用于生成勾股数:

pythag(X, Y, Z) :-
    intriple(X, Y, Z), 
    Sumsq is X*X + Y*Y, Sumsq is Z * Z.
intriple(X, Y, Z) :-
    is_integer(Sum), 
    minus(Sum, X, Suml), minus(Suml, Y, Z).
minus(Sum, Sum, 0).
minus(Sum, Dl, D2) :-
    Sum > 0, Suml is Sum - 1, 
    minus(Suml, Dl, D3), D2 is D3 + 1.
is_integer(0).
is_integer(N) :- is_integer(Nl), N is Nl + 1.

该程序使用谓词 intriple 生成可能的整数三元组 X Y Z ,然后检查该三元组是否为勾股数。

1.4 简单语法规则转换为Prolog

以下是将简单语法规则转换为Prolog的程序,假设规则中不包含带额外参数的短语类型、花括号内的目标、析取或“cut”:

?- op(1199,xfx,~>).
translate((Pl—>P2),(G1:-G2)) :-
    left_hand_side(Pl,SO,S,Gl), 
    right_hand_side(P2,S0,S,G2).
left_hand_side(P0,S0,S,G) :-
    nonvar(PO), tag(P0,S0,S,G).
right_hand_side((Pl,P2),S0,S,G) :-
    right_hand_side(Pl,SO,Sl,Gl), 
    right_hand_side(P2,Sl,S,G2), 
    and(Gl,G2,G).
right_hand_side(P,SO,S,true) :-
    islist(P),
    append(P,S,SO).
right_hand_side(P,SO,S,G) :-
    tag(P,S0,S,G).
tag(P,S0,S,G) :- atom(P), G =..[P,S0,S].
and(true,G,G) :-!.
and(G,true,G) :- !.
and(Gl,G2,(Gl,G2)).
islist([]) :- !.
islist([_|_]).
append([A|B],C,[A|D]) :- append(B,C,D).
append([],X,X).

在这个程序中,以 P 开头的变量代表语法规则中的短语描述,以 G 开头的变量代表Prolog目标,以 S 开头的变量代表Prolog目标的参数。

1.5 通用短语定义

通用版本的 phrase 定义如下:

phrase(Ptype,Words) :-
    Ptype =.. [Pred|Args], 
    append(Args, [Words, []],Newargs), 
    Goal=.. [Pred|Newargs], 
    call(Goal).

其中 append 的定义与之前章节相同。

2. 子句形式程序清单

2.1 整体流程

将公式转换为子句形式的程序顶层如下:

translate(X) :-
    implout(X,Xl),  /* 阶段1 */
    negin(Xl,X2),  /* 阶段2 */
    skolem(X2,X3,[]),  /* 阶段3 */
    univout(X3,X4),  /* 阶段4 */
    conjn(X4,X5),  /* 阶段5 */
    clausify(X5,Clauses),  /* 阶段6 */
    pclauses(Clauses).  /* 打印子句 */

该程序定义了谓词 translate ,若给定目标 translate(X) ,其中 X 代表谓词演算公式,程序将输出该公式的子句表示。

2.2 运算符声明

需要进行以下运算符声明:

?- op(200,fx,~).
?- op(400,xfy,#).
?- op(400,xfy,&).
?- op(700,xfy,->).
?- op(700,xfy,<->).

需要注意运算符的优先级定义,特别是 <-> 的优先级低于 # & 。同时,要确保公式中的变量已按需重命名,避免出现名称冲突。

2.3 各阶段实现

2.3.1 移除蕴含关系(阶段1)
implout((P <-> Q),((P1 & Ql) # (~P1 & ~Q1))) :-
   !, implout(P,Pl), implout(Q,Ql).
implout((P -> Q),(~P1 # Ql)) :-
   !, implout(P,Pl), implout(Q,Ql).
implout(all(X,P),all(X,Pl)):- !, implout(P,Pl).
implout(exists(X,P),exists(X,Pl)):-!, implout(P,Pl).
implout((P & Q),(P1 & Ql)) :-
   !, implout(P,Pl), implout(Q,Ql).
implout((P # Q),(P1 # Ql)) :-
   !, implout(P,Pl), implout(Q,Ql).
implout((~P),(~Pl)) :- !, implout(P,Pl).
implout(P,P).
2.3.2 向内移动否定(阶段2)
negin((~P),Pl) :- !, neg(P,Pl).
negin(all(X,P),all(X,Pl)):- !, negin(P,Pl).
negin(exists(X,P),exists(X,Pl)) :-!, negin(P,Pl).
negin((P & Q),(P1 & Ql)) :-
   !, negin(P,Pl), negin(Q,Ql).
negin((P # Q),(P1 # Ql)) :-
   !, negin(P,Pl), negin(Q,Ql).
negin(P,P).
neg((~P),Pl):-!, negin(P,Pl).
neg(all(X,P),exists(X,Pl)):-!, neg(P,Pl).
neg(exists(X,P),all(X,Pl)):- !, neg(P,Pl).
neg((P & Q),(P1 # Ql)) :- !, neg(P,Pl), neg(Q,Ql).
neg((P # Q),(P1 & Ql)) :- !, neg(P,Pl), neg(Q,Ql).
neg(P,(~P)).
2.3.3 Skolem化(阶段3)
skolem(all(X,P),all(X,Pl),Vars) :-
   !, skolem(P,Pl,[X|Vars]).
skolem(exists(X,P),P2,Vars):-
    gensym(f,F), 
    Sk =..[F|Vars], 
    subst(X,Sk,P,Pl), 
    skolem(Pl,P2,Vars).
skolem((P # Q),(P1 # Ql),Vars) :-
   !, skolem(P,Pl,Vars), skolem(Q,Ql,Vars).
skolem((P & Q),(P1 & Ql),Vars) :-
   !, skolem(P,Pl,Vars), skolem(Q,Ql,Vars).
skolem(P,P,_).

此阶段使用了 gensym subst 两个新谓词, gensym 用于生成新的原子, subst 用于替换公式中的变量。

2.3.4 向外移动全称量词(阶段4)
univout(all(X,P),Pl):- !, univout(P,Pl).
univout((P & Q),(P1 & Ql)) :-
   !, univout(P,Pl), univout(Q,Ql).
univout((P # Q),(P1 # Ql)) :-
   !, univout(P,Pl), univout(Q,Ql).
univout(P,P).

该定义假设此操作仅在前三阶段完成后进行,因此公式中不包含蕴含关系或存在量词。

2.3.5 分配 & # (阶段5)
conjn((P # Q),R) :-
    conjn(P,Pl), conjn(Q,Ql), 
    conjnl((Pl # Ql),R).
conjn((P & Q),(P1 & Ql)) :- 
   !, conjn(P,Pl), conjn(Q,Ql).
conjn(P,P).
conjnl(((P & Q) # R),(P1 & Ql)) :-
   !, conjn((P # R),P1), conjn((Q # R),Ql).
conjnl((P # (Q & R)),(P1 & Ql)) :-
   !, conjn((P # Q),P1), conjn((P # R),Q1).
conjnl(P,P).
2.3.6 转换为子句(阶段6)
clausify((P & Q),C1,C2) :-
   !, clausify(P,Cl,C3), clausify(Q,C3,C2).
clausify(P,[cl(A,B)|Cs],Cs) :-
    inclause(P,A,[],B,[]),!.
clausify(_,C,C).
inclause((P # Q),A,A1,B,B1) :-
    inclause(P,A2,Al,B2,Bl), inclause(Q,A,A2,B,B2).
inclause((~P),A,A,Bl,B) :-
   !, notin(P,A), putin(P,B,Bl).
inclause(P,Al,A,B,B) :- notin(P,B), putin(P,A,Al).
notin(X,[X|_]) :-!, fail.
notin(X,[_|L]):-!, notin(X,L).
notin(X,[]).
putin(X,[],[X]):- !.
putin(X,[X|L],[X|L]):- !.
putin(X,[Y|L],[Y|Ll]):- putin(X,L,Ll).

2.4 打印子句

pclauses([]) :- !, nl, nl.
pclauses([cl(A,B) |Cs]) :-
    pclause(A,B), nl, pclauses(Cs).
pclause(L,[]) :-
   !, pdisj(L), write('.').
pclause([],L) :-
   !, write(':-'), pconj(L), write('.').
pclause(Ll,L2) :-
    pdisj(Ll), 
    write(':-'), pconj(L2), write('.').
pdisj([L]) :- !, write(L).
pdisj([L|Ls]) :- write(L), write(';'), pdisj(Ls).
pconj([L]) :- !, write(L).
pconj([L|Ls]) :- write(L), write(', '), pconj(Ls).

3. 编写可移植的标准Prolog程序

3.1 标准Prolog的可移植性

每次开发新的计算机硬件或操作系统时,通常需要编写新的程序来使Prolog对用户可用。不同的Prolog实现由不同的人在不同时间编写,它们可能会有不同的内置谓词名称和功能,这导致Prolog程序在不同实现上的兼容性问题。

为了解决这个问题,1995年制定了Prolog标准(ISO/IEC 13211 - 1),该标准精确规定了Prolog程序的定义和执行方式,包括一组内置谓词的规范。如果所有Prolog实现都完全符合标准,那么在不同实现上运行程序将不会有问题。

3.2 不同的Prolog实现

目前,很少有Prolog实现完全兼容标准Prolog。编写可移植Prolog程序的最佳方法是遵循标准。如果某个实现不提供标准谓词,但提供了可以用来定义该标准谓词的其他谓词,那么可以在单独的“兼容性文件”中定义这些谓词。

具体操作步骤如下:
1. 检查你的实现是否提供所需的标准谓词。
2. 如果没有提供,尝试使用其他可用的谓词来定义该标准谓词。
3. 将这些定义保存在一个单独的文件(兼容性文件)中。
4. 使用相同实现的用户在运行程序时,需要同时咨询兼容性文件。
5. 与标准完全兼容的实现用户可以直接运行程序,无需咨询兼容性文件。
6. 对于与标准不兼容的其他实现用户,可能需要编写自己的兼容性文件(可以参考已有的文件)。

以下是一个简单的表格总结不同情况:
| 实现情况 | 操作步骤 |
| ---- | ---- |
| 提供标准谓词 | 直接使用 |
| 不提供标准谓词 | 用其他谓词定义,保存到兼容性文件,运行时咨询该文件 |
| 与标准完全兼容 | 直接运行程序 |
| 与标准不兼容 | 编写自己的兼容性文件 |

mermaid流程图如下:

graph LR
    A[开始] --> B{实现是否提供标准谓词}
    B -- 是 --> C[直接使用]
    B -- 否 --> D[用其他谓词定义]
    D --> E[保存到兼容性文件]
    E --> F{实现是否与标准完全兼容}
    F -- 是 --> G[直接运行程序]
    F -- 否 --> H[编写自己的兼容性文件]
    H --> I[运行时咨询兼容性文件]
    C --> J[结束]
    G --> J
    I --> J

通过以上内容,我们了解了Prolog编程中部分练习的解答、公式转换为子句形式的过程以及编写可移植标准Prolog程序的方法和注意事项。这些知识对于提高Prolog编程能力和程序的通用性具有重要意义。

4. 关键技术点分析

4.1 家庭关系定义中的谓词运用

在家庭关系定义中,使用了多个谓词来表示不同的家庭关系。例如 is_mother is_father is_son 等。这些谓词通过组合其他已有的谓词(如 mother father parent male female )来实现特定的逻辑。

sister_of 谓词为例:

sister_of(Sis, Pers) :-
    parent(Par, Sis), parent(Par, Pers), 
    female(Sis), diff(Sis, Pers).

该谓词表示 Sis Pers 的姐妹,需要满足以下条件:
1. Sis Pers 有共同的父母 Par
2. Sis 是女性。
3. Sis Pers 不是同一个人(通过 diff 谓词保证)。

这里的 diff 谓词虽然现阶段无法定义,但它起到了避免逻辑错误的重要作用,防止系统得出某人是自己姐妹的不合理结论。

4.2 字符处理程序中的“cut”运用

在字符处理程序中:

go :- repeat, get_char(C), deal_with(C), fail.
deal_with(a) :- !, put_char(b).
deal_with(X) :- put(X).

“cut”( ! )在 deal_with(a) 规则中是必不可少的。当输入字符为 a 时,执行 deal_with(a) 规则,遇到“cut”后,会阻止回溯,确保不会再尝试其他规则。如果没有“cut”,当输入 a 时,程序可能会继续尝试 deal_with(X) 规则,导致逻辑混乱。

4.3 勾股数生成程序的逻辑

勾股数生成程序通过 intriple 谓词生成可能的整数三元组 X Y Z ,然后使用 pythag 谓词检查该三元组是否为勾股数。

pythag(X, Y, Z) :-
    intriple(X, Y, Z), 
    Sumsq is X*X + Y*Y, Sumsq is Z * Z.

intriple 谓词的实现保证了所有可能的整数三元组最终都会被生成。它首先生成一个整数 Sum 作为 X Y Z 的和,然后使用 minus 谓词来生成 X Y Z 的值。

intriple(X, Y, Z) :-
    is_integer(Sum), 
    minus(Sum, X, Suml), minus(Suml, Y, Z).

4.4 语法规则转换程序的结构

简单语法规则转换为Prolog的程序通过 translate 谓词将语法规则转换为Prolog目标。程序中使用了多个辅助谓词,如 left_hand_side right_hand_side tag and 等。

translate((Pl—>P2),(G1:-G2)) :-
    left_hand_side(Pl,SO,S,Gl), 
    right_hand_side(P2,S0,S,G2).

这些辅助谓词分别处理语法规则的左右两边,将短语描述转换为Prolog目标,并处理目标之间的逻辑关系。

4.5 子句形式转换程序的各阶段作用

公式转换为子句形式的程序分为六个阶段,每个阶段都有其特定的作用:
| 阶段 | 作用 |
| ---- | ---- |
| 移除蕴含关系 | 将公式中的蕴含关系( -> <-> )转换为其他逻辑运算符( # & ) |
| 向内移动否定 | 将否定符号移动到公式内部,使否定只作用于原子公式 |
| Skolem化 | 消除存在量词,用Skolem函数或常量代替 |
| 向外移动全称量词 | 将全称量词移动到公式的最外层 |
| 分配 & # | 将公式转换为合取范式 |
| 转换为子句 | 将合取范式转换为子句形式 |

mermaid流程图如下:

graph LR
    A[原始公式] --> B[移除蕴含关系]
    B --> C[向内移动否定]
    C --> D[Skolem化]
    D --> E[向外移动全称量词]
    E --> F[分配&到#]
    F --> G[转换为子句]
    G --> H[打印子句]

5. 总结与建议

5.1 总结

通过对Prolog编程中练习解答、子句形式转换和可移植性的探讨,我们了解到:
- 在编写Prolog程序时,要合理运用谓词和逻辑运算符,处理好各种逻辑关系,如家庭关系定义和语法规则转换。
- 对于复杂的程序,如公式转换为子句形式的程序,要按照特定的步骤进行处理,每个阶段都有其重要的作用。
- 编写可移植的Prolog程序时,要遵循标准Prolog的规范,处理好不同实现之间的兼容性问题。

5.2 建议

  • 在实际编程中,多使用标准谓词,避免使用特定实现的内置谓词,以提高程序的可移植性。
  • 对于复杂的逻辑处理,如公式转换,要仔细分析每个阶段的作用,确保程序的正确性。
  • 在编写程序时,要注意代码的可读性和可维护性,添加必要的注释,方便自己和他人理解代码。

总之,掌握Prolog编程的这些知识和技巧,能够帮助我们编写更加高效、可靠和可移植的程序。

下载前可以先看下教程 https://pan.quark.cn/s/16a53f4bd595 小天才电话手表刷机教程 — 基础篇 我们将为您简单的介绍小天才电话手表新机型的简单刷机以及玩法,如adb工具的使用,magisk的刷入等等。 我们会确保您看完此教程后能够对Android系统有一个最基本的认识,以及能够成功通过magisk root您的手表,并安装您需要的第三方软件。 ADB Android Debug Bridge,简称,在android developer的adb文档中是这么描述它的: 是一种多功能命令行工具,可让您设备进行通信。 该命令有助于各种设备操作,例如安装和调试应用程序。 提供对 Unix shell 的访问,您可以使用它在设备上运行各种命令。 它是一个客户端-服务器程序。 这听起来有些难以理解,因为您也没有必要去理解它,如果您对本文中的任何关键名词产生疑惑或兴趣,您都可以在搜索引擎中去搜索它,当然,我们会对其进行简单的解释:是一款在命令行中运行的,用于对Android设备进行调试的工具,并拥有比一般用户以及程序更高的权限,所以,我们可以使用它对Android设备进行最基本的调试操作。 而在小天才电话手表上启用它,您只需要这么做: - 打开拨号盘; - 输入; - 点按打开adb调试选项。 其次是电脑上的Android SDK Platform-Tools的安装,此工具是 Android SDK 的组件。 它包括 Android 平台交互的工具,主要由和构成,如果您接触过Android开发,必然会使用到它,因为它包含在Android Studio等IDE中,当然,您可以独立下载,在下方选择对应的版本即可: - Download SDK Platform...
已经博主授权,源码转载自 https://pan.quark.cn/s/b24469074755 SmartDNS English SmartDNS SmartDNS 是一个运行在本地的 DNS 服务器,它接受来自本地客户端的 DNS 查询请求,然后从多个上游 DNS 服务器获取 DNS 查询结果,并将访问速度最快的结果返回给客户端,以此提高网络访问速度。 SmartDNS 同时支持指定特定域名 IP 地址,并高性匹配,可达到过滤广告的效果; 支持DOT,DOH,DOQ,DOH3,更好的保护隐私。 DNSmasq 的 all-servers 不同,SmartDNS 返回的是访问速度最快的解析结果。 支持树莓派、OpenWrt、华硕路由器原生固件和 Windows 系统等。 使用指导 SmartDNS官网:https://pymumu..io/smartdns 软件效果展示 仪表盘 SmartDNS-WebUI 速度对比 阿里 DNS 使用阿里 DNS 查询百度IP,并检测结果。 SmartDNS 使用 SmartDNS 查询百度 IP,并检测结果。 从对比看出,SmartDNS 找到了访问 最快的 IP 地址,比阿里 DNS 速度快了 5 倍。 特性 多虚拟DNS服务器 支持多个虚拟DNS服务器,不同虚拟DNS服务器不同的端口,规则,客户端。 多 DNS 上游服务器 支持配置多个上游 DNS 服务器,并同时进行查询,即使其中有 DNS 服务器异常,也不会影响查询。 支持每个客户端独立控制 支持基于MAC,IP地址控制客户端使用不同查询规则,可实现家长控制等功能。 返回最快 IP 地址 支持从域名所属 IP 地址列表中查找到访问速度最快的 IP 地址,并返回给客户端,提高...
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值