不使用内置谓词实现Prolog简易版findall的方法咨询
Prolog 手动实现简易findall功能
需求说明
练习目标为不使用Prolog内置findall及同类集合收集内置谓词,自主实现简易的结果收集功能。
给定如下事实定义:
a(1). a(11). a(13).
需要实现谓词find_the_as(List),调用后返回List = [1,11,13]的正确结果。
目前已实现基于fail循环+assert/retract动态数据库操作的版本,但希望找到纯累加器递归的无副作用实现方案。
注:该动态库版本最终返回的列表顺序为[13,11,1],和事实定义顺序相反,需要额外逆序才能得到预期顺序。
当前已实现的动态库版本代码如下:
% % 收集所有满足a(N)的N到结果列表List % collect_n_such_that_a_of_n(List) :- retractall(the_n_such_that_a_of_n(_)), assert(the_n_such_that_a_of_n([])), ( a(N), the_n_such_that_a_of_n(As), retractall(the_n_such_that_a_of_n(_)), assert(the_n_such_that_a_of_n([N|As])), fail ) ; true, the_n_such_that_a_of_n(List).
累加器递归实现方案
纯递归实现不需要修改动态数据库,没有副作用,核心逻辑是通过累加器记录已经收集到的解,每次生成新解时判断是否已经收集过,避免回溯导致的重复收集,所有解收集完成后翻转累加器得到正序结果即可。
完整实现代码
% 入口谓词,初始化空累加器 find_the_as(List) :- collect_a([], List). % 核心收集逻辑:有新解则加入累加器继续递归,无新解则返回逆序后的结果 collect_a(Acc, List) :- ( a(N), \+ my_member(N, Acc) % 校验当前N未被收集过 -> collect_a([N|Acc], List) ; my_reverse(Acc, List) ). % 自行实现基础成员判断谓词,避免依赖内置列表处理谓词 my_member(X, [X|_]). my_member(X, [_|T]) :- my_member(X, T). % 自行实现列表逆序谓词,基于累加器实现 my_reverse(L, R) :- reverse_acc(L, [], R). reverse_acc([], Acc, Acc). reverse_acc([H|T], Acc, R) :- reverse_acc(T, [H|Acc], R).
执行流程说明
- 调用入口谓词时,传入空列表作为初始累加器
- 每次进入收集逻辑,首先尝试查找一个不在已收集列表中的
a(N):- 找到符合条件的N时,将其插入累加器头部,递归进入下一轮收集
- 找不到未收集的N时,说明所有解已经收集完成,因为采用头插法累加的列表是逆序的,逆序后绑定为最终结果
- 以上述事实库为例,执行时累加器的变化过程为
[] -> [1] -> [11,1] -> [13,11,1],最终逆序得到[1,11,13],符合预期。
注意事项
- 实现中用到的
\+(否定谓词)属于Prolog基础控制谓词,不属于findall类的集合收集内置谓词,符合练习约束。 - 条件判断
->自带隐式选择点切断,不需要额外加!(cut)即可避免不必要的回溯。 - 如果允许使用基础内置列表谓词,可以直接替换掉自实现的
my_member和my_reverse,核心收集逻辑不需要修改。
内容的提问来源于stack exchange,提问作者PaulM
相关产品推荐
相关产品推荐

