0

The problem of the river crossing description: We have a farmer, a wolf, a goat and a cabbage and they need to cross the river with the following restrictions:

  1. The wolf can’t be on the shame side with the goat

  2. The goat can’t stay at the same side with the cabbage

  3. -Initial state: (‘L’,‘L’,‘L’,‘L’)

  4. -Finale state: (‘R’,‘R’,‘R’,‘R’)
    The state list describes where everyone is, (farmer, wolf, goat, cabbage) so state(‘L’,‘R’,‘L’,‘R’) means that the wolf and the cabbage are on the right side and the farmer and the goat are on the left side of the river. I don’t want to make it more complex by implementing list of lists. The problem with my code is that it starts with the initial state and after the first step (farmer and goat on the right) it stops. I can’t find any way to make the recursion to work in order to have an acceptable result. So I could use some help in order to make the recursion functional and work as intended. Thank you in advance.

    """ ----------------------------------------------------------------------------
    ******** Search Code for DFS  and other search methods
    ******** (expanding front and extending queue)
    ******** author:  
    """
    
    
    import copy
    
    #left, right of the river
    spaces = {
      'R': ['L'],
      'L': ['R']
    }
    
    def enter(state):
      if state[0]==state[1] and state[2]==state[3]:
        new_state=state=['R', state[1]] + ['R', state[3]]
        return new_state
    
    #function that swaps the side of the river that the farmer is
    def swap(state_l, i, j): 
    state_l[i] = state_l[j]
    return state_l
    
    '''
    operators for the movement of the farmer
    '''
    
    def neighbour1(state):
      elem=['L']
      i=state.index(elem) if elem in state else -1  #checking if the elem is in the state
      if i >=0:
        swap(state, i, spaces[i][0])
        return state
    
    def neighbour2(state):   
      elem=['L']
      i=state.index(elem) if elem in state else -1
      if i >=0:
        swap(state, i, spaces[i][1])
        return state
    
    
    """ ----------------------------------------------------------------------------
    **** FRONT managment
    **** 
    """
    
    def find_children(state):
    
      children=[]
    
      enter_state=copy.deepcopy(state)
      enter_child=enter(enter_state)
    
      tr1_state=copy.deepcopy(state)
      tr1_child=neighbour1(tr1_state)
    
      tr2_state=copy.deepcopy(state)
      tr2_child=neighbour2(tr2_state)
    
      if tr1_child is not None: 
          children.append(tr1_child)
    
      if tr2_child is not None: 
          children.append(tr2_child)
    
      if enter_child is not None: 
          children.append(enter_child)
    
      return children
    
    
    """ ----------------------------------------------------------------------------
    ** initialization of front
    ** 
    """
    
    def make_front(state):
      return [state]
    
    """ ----------------------------------------------------------------------------
    **** expanding front
    ****   
    """
    
    def expand_front(front, method):  
      if method=='DFS':        
        if front:
            print("Front:")
            print(front)
            node=front.pop(0)
            for child in find_children(node):     
                front.insert(0,child)
    
      elif method=='BFS':
        if front: 
            print("Front:")
            print(front)
            node=front.pop(0)
            for child in find_children(node):     
                front.append(child)
    return front
    '''
    elif method=='BestFS':
    #'''           
    #else: "other methods to be added"        
    
    
    """ ----------------------------------------------------------------------------
    **** QUEUE
    **** 
    """
    
    """ ----------------------------------------------------------------------------
    ** initialization of queue
    ** 
    """
    
    def make_queue(state):
      return [[state]]
    
    """ ----------------------------------------------------------------------------
    **** expanding queue
    **** επέκταση ουράς
    """
    
    def extend_queue(queue, method):
      if method=='DFS':
        print("Queue:")
        print(queue)
        node=queue.pop(0)
        queue_copy=copy.deepcopy(queue)
        children=find_children(node[-1])
        for child in children:
            path=copy.deepcopy(node)
            path.append(child)
            queue_copy.insert(0,path)
    
    elif method=='BFS': 
        if queue:
            print("Queue:")
            print(queue)
            node=queue.pop(0)
            queue_copy=copy.deepcopy(queue)
            children=find_children(node[-1])
            for child in children:
                path=copy.deepcopy(node)
                path.append(child)
                queue_copy.append(path)
    '''
    elif method=='BestFS':
    #'''
    #else: "other methods to be added" 
    
    return queue_copy
    
    """ ----------------------------------------------------------------------------
    **** Basic recursive function to create search tree (recursive tree expansion)
    **** 
    """
    
    def find_solution(front, queue, closed, method):
    
      if not front:
        print('_NO_SOLUTION_FOUND_')
    
      elif front[0] in closed:
        new_front=copy.deepcopy(front)
        new_front.pop(0)
        new_queue=copy.deepcopy(queue)
        new_queue.pop(0)
        find_solution(new_front, new_queue, closed, method)
    
      elif is_goal_state(front[0]):
        print('_GOAL_FOUND_')
        print(queue[0])
    
      else:
        closed.append(front[0])
        front_copy=copy.deepcopy(front)
        front_children=expand_front(front_copy, method)
        queue_copy=copy.deepcopy(queue)
        queue_children=extend_queue(queue_copy, method)
        closed_copy=copy.deepcopy(closed)
        find_solution(front_children, queue_children, closed_copy, method)
    
    
    """" ----------------------------------------------------------------------------
    ** Executing the code
    ** 
    """
    
    def is_goal_state(state):  
      if state == ['R','R','R','R']:
        return True 
    
    def main():
    
    initial_state = ['L','L','L','L']  
    method='DFS'
    
    """ ----------------------------------------------------------------------------
    **** starting search
    **** 
    """
    
    print('____BEGIN__SEARCHING____')
    find_solution(make_front(initial_state), make_queue(initial_state), [], method)
    
    
    if __name__ == "__main__":
      main()
    
  • Do you expect someone to read and understand your whole code and explain to you how to improve it? Is there any way you can make a much shorter code that reproduces the same failure, so we can focus on that? – Stef Nov 23 '20 at 19:53
  • Not the entire code. The only methods that need looking I think are the swap and the neighborx. These three I think cause the problem but I don't know why. All the other functions are tested and working. – Jonathan Baxevanidis Nov 23 '20 at 19:58
  • The `swap` function puzzles me because the comments say "swaps the side of the river the farmer is in" but if that's really what it does, what are the `i` and `j` parameters? Just changing the farmer's side would be something like `def move_farmer(state): f,w,g,c = state; new_f = 'R' if f == 'L' else 'L'; return (new_f, w, g, c)` – Stef Nov 23 '20 at 20:02
  • It says that is changes the side that the farmer is because if you see it uses the dictionary to change the L to R for the farmer. The farmer is state[0]. The only things that changes in the list are the Ls and the Rs according to the swap that uses the dictionary. And because it changes for the farmer it changes side for what it carries to the other side. So if the farmer with the goat change side state[0] and state[2] must change. – Jonathan Baxevanidis Nov 23 '20 at 20:28
  • `state[i] = state[j]` does not change `state[j]`. It only changes `state[i]`, to make it a copy of `state[j]`. Try like this: `def move_farmer(state): state[i] = 'L' if state[i] == 'R' else 'R'; state[j] = state[i]; return state` – Stef Nov 23 '20 at 21:16

