Search code examples
prologcryptarithmetic-puzzle

How to write a Prolog solver to solve this logic problem?


I have spent some time learning Prolog, and have some basic understanding of Prolog concepts like facts, rules, lists. But still find it hard to use Prolog as a tool to solve logic problems. For example, the following one:

Guess the number with following facts:
2741: A digit is right, but it's in the wrong place.
4132: Two digits are right, but it's in the wrong place.
7642: None of the digits are right.
9826: One digit is correct and in the right place.
5079: Two digits are right, one is in the right place and 
      the other is in the wrong place.

I manually solved this problem, the answer is 9013. How can I write a Prolog problem to solve this problem? Right now I prefer not using any modules, for learning purposes.


Solution

  • Generalizing my code from the previous entry about this type of puzzle, we get

    check( Sol, Guess, NValues-NPlaces ) :-
        findall( t, (member(E, Guess), member(E, Sol)), Values ),
        length( Values, NValues ), 
        maplist( eq, Sol, Guess, NS),
        sum_list( NS, NPlaces).
    
    eq(A,B,X) :- A =:= B -> X=1 ; X=0.
    
    select([X|XS],Dom) :- select(X,Dom,Dom2), select(XS,Dom2).
    select([],_).
    
    puzzle( [A,B,C,D] ) :-   
        Dom = [0, 1, 3, 5, 8, 9],          % using hint 3 
        select( [A,B,C,D], Dom),           % with unique digits
        maplist( check([A,B,C,D]), 
                 [[2,7,4,1], [4,1,3,2], [9,8,2,6], [5,0,7,9]],
                 [ 1-0,      2-0,       1-1,       2-1      ] ).
    

    Trying it out:

    164 ?- time( puzzle(X) ).
    % 33,274 inferences, 0.016 CPU in 0.011 seconds (142% CPU, 2132935 Lips)
    X = [9, 0, 1, 3] ;
    % 5,554 inferences, 0.016 CPU in 0.002 seconds (780% CPU, 356023 Lips)
    false.
    

    Or we can inline the definitions, fusing them together as

    mmd(Sol):-
      %%maplist( dif, [2,7,4,1], Sol),         % 1.
      maplist( dif, [4,1,3,2], Sol),           % 2.
      maplist( eq1, Sol, [9,8,2,6], NS4),
                          sum_list( NS4, 1),   % 4.
      maplist( eq1, Sol, [5,0,7,9], NS5),
                          sum_list( NS5, 1),   % 5.
      select( Sol, [0,1,3,5,8,9]),             % 3.
    
      %%findall( t, (member(E, [2,7,4,1]), member(E, Sol)), [_] ), % 1.
      findall( t, (member(E, [4,1,3,2]), member(E, Sol)), [_,_] ), % 2.
      %%findall( t, (member(E, [9,8,2,6]), member(E, Sol)), [_] ), % 4.
      findall( t, (member(E, [5,0,7,9]), member(E, Sol)), [_,_] ). % 5.
    
    eq1(A,B,C) :- (A#=B,C=1 ; dif(A,B),C=0).
    

    rearranging the sub-goals to constrain the search space as much as possible, so the solution is found almost instantly and the inferences overall are cut more than in half:

    167 ?- time( mmd(S) ).
    % 1,219 inferences, 0.000 CPU in 0.001 seconds (0% CPU, Infinite Lips)
    S = [9, 0, 1, 3] ;
    % 13,714 inferences, 0.000 CPU in 0.001 seconds (0% CPU, Infinite Lips)
    false.
    

    As was first noticed in the other answer, not all clues are actually needed, and removing them even reduces the number of inferences needed to solve this by some additional small margin.