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