[net.lang.prolog] Farmer Puzzle

mob@mit-amt.MIT.EDU (Mario O. Bourgoin) (08/13/86)

First, I would like to thank all the persons who answered my request for
help. Your advice and  suggestions  were  most  useful  in  making  this
exercise a learning experience for me.

Thanks to:


   - Dave  Broderick. He was the first to send me a solution to the
     farmer problem. His program modeled the state as  composed  of
     left  and  right  riverbank lists and the problem as finding a
     sequence of sequential addition/deletions from these lists  to
     reach the goal. As an example:


     Start state:    river(farmer+[fox,goose,grain],[]).
     Goal state:     river([],farmer+[fox,goose,grain]).


   - Chrisj@basser.oz. He was the last to send me a solution to the
     farmer problem. I got it just as I was  writing  this  article
     and  haven't  had time to try it out yet. His answer is to the
     Australian  version  of  the  problem,  involving   a   Koala,
     Eucalyptus leaves, a Dingo and of course a Farmer.

   - Maarten  van  Emden  mhvanemden.  His  reply  was to send me a
     problem set which included questions about moving from node to
     node  through  a graph which was where my head space was at at
     the time.    This  covered  two  methods  to  prevent  program
     looping:   the   depth-bound  approach  and  the  path  record
     approach. The problem set also included many  questions  which
     brought  home  other points that were essential in cleaning up
     my model of Prolog's behavior. This reply  was  without  doubt
     the most useful one.

   - Ed  Windes. He pointed out the problem of the path/2 procedure
     being left-recursive and also mentioned the need for keeping a
     record  of  visited  states  to  prevent looping while walking
     through the problem state graph.

   - Wayne Cook and Suzanne Dietrich. Both made the suggestion that
     already  seen  states  be asserted and looked up as a means of
     controlling looping. I had already used  the  technique  in  a
     small  classification  expert  system  as  a means of avoiding
     solving for already discovered facts.

     Truth Maintenance Systems (TMS) accomplish this caching in  an
     efficient  way that allows easy checking of which facts are or
     are  not  applicable  to  a  situation.    TMSs  also  replace
     chronological    backtracking    with    dependency   directed
     backtracking for greater search  efficiency.  Johan  de  Kleer
     (Artificial Intelligence, Vol. 28, No.  1, 1986) of Xerox PARC
     has designed an assumption based  TMS  which  eliminates  most
     backtracking  of  all  types.    Maybe  such  systems  will be
     included in future versions of Prolog.

   - Chuck Restivo. For being the moderator to this  newsgroup  and
     sending  my  request for help to places that may not have been
     reached otherwise.


------------------------------------------------------------------------


On to the problem itself. There were three problems that  prevented  the
original program from finding _a_ solution to the farmer problem:


   - There was a typo in the statement of the linked/2 procedure.

   - The  path/2  procedure  was left recursive. While the solution
     statement was logically correct,  the  left  recursion  caused
     much unnecessary searching to find a solution.  This was fixed
     by replacing the left call to a call to linked/2.

   - The path/2 procedure did not restrict alternate linked  states
     to those not yet encountered.  This was fixed by adding a Path
     variable to the input(?) list of the path/2  predicate  making
     it into a path/3 predicate.


While the last two fixes are not in theory essential to the finding of a
solution, not fixing them results in stack overflow  before  a  solution
can  be found. If Prolog refused to solve for goals already encountered,
this problem would not exist. A hash table of  calls  would  provide  an
efficient  solution  to  the  problem  of  whether  a state has been met
already given a consistent  encoding  of  variable  names.  Can  someone
provide me with a reason other than efficiency for why this is not done?

Ed  Windes  and Suzanne Dietrich pointed out that the linked/2 procedure
did not produce all of correct  linked  states  for  the  state(1,1,1,1)
input.  This  was  due  only  to the typo in the third and clause of the
predicate. With this fixed, the predicate is able to generate all of the
linked states, albeit with much redundancy.

