2

in most implementations that I see, using a recursive solution. But, I don´t want this. I want search in tree with a search algorithm, like a breadth-fisrt or depth-first.

thank you

Faith No More
  • 21
  • 1
  • 2
  • 1
    Why don't you want recursion? How do you imagine your desired solution to work? Recursion really is the natural way to do it in Prolog. – Junuxx Jun 19 '12 at 22:05
  • Yes, but is this a school project, and teacher want in breadth-fisrt or depth-first... – Faith No More Jun 19 '12 at 22:26
  • 1
    Did he also say it had to be non-recursive? – Junuxx Jun 19 '12 at 22:28
  • Only he want is a search algorithm in a tree of the all nodes that Hanoi have. – Faith No More Jun 19 '12 at 23:09
  • 1
    The basic pattern of Prolog's engine seeking a solution can be said to be depth-first. Simulating a breadth-first search can be done, but requires setting up "bookeeping" data structures to enforce that. Recursive only means that a predicate has rules that invoke itself, and it does not rule out either depth-first or breadth-first search strategies. – hardmath Jun 20 '12 at 18:43
  • 1
    recursive is not an algorithm. It's just the fact for a predicate to call itself. both breadth-first and depth-first are usually implemented through recursion in prolog (and in other declarative languages too usually). – m09 Jun 21 '12 at 11:25
  • 3
    Nobody take care to say that in Prolog it's nearly impossible to solve *any* task without recursion? And that doesnt' make *any* sense, anyway. – CapelliC Jun 25 '12 at 06:08

2 Answers2

2

BFS may be something like that (works with SWI-Prolog)

% to store the tree of the possibilities
:- dynamic lst_hanoi/1.

hanoi_BFS :-
    init,

    % BFS loop
    repeat,
    next,
    finish(Hanoi),

    % display the solution
    reverse(Hanoi, Rev_Hanoi),
    maplist(writeln, Rev_Hanoi).


init :-
    % cleaning of the bdd
    retractall(lst_hanoi(_)),

    % store the initial list of configurations (only one)
    assert(lst_hanoi([[hanoi([1,2,3,4], [], [])]])).

% we search the final configuration
% here the first two columns are empty
finish([hanoi([], [], A) | B]) :-
    % get the list of configurations
    lst_hanoi(Lst),
    % test
    member([hanoi([], [], A) | B], Lst).

next :-
    % get the actual list of configurations
    retract(lst_hanoi(Hanoi)),

    % act on each configuration
    maplist(move_next,Hanoi, Hanoi_Temp),

    % concatenate the list of new onfigurations
    append(Hanoi_Temp, Hanoi_1),

    % some configurations are empty, remove them
    % SWI-Prolog feature
    exclude(=([]), Hanoi_1, Hanoi_2),

    % store the new list
    assert(lst_hanoi(Hanoi_2)).

% compute the next configurations got from one configuration
move_next([Hanoi | T1], R) :-
    % Only the first element of the list is usefull
    % compute possible moves
    move(Hanoi, Next),
    % create the new configuration
    maplist(new_hanoi([Hanoi| T1]), Next, R).

% add the new position if it has not been already seen
new_hanoi(T, H, [H | T]) :-
    \+member(H, T), !.

% else the configuration will be remove
new_hanoi(_T, _H, []).

% the list of all the possibilities of moves
move(hanoi([H | T], [], []), [hanoi(T, [H], []), hanoi(T, [], [H])]).

move(hanoi([], [H | T], []), [hanoi([H], T, []), hanoi([], T, [H])]).

move(hanoi([], [], [H | T]), [hanoi([H], [], T), hanoi([], [H], T)]).


move(hanoi([H1 | T1], [H2 | T2], []),
     [hanoi(T1, [H2 | T2], [H1]), hanoi([H1 | T1], T2, [H2]),  hanoi([H2, H1 | T1], T2, [])]) :-
    H1 > H2, !.

move(hanoi([H1 | T1], [H2 | T2], []),
     [hanoi(T1, [H2 | T2], [H1]), hanoi([H1 | T1], T2, [H2]),  hanoi(T1, [H1, H2 | T2], [])]).

move(hanoi([H1 | T1], [], [H2 | T2]),
     [hanoi(T1, [H1], [H2 | T2]), hanoi([H1 | T1], [H2], T2),  hanoi([H2, H1 | T1], [], T2)]) :-
    H1 > H2, !.

move(hanoi([H1 | T1], [], [H2 | T2]),
     [hanoi(T1, [H1], [H2 | T2]), hanoi([H1 | T1], [H2], T2),  hanoi(T1, [], [H1, H2 | T2])]).

move(hanoi([], [H1 | T1], [H2 | T2]),
     [hanoi([H1], T1, [H2 | T2]), hanoi([H2], [H1 | T1], T2),  hanoi([], [H2, H1 | T1], T2)]) :-
    H1 > H2, !.

move(hanoi([], [H1 | T1], [H2 | T2]),
     [hanoi([H1], T1, [H2 | T2]), hanoi([H2], [H1 | T1], T2),  hanoi([], T1, [H1, H2 | T2])]).


move(hanoi([H1 | T1], [H2 | T2], [H3 | T3]),
     [hanoi(T1, [H1, H2 | T2], [H3 | T3]),
      hanoi(T1, [H2 | T2], [H1, H3 | T3]),
      hanoi([H1 | T1] , T2, [H2, H3 | T3])]) :-
     H1 < H2, H2 < H3, !.