1 Answers1

2

The Farmer Problem

I'm going to approach your problem from a different angle. Instead of debugging your problem I've solved it in a step-by-step method that has tests as we go.

The setup and rules

First lets define the situation and the rules

FARMER = 0
WOLF = 1
GOAT = 2
CABBAGE = 3

START_STATE = ['L','L','L','L']

def wolfRule(state):
    return state[FARMER] == state[WOLF] or state[WOLF] != state[GOAT]

assert( wolfRule(['L','L','L','L']) == True)
assert( wolfRule(['R','L','L','L']) == False)
assert( wolfRule(['R','L','R','L']) == True)

def goatRule(state):
    return state[FARMER] == state[GOAT] or state[GOAT] != state[CABBAGE]

assert( goatRule(['L','L','L','L']) == True)
assert( goatRule(['R','L','L','L']) == False)
assert( goatRule(['R','L','L','R']) == True)

def validRule(state):
    return wolfRule(state) and goatRule(state)

def winRule(state):
    return state == ['R','R','R','R']

assert( winRule(['L','L','L','L']) == False)
assert( winRule(['R','L','L','L']) == False)
assert( winRule(['R','R','R','R']) == True)

We have defined each rule, added some assert statements so we know they work, and when we run the above code we can be sure it all is good-to-go.

Generating moves

Part of the recursive search is to generate the next valid move. We will do this in two parts. The first just generates all possible moves, and the second part will filter out only the valid moves

def generateMoves(state):
    # The farmer moves to the other side and can bring 0 or 1 other things so long as it is on the same starting side
    for other in [FARMER, WOLF, GOAT, CABBAGE]:
        if state[FARMER] == state[other]:
            move = copy(state)
            move[FARMER] = 'L' if state[FARMER] == 'R' else 'R'
            move[other] = 'L' if state[other] == 'R' else 'R'
            yield move

assert( list(generateMoves(START_STATE)) == [['R','L','L','L'],['R','R','L','L'],['R','L','R','L'],['R','L','L','R']]  )

Again, we add a test to make sure it is doing what we expect when we generate some moves

def validMoves(state_list):
    return [ state for state in state_list if validRule(state)]

assert( list(validMoves(generateMoves(START_STATE))) == [['R','L','R','L']]  )

Indeed the only first valid move is for the farmer to move the goat!

Depth First Search

Now we do a depth first search, using a previous_states list to keep track of where we have been. This function only returns a winning answer.

def depthFirstSearch(state, previous_states):
    previous_states.append(state)
    if winRule(state):
        return previous_states
    for move in validMoves(generateMoves(state)):
        if move not in previous_states:
            result = depthFirstSearch(move, previous_states)
            if result is not None:
                return result
            previous_states.pop()
    return None

again, at least one test to make sure it is giving expected results

assert( depthFirstSearch(['L','R','L','R'],[]) == [['L','R','L','R'],['R','R','R','R']]  )

Final output

We run it

depthFirstSearch(START_STATE,[])

and get the solution!

[['L', 'L', 'L', 'L'],
 ['R', 'L', 'R', 'L'],
 ['L', 'L', 'R', 'L'],
 ['R', 'R', 'R', 'L'],
 ['L', 'R', 'L', 'L'],
 ['R', 'R', 'L', 'R'],
 ['L', 'R', 'L', 'R'],
 ['R', 'R', 'R', 'R']]
Anton Codes
  • 3,663
  • 1
  • 19
  • 28
  • 1
    Approaching this problem by defining the rules as defs and writing asserts as I went made debugging a breeze. The recursion worked the first time. – Anton Codes Nov 24 '20 at 19:06