Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lock Challenge in Prolog

Tags:

prolog

I just started learning prolog and I'm stuck trying to solve this puzzle :

Alt

I tried to add some rules like this example http://swish.swi-prolog.org/example/houses_puzzle.pl but I couldn't come up with a solution.

What I tried so far:

% 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).

But I don't even know how to write a rule that can check if a number is correct and on the right place.

like image 860
William Okano Avatar asked Oct 11 '25 09:10

William Okano


2 Answers

To the existing answers, I would like to add a version using CLP(FD) constraints.

The two building blocks I shall use are num_correct/3 and num_well_placed/3.

First, num_correct/3, relating two lists of integers to the number of common elements:

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)).

Sample query:

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

As is characteristic for pure relations, this also works for much more general queries, for example:

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

Second, I use num_well_placed/3, which relates two lists of integers to the number of indices where corresponding elements are equal:

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.

Again, a sample query and answer:

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

The following predicate simply combines these two:

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

Thus, the whole puzzle can be formulated as follows:

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).

No search at all is required in this case:

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

Moreover, if I generalize away the last hint, i.e., if I write:

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).

then the unique solution can still be determined without search:

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

In fact, I can even also take away the penultimate hint:

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).

and still the solution is unique, although I now have to use label/1 to find it:

?- lock(Vs), label(Vs).
Vs = [0, 4, 2] ;
false.
like image 173
mat Avatar answered Oct 14 '25 20:10

mat


I hope there are better ways but...

You can implement "one number is correct and well placed" as follows

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]).

For "one number is correct but wrong placed, you can use

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]).

For "two number are correct but wrong placed", you can write

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]).

And, for "nothing is correct", become simply

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

Now you can put all togheter and write

  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).

obtaining (in S1, S2 and S3) the right solution.

The preceding examples are written without the use of clp(fd), that I don't know well but that (I suppose) can semplify a lot.

like image 32
max66 Avatar answered Oct 14 '25 21:10

max66



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!