Prolog:寻找所有解决方案

时间:2015-02-03 00:27:56

标签: matrix prolog

标题可能看起来像一打一打,但事实并非如此。该程序的目标是采用这些类(需求)

needs([[ece2090,1,m,13,16],
[ece3520,1,tu,11,14],
[ece4420,1,w,13,16]].

并将他们与具有教授课程资格的大学助教配对,并且在此期间也是免费的(资源; TA的日期,开始和停止的价值意味着他无法在这一次。)

resources([[joel, [ece2090,ece2010,ece3520,ece4420],[[m,13,16]]],
 [sam, [ece2090,ece4420],[]],
[pete, [ece3520],[[w,13,16]]]].

在这个版本中,每个TA只能占用一个类。我已经建立了一个程序来完成这个,下面是一个解决方案。

A = [[ece2090, 1, 'NONE'], [ece3520, 1, joel], [ece4420, 1, sam]] .

这些是TA与课程的配对。如您所见,其中一个标记为无。但是,如果你看起来你会看到一个有效的配置,分配所有树的TA(Pete到ECE3520,Sam到ECE2090,和Joel到ECE4420)。出于以下问题的目的,这两个都将被视为解决方案。

如何显示所有解决方案? Findall通常是要走的路,而且有一个不可避免的失败条款也可以做到这一点,但是这里有一个踢球者:对于prolog,需求和资源都只是变量的一个实例而不是三个,因为它们是矩阵。出于这个原因,如果我要强制使用失败条款进行回溯或者发现

,那么没有任何内容可以让我们回溯。

那么有没有找到所有解决方案的方法?我可以以某种方式采用矩阵的所有可能的排列并将它们全部投入到程序中吗?

2 个答案:

答案 0 :(得分:1)

我完全同意 lurker 的数据组织。另外,我会说你的问题陈述让我想到了组合优化。在这种情况下,我总是建议使用Constraint Programming将其表述为CSP,并使用Prolog的Constraint Logic Programming over Finite Domains library(CLP(FD))。

约束满意度问题
一般而言,CP问题包括:

  • 设置 X = {x_1,x_2,...,x_N} 变量。
  • 每个变量的域 D = {D(x_1),D(x_2),...,D(x_N)} ,即:可以分配这些变量的值集(例如 D(x_i)= {1,2,3}
  • 变量之间的约束 C (例如 A> B

约束满足问题(CSP)的解决方案是赋值{x_1 = V_1,x_2 = V_2,...,x_N = V_N},以满足C中的所有约束。这就是要点。如果你想了解更多关于这些问题的建议,我建议你在当地的图书馆找一本书,应该有很多。

我建议您使用此配方,因为CLP(FD)库具有求解器,可用于找到CP中制定的问题的解决方案。使用CP,您可以在以下部分中构建程序:

  1. 问题定义:定义决策变量的初始域。
  2. 应用一组约束。即:使用必须满足的条件“链接”您的变量。
  3. 调用一个解算器,该解算器会自动为指定域中的决策变量赋值并满足所有约束。
  4. 在Prolog中实施CSP
    我写了一个简短的例子来向您展示如何在SWI-Prolog中完成这项工作。首先,我假设您按以下方式构建输入数据:

    course(ece2090,1,m,13,16).
    course(ece3520,1,tu,11,14).
    course(ece4420,1,w,13,16).
    
    ta(joel, [ece2090,ece2010,ece3520,ece4420], [m([13,16])]).
    ta(sam, [ece2090,ece4420],[]).
    ta(pete, [ece3520],[w([13,16])]).
    ta(alan, [ece3520],[m([13,16],[19,21]),w([12,14])]).
    

    请注意,我允许每个TA的可用性比初始规范中的更复杂一些。由于您希望找到问题的所有可能解决方案,因此我将解决方案编码为列表列表,其中每个内部列表可以取0到1之间的值(即变量')。每个外部列表对应于每个课程,而内部列表对应于该特定课程的TA的分配。

                 %  TA1, TA2, ...
    Assignments = [  [1,   0, ...] ,        % Course 1 
                     [0,   1, ...] ,        % Course 2
                     [0,   0, ...] | ... ]  % ...
    

    如果我们只有2个TA(比如Joel和Sam),上面的解决方案将意味着Joel被分配到第一个球场而Sam被分配到第二个球场。你想要做的是用域0..1定义无界变量,应用所有必要的约束,然后自动解决它(即 label )。

    timetable(ASs) :-
        findall(C, course(C,_,_,_,_), Cs), % Gets all courses.
        findall(TA, ta(TA,_,_), TAs),      % Gets all T.A.'s
        length(TAs, Nta),                  
        assign(Cs, TAs, Nta, ASs),         % ASs is a list of assignments.
        append(ASs,Xs),
        sum(Xs,#>,0),                      % Applies an extra constraint
                                           % to discard solutions with no
                                           % assignments.
        label(Xs).                         % Starts the solver.
    

    此处,assign/4正在生成如上定义的分配列表。但是,这些值既不是0也不是1。列表ASs看起来像这样:

                 %   TA1, TA2, ...
    Assignments = [  [X1,   0, ...] ,        % Course 1 
                     [ 0,  X1, ...] ,        % Course 2
                     [ 0,   0, ...] | ... ]  % ...
    

    本质上,assign/4谓词只是为每个项目TA课程放置0,这些课程不匹配,先验,任何条件(即作业0如果TA没有教授课程的证书,或者他/她是否在那个特定时间忙碌。)

    如果这是你的作业,我建议你不要在这里阅读,并试着提供你自己的assign/4实施。如果您不熟悉CP或希望找到一点灵感,我会留下自己的。我已经使用了谓语available(?TA,+C),当教学助手TA可用于教授课程C并且具有必要的证书时,该谓词成功。另外,谓词assign_(?Xs:list, +TAs:list, ?Ms:list)是一个简单的谓词,当X不是TA中可用TA列表的成员时,它将变量(Ms)绑定为0 。

    assign([], _, _, []).                  % Stops recursion.
    assign([C|Cs], TAs, Nta, [AS|ASs]) :-  % Iterates through courses.
        length(AS, Nta),                   % Generates a new list with Nta elements.
        AS ins 0..1,                       % Sets the domain for each element in the list.
        findall(TA, available(TA,C), Ms),  % Finds all possible TA's for course C.
        assign_(AS, TAs, Ms),       % Ms now has 0s for the unavailable TA's.
        sum(AS, #=<, 1),            % Sets a constraint: only one TA can be assigned to a course.
        assign(Cs,TAs,Nta,ASs).     % Continues for the rest of courses.
    
    % Helper predicate:
    assign_([],[],_).               
    assign_([_|Xs],[TA|TAs],Ms) :-
        memberchk(TA,Ms),
        assign_(Xs,TAs,Ms).
    assign_([0|Xs],[_|TAs],Ms) :-
        assign_(Xs,TAs,Ms).
    

    请参阅sum/3以了解如何应用约束。为了完成该程序,您只需要为available/2谓词提供实现。然后,以下调用将给您一个答案:

    ?- timetable(Solution).
    

    如果您想要每种可能的解决方案,只需再次使用findall/3

    ?- findall(S, timetable(S), AllSolutions).
    

    我还没有真正测试过我的程序,它只是为了说明,但我希望你发现它有用。

    注意:请记住,组合问题(特别是您想要所有解决方案的组合问题)在计算上非常复杂。我想你想找到那些以后优化你的时间表。在这种情况下,我鼓励你在程序本身内这样做(即labeling/2)。

答案 1 :(得分:0)

这可能是一项任务,所以我不会给出完整的解决方案。但我建议以不同的方式构建您的信息,以更典型的方式在Prolog中完成:

need(ece2090, 1, [m, 13, 16]).
need(ece3520, 1, [tu, 11, 14]).
need(ece4420, 1, [w, 13, 16]).

qualified_for(joel, ece2090).
qualified_for(joel, ece2010).
qualified_for(joel, ece3520).
qualified_for(joel, ece4420).
qualified_for(sam, ece2090).
qualified_for(sam, ece4420).
qualified_for(pete, ece3520).

busy(joel, [m, 13, 16]).
% busy(sam, []).  % absence of this fact means sam isn't busy
busy(pete, [w, 13, 16]).
busy(pete, [f, 9, 12]).

在这里,每个事实都独立存在,而不是在事实中嵌入列表。每个不同的仿函数就像数据库中的一个表,您可以查询,而不同的仿函数是基于某些参数相互关联的不同表。

所以,如果你想知道joel有资格获得什么:

| ?- qualified_for(joel, C).

C = ece2090 ? ;
C = ece2010 ? ;
C = ece3520 ? ;
C = ece4420

yes
| ?-

如果您需要所有合格助教的清单:

| ?- setof(TA, C^qualified_for(TA, C), AllTAs).

AllTAs = [joel,pete,sam]

yes
| ?-

如果您想查看哪些课程joel可用并且有资格教授:

| ?-  need(Class, I, Schedule), qualified_for(joel, Class), \+ busy(joel, Schedule).

Class = ece3520
I = 1
Schedule = [tu,11,14] ? a

Class = ece4420
I = 1
Schedule = [w,13,16]

(1 ms) yes
| ?-

上面的假设是“调度”块都以相同的方式描述,[day, start, end]如图所示,因此它们可以简单地匹配为整个3元素列表。通过一点额外的努力,可以更加通用地更详细地检查日期和时间间隔。但这似乎不是你的要求。以上是帮助解决问题的良好构建块。它只需要几行代码(我只使用了12行,加上上述事实)。 :)