【问题标题】:Prolog: combined predicate failedProlog:组合谓词失败
【发布时间】:2021-04-12 15:58:20
【问题描述】:

作为一个初学者,我正在尝试解决一个矩阵问题,其中解决方案是矩阵中不同数字的求和或乘法。例如,[[14,_,_,_],[15,_,_,_],[28,_,1,_]]。谓词应根据矩阵生成解决方案。但是,我遇到了一个问题,我的组合谓词失败了,但它们每个都独立成功。

我将问题分解为求和和乘法。因此,使用 clpfd 库和一些谓词进行求和:

removeHead([Head|Tail],Tail).

get_head_element([],_).
get_head_element([Head|Rest],Head).

solve_sum(Row,RestRow):-
    get_head_element(Row, Goal),
    removeHead(Row,RestRow),
    RestRow ins 1..9,
    all_distinct(RestRow),
    sum(RestRow, #=, Goal).

对于乘法:

multiply(List,Result):-
    multiply(List,1,Result).

multiply([Element|RestList],PrevResult,Result):-
    NextResult #= PrevResult * Element,
    multiply(RestList,NextResult, Result).

multiply([], Result, Result).

solve_multiply(Row,RestRow):-
    get_head_element(Row, Goal),
    removeHead(Row,RestRow),
    RestRow ins 1..9,
    multiply(RestRow,Goal),
    all_distinct(RestRow).

solve_sumsolve_multiply 都适用于单行,但在这里我将这两个谓词结合起来:

solve_row_sum_or_multiply([],_).

solve_row_sum_or_multiply([HeadRow|Matrix],Solution):-
maplist(all_distinct,Matrix),
get_head_element(HeadRow,Goal),
( Goal >= 25
-> solve_multiply(HeadRow,Solution),
   write(Solution)
; ( solve_sum(HeadRow,Solution),
    write(Solution)
    ; solve_multiply(HeadRow,Solution),
    write(Solution))
),solve_row_sum_or_multiply(Matrix,Solution).

当我使用solve_row_sum_or_multiply([[14,_,_,_],[15,_,_,_],[28,_,_,_]],X) 时,它会给我可能的组合。但是,当solve_row_sum_or_multiply([[14,_,_,_],[15,_,_,_],[28,_,1,_]],X) 时,它给了我错误,但是那里有可能的解决方案,例如[[14,7,2,1],[15,3,7,5],[28,4,1,7]]

我想知道可能出了什么问题。非常感谢任何帮助!

编辑

问题出在组合谓词内部,最好编写答案建议的谓词,而不是嵌套的 if-then-else 谓词。

【问题讨论】:

    标签: prolog predicate clpfd


    【解决方案1】:

    让我们回顾一下这段代码。

    介绍代码

    :- use_module(library(clpfd)).
    
    % ---
    % Make sure you see all the information in a list printed at the 
    % toplevel, i.e. tell the toplevel to not elide large lists (print
    % them in full, don't use '...')
    % ---
    
    my_print :-
       Depth=100,
       Options=[quoted(true), portray(true), max_depth(Depth), attributes(portray)],
       set_prolog_flag(answer_write_options,Options),
       set_prolog_flag(debugger_write_options,Options).
       
    :- my_print.   
    

    乘/2

    重新编码multiply/2 谓词,使其更易于阅读。添加测试代码以确保它确实像我们认为的那样:

    % ---
    % Set up constraint: all the elements in List, multiplied
    % together, must yield Result. If the List is empty, the
    % Result must be 1.
    % ---
    
    multiply(Factors,Product):-
        multiply_2(Factors,1,Product).
    
    multiply_2([Factor|Factors],PartialProduct,Product):-
        PartialProduct2 #= PartialProduct * Factor,
        multiply_2(Factors,PartialProduct2,Product).
    
    multiply_2([],Product,Product).
    
    solve_multiply([Product|Factors]):-
        Factors ins 1..9,
        multiply(Factors,Product),
        all_distinct(Factors).
        
    % ---
    % Testing
    % ---
    
    :- begin_tests(multiply).
    
    test(1) :- 
       multiply([X,Y,Z],6),
       [X,Y,Z] ins 1..9,
       X #=< Y, Y #=< Z, 
       bagof([X,Y,Z],label([X,Y,Z]),Bag),
       sort(Bag,BagS),
       assertion(BagS == [[1, 1, 6], [1, 2, 3]]).
    
    test(2) :- 
       multiply([],Result),
       assertion(Result == 1).
    
    test(3) :- 
       multiply([X,Y],3),
       [X,Y] ins 1..9,
       X #=< Y, 
       bagof([X,Y],label([X,Y]),Bag),
       sort(Bag,BagS),
       assertion(BagS == [[1,3]]).
    
    test(4) :-
       solve_multiply([6,X,Y,Z]),
       bagof([X,Y,Z],label([X,Y,Z]),Bag),
       sort(Bag,BagS),
       assertion(BagS == [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]).
       
    test(5) :-
       solve_multiply([362880,F1,F2,F3,F4,F5,F6,F7,F8,F9]),
       F1 #=< F2,
       F2 #=< F3,
       F3 #=< F4,
       F4 #=< F5,
       F5 #=< F6,
       F6 #=< F7,
       F7 #=< F8,
       F8 #=< F9,
       bagof([F1,F2,F3,F4,F5,F6,F7,F8,F9],label([F1,F2,F3,F4,F5,F6,F7,F8,F9]),Bag),
       sort(Bag,BagS),
       assertion(BagS == [[1,2,3,4,5,6,7,8,9]]).
    
    test(6,fail) :-
       solve_multiply([-1,X,Y,Z]).
       
    :- end_tests(multiply).
    

    solve_sum/1

    总而言之,在简化和重新编码为 Prolog 的“模式匹配”样式而不是(因为想要一个更好的词)“Algol 调用样式”之后,在原始问题中使用(这让我非常困惑,我不得不说)。

    这看起来也不错。请注意,谓词 solve_sum/1multiply/2 的参数风格完全不同。如果一个人保持相同的调用约定,那就更好了——而且对读者(和自己)来说更容易。

    solve_sum([Sum|Terms]):-
        Terms ins 1..9,
        all_distinct(Terms),
        sum(Terms, #=, Sum).
        
    % ---
    % Testing
    % ---
    
    :- begin_tests(sum).
    
    test(0) :- 
       solve_sum([0]).
       
    test(1) :- 
       solve_sum([6,X,Y,Z]),
       X #=< Y, Y #=< Z, 
       bagof([X,Y,Z],label([X,Y,Z]),Bag),
       sort(Bag,BagS),
       assertion(BagS == [[1, 2, 3]]).
    
    test(2) :- 
       solve_sum([9,X,Y,Z]),
       X #=< Y, Y #=< Z, 
       bagof([X,Y,Z],label([X,Y,Z]),Bag),
       sort(Bag,BagS),
       assertion(BagS == [[1,2,6],[1,3,5],[2,3,4]]).
    
    test(3,fail) :- 
       solve_sum([1,_X,_Y,_Z]).
       
    :- end_tests(sum).   
    

    获得良好结果的打印谓词

    Prolog 使“动态”构建任意结构变得容易,只需明确规定结构的外观即可。

    我们的“解决方案”是:

    • 列表
    • 列表中的每个元素都有一个“行”(代表其中一个表达式)
    • 行是一个术语:row(Result,Op,RowValues) 其中Result 是等式的左侧,Opaddmult 之一,RowValues 是一个包含值的列表找到等式中的值。
    print_solution([]).
    
    print_solution([Labeling|Labelings]) :-
       format("---~n"),
       print_rows_of_solution(Labeling),
       format("---~n"),
       print_solution(Labelings).
    
    print_rows_of_solution([]).
    
    print_rows_of_solution([row(Result,Op,RowValues)|Rows]) :-
       print_row(Result,RowValues,Op),
       print_rows_of_solution(Rows).
    
    print_row(Result,[RowEntry|RowEntries],Op) :-
       format("~q = ~q",[Result,RowEntry]),
       print_row_2(RowEntries,Op).
       
    print_row_2([RowEntry|RowEntries],Op) :-
       ((Op == mult) -> OpChar = '*'
       ; (Op == add)  -> OpChar = '+'
       ; OpChar = '?'),
       format(" ~q ~q",[OpChar,RowEntry]),
       print_row_2(RowEntries,Op).
       
    print_row_2([],_) :-
       format("~n").
    

    solve_row_sum_or_multiply/2

    这已被修改为还收集在第二个参数(列表)中选择的操作。

    请注意,“maplist-all-distinct over the matrix”没有在这里完成,因为这似乎是错误的。

    solve_row_sum_or_multiply([],[]).
    
    solve_row_sum_or_multiply([Row|MoreRows],[mult|Ops]) :-
       Row = [X|_Xs],
       X >= 25,   
       solve_multiply(Row), % we now have imposed a product constraint on the current Row
       solve_row_sum_or_multiply(MoreRows,Ops).
    
    solve_row_sum_or_multiply([Row|MoreRows],[add|Ops]) :-
       Row = [X|_Xs],
       X < 25,
       solve_sum(Row), % we now have imposed a sum constraint on the current Row
       solve_row_sum_or_multiply(MoreRows,Ops).
     
    solve_row_sum_or_multiply([Row|MoreRows],[mult|Ops]) :-
       Row = [X|_Xs],
       X < 25,
       solve_multiply(Row), % alternatively, we now have imposed a product constraint on the current Row   
       solve_row_sum_or_multiply(MoreRows,Ops).
    

    最后是“主要”谓词

    trial_run/1 中,我们规定除了X32 之外的所有变量都是不同的,并且X32 是1。如果X32 在“不同的集合”中,则没有一个变量可以取值1 - 那里在那种情况下是没有解决办法的。

    我们还强制进行排序以仅获得一组“规范”解决方案:

    我们使用bagof/3收集所有可能的解决方案和标记:

    trial_run(Solutions) :-
       all_distinct([X11,X12,X13,X21,X22,X23,X31,X33]),
       X32=1,
       X11 #=< X12, X12 #=< X13,
       X21 #=< X22, X22 #=< X23,
       X31 #=< X33,
       % multiple solutions solving x labeling may exist; collect them all
       bagof(
          [
             row(14,Op1,[X11,X12,X13]),
             row(15,Op2,[X21,X22,X23]),
             row(28,Op3,[X31,X32,X33])
          ],
          (
             solve_row_sum_or_multiply( [[14,X11,X12,X13],[15,X21,X22,X23],[28,X31,X32,X33]], [Op1,Op2,Op3] ),
             label([X11,X12,X13,X21,X22,X23,X31,X32,X33])
          ),   
          Solutions).
    

    有效吗?

    显然是的,而且只有一种解决方案:

    ?- trial_run(Solutions),print_solution(Solutions).
    ---
    14 = 2 + 3 + 9
    15 = 1 + 6 + 8
    28 = 4 * 1 * 7
    ---
    Solutions = [[row(14,add,[2,3,9]),row(15,add,[1,6,8]),row(28,mult,[4,1,7])]].
    

    【讨论】:

    • 谢谢,multiply 谓词有效,但组合谓词未按预期工作。我认为组合逻辑内部的逻辑有问题,但不知道。
    • 另外,感谢您的测试用例,谢谢。
    • 真的很感激!我还没学会怎么写测试用例。
    • @Woden 它们真的是救命稻草,比在调用过程时在多个维度上没有边缘情况的其他编程语言要好得多。只是一个 begin_tests/end_tests 块和里面的子句。测试断言应该在头脑中,但我更喜欢主体中的“断言”调用,“代码流”随着他们的眼睛移动(即你不必跳回头脑来查看正在测试的内容)。使用run_tests/0 运行。
    • @Woden 它似乎确实有效。找到 1 个解决方案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多