move(hanoi([H1 | T1], [H2 | T2], [H3 | T3]),
     [hanoi(T1, [H1, H2 | T2], [H3 | T3]),
      hanoi(T1, [H2 | T2], [H1, H3 | T3]),
      hanoi([H1 | T1] , [H3, H2 | T2 ], T3)]) :-
     H1 < H3, H3 < H2, !.

move(hanoi([H1 | T1], [H2 | T2], [H3 | T3]),
     [hanoi([H2, H1 | T1], T2, [H3 | T3]),
      hanoi([H1 |T1], T2, [H2, H3 | T3]),
      hanoi(T1 , [H2 | T2 ], [H1 , H3 | T3])]) :-
     H2 < H1, H1 < H3, !.

move(hanoi([H1 | T1], [H2 | T2], [H3 | T3]),
     [hanoi([H2, H1 | T1], T2, [H3 | T3]),
      hanoi([H1 |T1], T2, [H2, H3 | T3]),
      hanoi([H3, H1 | T1] , [H2 | T2 ], T3)]) :-
     H2 < H3, H3 < H1, !.

move(hanoi([H1 | T1], [H2 | T2], [H3 | T3]),
     [hanoi([H3, H1 | T1], [H2 | T2], T3),
      hanoi([H1 |T1], [H3, H2 |T2], T3),
      hanoi(T1 , [H1, H2 | T2 ], [H3 | T3])]) :-
     H3 < H1, H1 < H2, !.

move(hanoi([H1 | T1], [H2 | T2], [H3 | T3]),
     [hanoi([H3, H1 | T1], [H2 | T2], T3),
      hanoi([H2, H1 |T1], T2, [H3 |T3]),
      hanoi([H1 | T1] , [H3, H2 | T2 ], T3)]) :-
     H3 < H2, H2 < H1, !.

This code may be improved !

joel76
  • 5,565
  • 1
  • 18
  • 22
0

OK, so this should be real easy. Really, everything should be easy in Prolog - where writing down the question is usually the solution itself. :)

So, what do we have? Three towers, say t(X,Y,Z). Each tower is a sequence of disks, in decreasing order of their sizes - only a smaller disk can go on top a bigger one, and not the opposite:

move1( t([A|B],[C|D],E), t(B,[A,C|D],E) ) :- A < C, writeln([move,A,on,C]).
move1( t([A|B],[],E),    t(B,[A],E)     ) :-      writeln([move,A,on,empty]).

These two rules describe a move from 1st tower to the second, either on top a bigger disk, or onto an empty tower. So these are the only moves possible, moving the top disk from the first pole to the second. But we can also move a disk onto a 3rd pole, or from 2nd or 3rd pole:

move( t(A,B,C), t(X,Y,Z)):- 
  move1( t(A,B,C), t(X,Y,Z)) ; move1( t(A,C,B), t(X,Z,Y)) ;
  move1( t(B,C,A), t(Y,Z,X)) ; move1( t(B,A,C), t(Y,X,Z)) ;
  move1( t(C,A,B), t( ... )) ; move1( t(C,B,A), t( ... )).

You see, we just write down what we already know about the question. So now we have the predicate move/2 which describes any possible move from a given position (its 1st argument), giving us the new position (its 2nd argument).

This is the predicate which defines our search space for us here. At each point, we make a move, and see whether we have a solution. This is a depth-first search, because we make a move and plunge deeper with it, hoping it will lead us to our goal.

hanoi( Start, Finish):- move(Start,Finish), write('Done.'),nl.
hanoi( Start, Finish):- move(Start, P), hanoi(P,Finish).

We run it e.g. with hanoi(t([1,2],[],[]), t([],[1,2],[])). Now what happens? It loops endlessly, trying same moves over and over again. But even if it were to try new ones and eventually hit the finishing state, we wouldn't be able to say which moves led it there - right now it just prints out every move it tries, even such that lead nowhere.


So we need not to write the moves out right away, but maintain the list of moves, and only when we hit the finishing state will we write out the successful list of moves that brought us there. We can now also check this list of moves, in order not to repeat the moves which we've already made:

move1( t([A|B],[C|D],E), t(B,[A,C|D],E) ) :- A < C.
move1( t([A|B],[],E),    ...            ).

move( P, P2, M, [P2|M] ):- move(P,P2), \+memberchk(P2,M).

Here M is "moves so far", i.e. a list of all the positions visited so far, and the 4th argument is the new list of positions - if our new position wasn't visited before (or else we'd go in circles, like our 1st version did).

Now we call it as

hanoi(Start,Finish,N):- hanoi_dfs(Start,Finish,[Start],N).

hanoi_dfs( Start, Finish, M, N):- move(Start, Finish, M, M2), 
                                  length(M2,K), N is K-1,
                                  reverse(M2,L), maplist(writeln,L), nl.
hanoi_dfs( Start, Finish, M, N):- move(Start, P, M, M2), 
                                  hanoi_dfs(P, Finish, M2, N).

and we get

?- hanoi(t([1,2],[],[]), t([],[1,2],[]), _).
t([1, 2], [], [])
t([2], [1], [])
t([], [1], [2])
t([], [], [1, 2])
t([1], [], [2])
t([1], [2], [])
t([], [1, 2], [])

But, this DFS is implicit - we rely on Prolog to try out the moves. Explicit search finds all possible moves for each position, and adds these moves to the operations buffer, which will contain pairs - each of a position and a list of moves that lead to that position.

Now, explicit DFS works by removing the first element from that list, finding all possible moves for that position, thus getting a list of new positions, each paired up with its list of moves - and prefixing this list back to the beginning of the operations buffer.

BFS differs from DFS in that it appends this list of new positions to the end of the operations buffer.

Will Ness
  • 70,110
  • 9
  • 98
  • 181