Prolog:“findall”用于有限数量的解决方案

时间:2013-12-31 06:36:33

标签: prolog

假设我想找到谓词的所有解的总和,我可以使用

findall(L, find(L), Sols),

只是总结Sols的成员。

但是,如果找到(L)有大量(无限可能)解决方案,我只想获得前10个?

我希望这可以在B-Prolog和ECLiPSe CLP中使用。

9 个答案:

答案 0 :(得分:7)

有许多类似的用途,因此可以考虑在两者之间定义一些抽象。例如。 call_firstn(Goal_0,N) N最多Goal_0findfirstn(N, Template, Goal_0, Instances) :- findall(Template, call_firstn(Goal_0, N), Instances). call_firstn(Goal_0, N) :- N + N mod 1 >= 0, % ensures that N >=0 and N is an integer call_nth(Goal_0, Nth), ( Nth == N -> ! ; true ). 多个答案。这反过来可以使用call_nth(Goal_0, Nth)实现。

call_nth/2

不会泄漏且仍然是可重入的{{1}}的完整实现无法直接在ISO Prolog中定义。你需要求助于所有类型的低级操作,这些操作通常是不完整的语义,而这些语义对于常规程序员来说是更好的隐藏。

答案 1 :(得分:5)

这是你在ECLiPSe中的表现方式:

find_n(N, Term, Goal, Solutions) :-
    ( N < 1 ->
        Solutions = []
    ;
        record_create(Bag),
        shelf_create(count(N), Counter),
        ( 
            once((
                call(Goal),
                recordz(Bag, Term),
                \+shelf_dec(Counter, 1)   % succeed if enough
            )),
            fail
        ;
            recorded_list(Bag, Solutions)
        )
    ).

这是可重入的并且不会泄漏内存(两者都是全局变量或基于动态谓词的解决方案的问题)。如果您希望它能够正确处理模块,则需要少量添加。

当然,你可以使用与Paulo使用的断言/收缩原语相同的代码结构。

答案 2 :(得分:3)

ISO Prolog中的便携式解决方案:

:- dynamic(find_n_solution/1).
:- dynamic(find_n_counter/1).

find_n(N, Term, Goal, Solutions) :-
    (   set_find_n_counter(N),
        retractall(find_n_solution(_)),
        once((
            call(Goal),
            assertz(find_n_solution(Term)),
            dec_find_n_counter(M),
            M =:= 0
        )),
        fail
    ;   findall(Solution, retract(find_n_solution(Solution)), Solutions)
    ).

set_find_n_counter(N) :-
    retractall(find_n_counter(_)),
    assertz(find_n_counter(N)).

dec_find_n_counter(M) :-
    retract(find_n_counter(N)),
    M is N - 1,
    assertz(find_n_counter(M)).

使用样本谓词find/1 @ChristianF回答:

| ?- find_n(10, X, find(X), L).
L = [0,1,2,3,4,5,6,7,8,9]
yes

请注意,即使所需的解决方案数量少于此解决方案,此解决方案仍会返回解决方案列表。一些Prolog编译器,包括B-Prolog和ECLiPSe,提供了可用于实现计数器的非逻辑全局变量,但这会使解决方案变得不可移植。

答案 3 :(得分:3)

SWI-Prolog提供内置谓词findnsols/4和findnsols / 5

gbcRightPane.gridWidth = 5;
rightPanel.add(labelDitigalTimer, gbcRightPanel);
gbcRightPane.gridWidth = 1; // reset for the other components.

您可能希望将整个调用包装成一次/ 1以防止对其他解决方案组进行回溯(例如,如果在上面的示例中您希望1-5列表成为您唯一的解决方案)。

?- findnsols(5, I, between(1, 12, I), L).
L = [1, 2, 3, 4, 5] ;
L = [6, 7, 8, 9, 10] ;
L = [11, 12].

答案 4 :(得分:2)

我刚刚意识到在B-Prolog中通过表格进行操作是微不足道的:

%% an example predicate with infinite solutions
%% from ChristianF answer.
find(0).
find(X) :- find(X1), X is X1+1.

:- table find(-).

?- table_cardinality_limit(find/1, 10), findall(X, find(X), L).
L = [0,1,2,3,4,5,6,7,8,9]
yes

答案 5 :(得分:1)

需要在SWI Prolog中制作一个线程安全的解决方案,我带来了这个:

find_n( N, Solution, Goal, Solutions ) :-
    thread_self( Thread_id ),
    atomic_list_concat( [counter, Thread_id], Counter_flag_key ),
    flag( Counter_flag_key, _, 0 ),

    findall( Solution, (

        call( Goal ),

        flag( Counter_flag_key, X, X ),
        X1 is X + 1,
        flag( Counter_flag_key, _, X1 ),

        ( X1 >= N -> !; true )

    ), Solutions ).

答案 6 :(得分:1)

最近添加的Logtalk线程引擎API或SWI-Prolog协同引擎API提供了另一种解决方案。例如,使用Logtalk线程引擎:

find_at_most(N, Template, Goal, List) :-
    threaded_engine_create(Template, Goal, Engine),
    collect_at_most(N, Engine, List0),
    threaded_engine_destroy(Engine),
    List = List0.

collect_at_most(N, Engine, [X| Xs]) :-
    N > 0,
    threaded_engine_next(Engine, X),
    !,
    M is N - 1,
    collect_at_most(M, Engine, Xs).
collect_at_most(_, _, []).

threaded_engine_create/3谓词计算第一个解决方案,然后暂停等待threaded_engine_next/2谓词检索它。当检索到解决方案时,引擎开始计算下一个解决方案,再次暂停直到它被消耗(当在解决方案准备好之前调用threaded_engine_next/2谓词时,此谓词只是阻止等待它) 。因此,最多计算N个解。

使用SWI-Prolog协同引擎的解决方案几乎完全相同,但没有阻塞语义(在线程引擎版本中),并且除非通过利用应用程序中的并发(其他地方)来抵消线程的隐式开销,否则预计会更有效:

find_at_most(N, Template, Goal, List) :-
    engine_create(Template, Goal, Engine),
    collect_at_most(N, Engine, List0),
    engine_destroy(Engine),
    List = List0.

collect_at_most(N, Engine, [X| Xs]) :-
    N > 0,
    engine_next(Engine, X),
    !,
    M is N - 1,
    collect_at_most(M, Engine, Xs).
collect_at_most(_, _, []).

两个API还支持_next/2谓词的已知版本,因此如果您想要消除辅助谓词中的切割,则支持find_at_most/4谓词的替代实现。

最后,但并非最不重要的是,此处提供的解决方案源自Paul Tarau的工作和发动机论文。

答案 7 :(得分:0)

我认为您需要手动实现findall中隐含的唯一性条件,即您需要聚合解决方案,并且对于每个新解决方案,检查它之前是否尚未被选中。

以下是一个如何运作的示例:

%% an example predicate with infinite solutions
find(0).
find(X) :- find(X1), X is X1+1.

%% myfindall(+Num) holds when there are at least Num different solutions for find
myfindall(Num) :-
    length(L, Num),
    mfa_aux(L, [], All),
    %% now do something with All, e.g., sum it.
    writeln(All).

mfa_aux([], All, All).
mfa_aux([H|T], All, Rtv) :-
    find(H),
    not(member(H, All)), !,
    mfa_aux(T, [H|All], Rtv).


%% Test
%% ?- myfindall(10).

答案 8 :(得分:0)

您可以将库解决方案中的findall/3limit/2合并 顺序,从SQL启发:

限制(+计数,:目标)
限制解决方案的数量
https://www.swi-prolog.org/pldoc/man?predicate=limit/2

这是一个示例运行:

?- findall(X, limit(3, (repeat, X = 1)), L).
L = [1, 1, 1].

几个Prolog系统的谓词如limit/2 在他们的曲目中。