There  are  two answers to the Farmer Puzzle. While with the above fixes
my program would manage to produce a correct  solution,  resolution  for
alternate   answers   would  repeat  many  of  the  already  encountered
solutions. The number of repetitions has an upper bound of 2^58 as  near
as  I can calculate. The 58 was obtained by counting the number of legal
second states produced by Prolog  using  linked/2  and  subtracting  the
total  number  of legal states from the result. Can someone substantiate
this answer?

The  problem   is   that   both   legal/1   and   linked/2   are   loose
characterizations of what they claim to represent so that they reproduce
previously given answers  upon  resolution.  legal/1  for  example  will
produce  16  legal  states  while  there  are only 10 legal ones. 16 was
obtained through figuring out the number  of  states  that  legal/1  can
produce.  Can  someone  give  me  an  explanation  or  a  pointer  to an
explanation as to how to calculate the number of repeated resolutions to
linked/2 or similar predicates?

Another  problem  of efficiency was that my statement of the problem did
not  take  advantage  of  Prolog's  unification  procedure  for  stating
similarity  between elements of states. Taking advantage of this feature
eliminates the same(yes,_,_) predicate  and  associated  matching.  This
results  in  up  to 2.5 times! fewer procedure calls. This statistic was
gathered by hand. Can someone point me to tools that  allow  me  to  get
statistics from Prolog? I use C-Prolog. Better yet, can someone point me
to theory that would allow me to calculate the expected number of  calls
a particular problem statement will require to generate all solutions?


------------------------------------------------------------------------


Here is the final solution:


/* The problem solver keeps track of the current  state as a predicate
   of the form:

        state(Farmer,Fox,Goose,Grain).

   Where Farmer is abbreviated F, Fox is X, Goose is G and Grain is A.
   Each  place can take  on a value of  either  1 or  2,  each  number
   representing a side of the river.

*/

/* Test for membership of an element in a list */
member(E,[E|_]).
member(E,[_|T]) :- member(E,T).

/* Define sides 1 and 2 as being opposite */
opposite(1,2).
opposite(2,1).

/* A legal state is one where either the farmer is on the same side as
   the goose or is on the same side as both the fox and  the grain. In
   the latter case, the condition that  the farmer  be on the opposite
   side from the goose is necessary to prevent resolutions that repeat
   states already covered by the first legal/1 definition.
*/
legal(state(F,_,F,_)).
legal(state(F,F,G,F)) :- opposite(F,G).

/* There are four cases for linked states:

   Only the farmer changes sides */
linked(state(F1,X,G,A),state(F2,X,G,A)) :-
        opposite(F1,F2),
        legal(state(F2,X,G,A)).

/* The farmer and the fox change sides */
linked(state(F1,F1,G,A),state(F2,F2,G,A)) :-
        opposite(F1,F2),
        legal(state(F2,F2,G,A)).

/* The farmer and the goose change sides */
linked(state(F1,X,F1,A),state(F2,X,F2,A)) :-
        opposite(F1,F2),
        legal(state(F2,X,F2,A)).

/* The farmer and the grain change sides */
linked(state(F1,X,G,F1),state(F2,X,G,F2)) :-
        opposite(F1,F2),
        legal(state(F2,X,G,F2)).

/* This is the  top  level   predicate whose purpose  is  to establish
   whether there is  a non-circular path from  the Start state to  the
   Goal state. Solution is that path. path/3 calls path/4 with  a list
   of the Start state as the germ of the already seen states list.
*/
path(Start,Goal,Solution) :-
        path(Start,[Start],Goal,Solution).

/* A path  from State to  Goal  exists if State  is  linked with Goal.
   Path is the list of states encountered up to now.  Solution is Path
   prepended with Goal.
*/
path(State,Path,Goal,[Goal|Path]) :-
        linked(State,Goal).

/* A path from  State1 to  Goal exists if  there exists a State2 which
   has not yet been encountered, which is  linked with State1 and  for
   which a path exists to Goal.  State2 is part of the  list of states
   encountered up to now and Solution  is the  list of states  leading
   from the original state to Goal.
*/
path(State1,Path,Goal,Solution) :-
        linked(State1,State2),
        not(member(State2,Path)),
        path(State2,[State2|Path],Goal,Solution).


--Mario O. Bourgoin