Search code examples
pythonpython-3.xartificial-intelligencebreadth-first-searchwater-jug-problem

Functions change values in list even though list is not passed to functions


I'm trying to implement a I.A. breadth-first-search-like algorithm to solve the Water Jugs problem but I encountered this problem:

Every time I add a new element to the array, it changes all elements inside the array to be like it.

In other words...

The "frontier" array is changing all elements inside it between each "jug" functions calls.

Could someone share some insight about this code?

(I was more worried about implementing the logic, so it isn't optimized)

The code:

J1SIZE = 3
J2SIZE = 4

frontier = []
explored = []


def IsEmpty(array):
    result = False
    if len(array) == 0:
        result = True
    return result


#Fill up a jug
def fillJug(tempNode, jug):
    copyNode = tempNode
    copyNode = copyNode
    if jug == 1:
        copyNode[0] = J1SIZE
    elif jug == 2:
        copyNode[1] = J2SIZE
    print(copyNode)
    return copyNode

#Empty a jug
def emptyJug(tempNode, jug):
    copyNode = tempNode
    if jug == 1:
        copyNode[0] = 0
    elif jug == 2:
        copyNode[1] = 0
    print(copyNode)
    return copyNode

#Pass the content of a jug to another
def passJug(tempNode, jug):
    copyNode = tempNode
    if jug == 1:
        copyNode[1] = copyNode.j1
        copyNode[0] = 0
        if copyNode[1] > J2SIZE:
            copyNode[1] = J2SIZE
    elif jug == 2:
        copyNode[0] = copyNode[1]
        copyNode[1] = 0
        if copyNode[0] > J1SIZE:
            copyNode[0] = J1SIZE
    print(copyNode)
    return copyNode

#Check if solution was found
def goalTest(goalNode,currentNode):
    result = False
    if goalNode == currentNode:
        result = True
    return result

def BFS(start,end,frontier,explored):

    start_node = start
    frontier = [start_node] + frontier

    if goalTest(end,(frontier[0])):
        print('Solution:', frontier[0])
        exit()

    #Loop start
    while IsEmpty(frontier) == False:

        tempNode = frontier.pop(0)

        #Generate nodes (states)
        if tempNode[0] < J1SIZE:
            newNode = fillJug(tempNode, 1)
            if newNode not in explored:
                frontier = [newNode] + frontier
                print('Frontier1: ', frontier)

        if tempNode[1] < J2SIZE:
            newNode = fillJug(tempNode, 2)
            if newNode not in explored:
                frontier = [newNode] + frontier
                print('Frontier2: ', frontier)

        if tempNode[0] == J1SIZE:
            newNode = emptyJug(tempNode, 1)
            if newNode not in explored:
                frontier = [newNode] + frontier
                print('Frontier3: ', frontier)

        if tempNode[1] == J2SIZE:
            newNode = emptyJug(tempNode, 2)
            if newNode not in explored:
                frontier = [newNode] + frontier
                print('Frontier4: ', frontier)

        if (tempNode[0] > 0) and (tempNode[1] < J2SIZE):
            newNode = passJug(tempNode, 1)
            if newNode not in explored:
                frontier = [newNode] + frontier
                print('Frontier5: ', frontier)

        if (tempNode[1] > 0) and (tempNode[0] < J1SIZE):
            new_node = passJug(tempNode, 2)
            if new_node not in explored:
                frontier = [new_node] + frontier
                print('Frontier6: ', frontier)

        print('Frontier: ', frontier)
        print('Explored: ', explored)

        for node in frontier:
            if goalTest(end, node):
                print('Solution:', frontier[0])
                exit()
            if node not in explored:
                explored = [node] + explored

BFS([0,0],[0,2],frontier,explored)

The fixed code:

http://codepaste.net/auun4u


Solution

  • I've made a smaller code example that reproduces your error:

    J1SIZE = 3
    J2SIZE = 4
    
    
    def fillJug(tempNode, jug):
        copyNode = tempNode
        copyNode = copyNode
        if jug == 1:
            copyNode[0] = J1SIZE
        elif jug == 2:
            copyNode[1] = J2SIZE
        return copyNode
    
    
    frontier = [[0, 0]]
    tempNode = frontier.pop(0)
    
    newNode = fillJug(tempNode, 1)
    frontier = [newNode] + frontier  # (1)
    
    newNode = fillJug(tempNode, 2)  # (2)
    frontier = [newNode] + frontier
    

    The problem is that in the line I've marked (1), you are setting newNode, which refers to the same object as tempNode to be the first item in frontier. Then, in the line I've marked (2), you are changing tempNode -- inside fillJug. So the value in frontier changes. Specifically, you are having copyNode refer to the same object as tempNode, and then you are changing the data to which copyNode (and tempNode) refer.

    You should redefine copyNode as

    copyNode = tempNode[:]

    This will cause copyNode and tempNode to refer to different objects in memory (with the same values). (Another option is to set copyNode to copy.copy(tempNode), after importing copy.)

    So the new fillJug would be

    def fillJug(tempNode, jug):
        copyNode = tempNode[:]
        if jug == 1:
            copyNode[0] = J1SIZE
        elif jug == 2:
            copyNode[1] = J2SIZE
        return copyNode
    

    The same fix applies to the other Jug functions.