代码之家  ›  专栏  ›  技术社区  ›  fferri

如何更好地解决“破解密码”逻辑难题【复制】

  •  0
  • fferri  · 技术社区  · 6 年前

    我刚开始学习prolog,但我一直在努力解决这个难题:

    Alt

    我试着添加一些规则,比如这个例子 http://swish.swi-prolog.org/example/houses_puzzle.pl

    我目前所做的努力:

    % Render the houses term as a nice table.
    :- use_rendering(table,
             [header(h('N1', 'N2', 'N3'))]).
    numbers(Hs) :-
        length(Hs, 1),
        member(h(6,8,2), Hs),
        member(h(6,1,4), Hs),
        member(h(2,0,6), Hs),
        member(h(7,3,8), Hs),
        member(h(7,8,0), Hs),
        correct_and_placed(6, 8, 2, Hs).
    
    correct_and_place(A, B, C, R).
    

    但我甚至不知道如何编写一个规则来检查一个数字是否正确并且在正确的位置。

    0 回复  |  直到 8 年前
        1
  •  4
  •   mat    8 年前

    对于现有答案,我想添加一个使用 CLP(FD)约束 .

    我将使用的两个构建块是 num_correct/3 num_well_placed/3 .

    第一, ,将两个整数列表与 常见的

    num_correct(Vs, Ns, Num) :-
            foldl(num_correct_(Vs), Ns, 0, Num).
    
    num_correct_(Vs, Num, N0, N) :-
            foldl(eq_disjunction(Num), Vs, 0, Disjunction),
            Disjunction #<==> T,
            N #= N0 + T.
    
    eq_disjunction(N, V, D0, D0 #\/ (N #= V)).
    

    示例查询:

    ?- num_correct([1,2,3], [3,5], Num).
    Num = 1.
    

    与纯关系的特征一样,这也适用于更一般的查询,例如:

    ?- num_correct([A], [B], Num).
    B#=A#<==>Num,
    Num in 0..1.
    

    第二,我使用 ,它将两个整数列表与相应元素所在的索引数相关联 :

    num_well_placed(Vs, Ns, Num) :-
            maplist(num_well_placed_, Vs, Ns, Bs),
            sum(Bs, #=, Num).
    
    num_well_placed_(V, N, B) :- (V #= N) #<==> B.
    

    ?- num_well_placed([8,3,4], [0,3,4], Num).
    Num = 2.
    

    下面的谓词将这两个简单地结合在一起:

    num_correct_placed(Vs, Hs, C, P) :-
            num_correct(Vs, Hs, C),
            num_well_placed(Vs, Hs, P).
    

    因此,整个谜题可以表述如下:

    lock(Vs) :-
            Vs = [_,_,_],
            Vs ins 0..9,
            num_correct_placed(Vs, [6,8,2], 1, 1),
            num_correct_placed(Vs, [6,1,4], 1, 0),
            num_correct_placed(Vs, [2,0,6], 2, 0),
            num_correct_placed(Vs, [7,3,8], 0, 0),
            num_correct_placed(Vs, [7,8,0], 1, 0).
    

    在这种情况下需要:

    ?- lock(Vs).
    Vs = [0, 4, 2].
    

    而且,如果我 泛化

    lock(Vs) :-
            Vs = [_,_,_],
            Vs ins 0..9,
            num_correct_placed(Vs, [6,8,2], 1, 1),
            num_correct_placed(Vs, [6,1,4], 1, 0),
            num_correct_placed(Vs, [2,0,6], 2, 0),
            num_correct_placed(Vs, [7,3,8], 0, 0),
            * num_correct_placed(Vs, [7,8,0], 1, 0).
    

    那么唯一的解决方案可以 不经搜索就确定:

    ?- lock(Vs).
    Vs = [0, 4, 2].
    

    事实上,我甚至可以 去掉倒数第二个提示:

    lock(Vs) :-
            Vs = [_,_,_],
            Vs ins 0..9,
            num_correct_placed(Vs, [6,8,2], 1, 1),
            num_correct_placed(Vs, [6,1,4], 1, 0),
            num_correct_placed(Vs, [2,0,6], 2, 0),
            * num_correct_placed(Vs, [7,3,8], 0, 0),
            * num_correct_placed(Vs, [7,8,0], 1, 0).
    

    仍然 这个解决方案是独一无二的,尽管我现在必须使用 label/1 要找到它:

    ?- lock(Vs), label(Vs).
    Vs = [0, 4, 2] ;
    false.
    
        2
  •  2
  •   max66    8 年前

    我希望有更好的方法但是。。。

    您可以实现“一个数字是正确的,位置正确”,如下所示

    oneRightPlace(X, Y, Z, X, S2, S3) :-
      \+ member(Y, [S2, S3]),
      \+ member(Z, [S2, S3]).
    
    oneRightPlace(X, Y, Z, S1, Y, S3) :-
      \+ member(X, [S1, S3]),
      \+ member(Z, [S1, S3]).
    
    oneRightPlace(X, Y, Z, S1, S2, Z) :-
      \+ member(X, [S1, S2]),
      \+ member(Y, [S1, S2]).
    

    oneWrongPlace(X, Y, Z, S1, S2, S3) :-
      member(X, [S2, S3]),
      \+ member(Y, [S1, S2, S3]),
      \+ member(Z, [S1, S2, S3]).
    
    oneWrongPlace(X, Y, Z, S1, S2, S3) :-
      member(Y, [S1, S3]),
      \+ member(X, [S1, S2, S3]),
      \+ member(Z, [S1, S2, S3]).
    
    oneWrongPlace(X, Y, Z, S1, S2, S3) :-
      member(Z, [S1, S2]),
      \+ member(X, [S1, S2, S3]),
      \+ member(Y, [S1, S2, S3]).
    

    对于“两个数字是正确的,但位置错误”,你可以写

    twoWrongPlace(X, Y, Z, S1, S2, S3) :-
      member(X, [S2, S3]),
      member(Y, [S1, S3]),
      \+ member(Z, [S1, S2, S3]).
    
    twoWrongPlace(X, Y, Z, S1, S2, S3) :-
      member(X, [S2, S3]),
      member(Z, [S1, S2]),
      \+ member(Y, [S1, S2, S3]).
    
    twoWrongPlace(X, Y, Z, S1, S2, S3) :-
      member(Y, [S1, S3]),
      member(Z, [S1, S2]),
      \+ member(X, [S1, S2, S3]).
    

    而且,对于“没有什么是正确的”,变得简单

    zeroPlace(X, Y, Z, S1, S2, S3) :-
      \+ member(X, [S1, S2, S3]),
      \+ member(Y, [S1, S2, S3]),
      \+ member(Z, [S1, S2, S3]).
    

    现在你可以把所有的东西放在一起写了

      member(S1, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
      member(S2, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
      member(S3, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
      oneRightPlace(6, 8, 2, S1, S2, S3),
      oneWrongPlace(6, 1, 4, S1, S2, S3),
      twoWrongPlace(2, 0, 6, S1, S2, S3),
      zeroPlace(7, 3, 8, S1, S2, S3),
      oneWrongPlace(7, 8, 0, S1, S2, S3).
    

    S1 , S2 S3 )正确的解决方案。

        3
  •  2
  •   Jim Ashworth    8 年前

    所以,和所有的问题一样,我倾向于写一个通用的解算器,而不是一个特定的解算器。从 mastermind implementation 我不久前写了一篇文章(由这里的一个问题引出)我提出了以下内容:

    compare(List,Reference,RightPlace,WrongPlace)

    right_place(List,Reference,RightPlace) 它包装了一个累加器并使用每个列表头的元素,在匹配的位置递增,然后。。。

    any_match(List,Reference,Matches) 它包装了一个累加器,该累加器消耗列表的头,并在可能的情况下从引用列表中选择它,在发生这种情况的位置递增。

    WrongPlace 则是 RightPlace Matches .

    find_solutions(Soln) forall ,以确保满足所有提示约束。把它们和提示放在一起,你就会得到:

    :- use_module(library(clpfd)).
    
    compare(List,Reference,RightPlace,WrongPlace) :-
        right_place(List,Reference,RightPlace),
        any_match(List,Reference,Matches),
        WrongPlace #= Matches - RightPlace.
    
    right_place(List,Reference,RightPlace) :-
        right_place(List,Reference,0,RightPlace).
    
    right_place([],[],RightPlace,RightPlace).
    right_place([Match|List],[Match|Reference],Accumulator,RightPlace) :-
        NewAccumulator is Accumulator + 1,
        right_place(List,Reference,NewAccumulator,RightPlace).
    right_place([A|List],[B|Reference],Accumulator,RightPlace) :-
        A \= B,
        right_place(List,Reference,Accumulator,RightPlace).
    
    any_match(List,Reference,Matches) :-
        any_match(List,Reference,0,Matches).
    
    any_match([],_,Matches,Matches).
    any_match([Match|List],Reference,Accumulator,Matches) :-
        select(Match,Reference,NewReference),
        NewAccumulator is Accumulator + 1,
        any_match(List,NewReference,NewAccumulator,Matches).
    any_match([Match|List],Reference,Accumulator,Matches) :-
        \+member(Match,Reference),
        any_match(List,Reference,Accumulator,Matches).
    
    find_solutions(Soln) :-
        length(Soln,3),
        Soln ins 0..9,
        maplist(indomain,Soln),
        forall(hint(X,Y,Z),compare(Soln,X,Y,Z)).
    
    hint([6,8,2],1,0).
    hint([6,1,4],0,1).
    hint([2,0,6],0,2).
    hint([7,3,8],0,0).
    hint([7,8,0],0,1).
    
        4
  •  1
  •   Tomas By    8 年前

    我不确定我需要解释这么多。生成所有的可能性,然后编写约束条件。

    code(A,B,C) :-
      member(A,[0,1,2,3,4,5,6,7,8,9]),
      member(B,[0,1,2,3,4,5,6,7,8,9]),
      member(C,[0,1,2,3,4,5,6,7,8,9]),
      ( A = 6 ; B = 8 ; C = 2 ),
      ( A = 1, \+ member(B,[6,4]), \+ member(C,[6,4])
      ; A = 4, \+ member(B,[6,1]), \+ member(C,[6,1])
      ; B = 6, \+ member(A,[1,4]), \+ member(C,[1,4])
      ; B = 4, \+ member(A,[6,1]), \+ member(C,[6,1])
      ; C = 6, \+ member(B,[1,4]), \+ member(A,[1,4])
      ; C = 1, \+ member(B,[6,4]), \+ member(A,[6,4]) ),
      ( A = 0, B = 2, C \= 6
      ; A = 0, B = 6, C \= 2
      ; A = 6, B = 2, C \= 0
      ; B = 2, C = 0, A \= 6
      ; B = 6, C = 2, A \= 0
      ; B = 6, C = 0, A \= 2
      ; C = 2, A = 0, B \= 6
      ; C = 2, A = 6, B \= 0
      ; C = 0, A = 6, B \= 2 ),
      \+ member(A,[7,3,8]), \+ member(B,[7,3,8]), \+ member(C,[7,3,8]),
      ( A = 8, \+ member(B,[7,0]), \+ member(C,[7,0])
      ; A = 0, \+ member(B,[7,8]), \+ member(C,[7,8])
      ; B = 7, \+ member(A,[8,0]), \+ member(C,[8,0])
      ; B = 0, \+ member(A,[7,8]), \+ member(C,[7,8])
      ; C = 7, \+ member(B,[8,0]), \+ member(A,[8,0])
      ; C = 8, \+ member(B,[7,0]), \+ member(A,[7,0]) ).
    

    结果如下:

    | ?- code(A,B,C).
    A = 0,
    B = 4,
    C = 2 ? ;
    no