如何在Prolog中转换命令式任务?附N数码问题单块曼哈顿距离函式咨询
把命令式任务转换为Prolog的思路
刚接触Prolog的时候,最容易犯的就是用命令式的思维写代码,其实核心要转个弯:
- 从"步骤指令"到"关系描述":命令式是告诉电脑"先做A再做B最后得到C",但Prolog要你定义"什么情况会得到C",比如你要遍历数组找元素,不用写循环,而是用
nth0这类内置谓词描述"元素和它的位置之间的关系"。 - 用谓词参数代替返回值:命令式里函数return一个结果,Prolog里直接把结果作为谓词的一个参数传出来——就像你的
distanzaTile里的Dis,这就是Prolog的"函数式"实现方式。 - 别想着修改状态:命令式里变量可以反复赋值,但Prolog的变量绑定后就不能改了。比如处理N数码的状态变化,你要生成一个新的状态数组,而不是修改原来的那个。
- 用模式匹配和回溯代替分支循环:命令式的if-else、for循环,在Prolog里大多可以用多子句的模式匹配来实现,比如你的
distanzaTile写了两个子句,本质就是通过判断Elem是不是v来做分支,而且Prolog的回溯机制还能帮你自动处理多种可能的情况。
你的N数码曼哈顿距离函数分析
先把你的代码格式化一下,看起来更清楚:
distanzaTile(Stato, Pos, Dis) :- dim(D), nth0(Pos, Stato, Elem), Elem == v, Y is floor(abs((9 - (Pos + 1))/D)), X is mod(abs(9 - (Pos + 1)), D), Dis is X + Y. !. distanzaTile(Stato, Pos, Dis) :- dim(D), nth0(Pos, Stato, Elem), Y is floor(abs((Elem - (Pos + 1))/D)), X is mod(abs(Elem - (Pos + 1)), D), Dis is X + Y.
逻辑拆解
这个谓词的功能是对的——计算单个tile(包括空位v)的曼哈顿距离:
- 第一个子句处理空位
v:你默认空位的目标位置是第9个位置(对应3x3的8数码最后一格),通过计算当前位置(转成1-based的Pos+1)和目标位置的行差(Y)、列差(X),加起来得到距离,末尾的!截断回溯,避免匹配到第二个子句,这个设计很合理。 - 第二个子句处理普通tile:逻辑和空位类似,只是目标位置是tile本身的数值
Elem(因为8数码里tile值就是它的目标位置,比如数字5应该在第5格),计算行差列差求和,没问题。
优化建议
- 让代码更通用:你写的
9是3x3的总格子数,其实可以用D*D替代,这样如果是4x4的15数码,只要修改dim(D)的定义,代码不用改:Y is floor(abs((D*D - (Pos + 1))/D)), X is mod(abs(D*D - (Pos + 1)), D), - 提取重复逻辑:两个子句里都有
dim(D), nth0(Pos, Stato, Elem),可以抽出来做成一个公共前置子句,代码更简洁:distanzaTile(Stato, Pos, Dis) :- dim(D), nth0(Pos, Stato, Elem), distanzaTile_helper(Elem, Pos, D, Dis). distanzaTile_helper(v, Pos, D, Dis) :- TargetPos is D*D, Diff is abs(TargetPos - (Pos + 1)), Y is floor(Diff / D), X is mod(Diff, D), Dis is X + Y. distanzaTile_helper(Elem, Pos, D, Dis) :- Diff is abs(Elem - (Pos + 1)), Y is floor(Diff / D), X is mod(Diff, D), Dis is X + Y. - 小细节:
abs其实可以省略,因为不管是a-b还是b-a,绝对值结果一样,但保留也不影响,反而更清晰。
内容的提问来源于stack exchange,提问作者Lamberto Basti
相关产品推荐
相关产品推荐

