I am working on implementing an iterative deepening depth first search to find solutions for the 8 puzzle problem. I am not interested in finding the actual search paths themselves, but rather just to time how long it takes for the program to run. (I have not yet implemented the timing function).
However, I am having some issues trying to implement the actual search function (scroll down to see). I pasted all the code I have so far, so if you copy and paste this, you can run it as well. That may be the best way to describe the problems I'm having...I'm just not understanding why I'm getting infinite loops during the recursion, e.g. in the test for puzzle 2 (p2), where the first expansion should yield a solution. I thought it may have something to do with not adding a "Return" in front of one of the lines of code (it's commented below). When I add the return, I can pass the test for puzzle 2, but something more complex like puzzle 3 fails, since it appears that the now the code is only expanding the left most branch...
Been at this for hours, and giving up hope. I would really appreciate another set of eyes on this, and if you could point out my error(s). Thank you!
#Classic 8 puzzle game
#Data Structure: [0,1,2,3,4,5,6,7,8], which is the goal state. 0 represents the blank
#We also want to ignore "backward" moves (reversing the previous action)
p1 = [0,1,2,3,4,5,6,7,8]
p2 = [3,1,2,0,4,5,6,7,8]
p3 = [3,1,2,4,5,8,6,0,7]
def z(p): #returns the location of the blank cell, which is represented by 0
return p.index(0)
def left(p):
zeroLoc = z(p)
p[zeroLoc] = p[zeroLoc-1]
p[zeroLoc-1] = 0
return p
def up(p):
zeroLoc = z(p)
p[zeroLoc] = p[zeroLoc-3]
p[zeroLoc-3] = 0
return p
def right(p):
zeroLoc = z(p)
p[zeroLoc] = p[zeroLoc+1]
p[zeroLoc+1] = 0
return p
def down(p):
zeroLoc = z(p)
p[zeroLoc] = p[zeroLoc+3]
p[zeroLoc+3] = 0
return p
def expand1(p): #version 1, which generates all successors at once by copying parent
x = z(p)
#p[:] will make a copy of parent puzzle
s = [] #set s of successors
if x == 0:
s.append(right(p[:]))
s.append(down(p[:]))
elif x == 1:
s.append(left(p[:]))
s.append(right(p[:]))
s.append(down(p[:]))
elif x == 2:
s.append(left(p[:]))
s.append(down(p[:]))
elif x == 3:
s.append(up(p[:]))
s.append(right(p[:]))
s.append(down(p[:]))
elif x == 4:
s.append(left(p[:]))
s.append(up(p[:]))
s.append(right(p[:]))
s.append(down(p[:]))
elif x == 5:
s.append(left(p[:]))
s.append(up(p[:]))
s.append(down(p[:]))
elif x == 6:
s.append(up(p[:]))
s.append(right(p[:]))
elif x == 7:
s.append(left(p[:]))
s.append(up(p[:]))
s.append(right(p[:]))
else: #x == 8
s.append(left(p[:]))
s.append(up(p[:]))
#returns set of all possible successors
return s
goal = [0,1,2,3,4,5,6,7,8]
def DFS(root, goal): #iterative deepening DFS
limit = 0
while True:
result = DLS(root, goal, limit)
if result == goal:
return result
limit = limit + 1
visited = []
def DLS(node, goal, limit): #limited DFS
if limit == 0 and node == goal:
print "hi"
return node
elif limit > 0:
visited.append(node)
children = [x for x in expand1(node) if x not in visited]
print "\n limit =", limit, "---",children #for testing purposes only
for child in children:
DLS(child, goal, limit - 1) #if I add "return" in front of this line, p2 passes the test below, but p3 will fail (only the leftmost branch of the tree is getting expanded...)
else:
return "No Solution"
#Below are tests
print "\ninput: ",p1
print "output: ",DFS(p1, goal)
print "\ninput: ",p2
print "output: ",DLS(p2, goal, 1)
#print "output: ",DFS(p2, goal)
print "\ninput: ",p3
print "output: ",DLS(p3, goal, 2)
#print "output: ",DFS(p2, goal)
The immediate issue you're having with your recursion is that you're not returning anything when you hit your recursive step. However, unconditionally returning the value from the first recursive call won't work either, since the first child isn't guaranteed to be the one that finds the solution. Instead, you need to test to see which (if any) of the recursive searches you're doing on your child states is successful. Here's how I'd change the end of your DLS function:
for child in children:
child_result = DLS(child, goal, limit - 1)
if child_result != "No Solution":
return child_result
# note, "else" removed here, so you can fall through to the return from above
return "No Solution"
A slightly more "pythonic" (and faster) way of doing this would be to use None as the sentinel value rather than the "No Solution" string. Then your test would simply be if child_result: return child_result and you could optionally leave off the return statement for the failed searches (since None is the default return value of a function).
There are some other issues going on with your code that you'll run into once this recursion issue is fixed. For instance, using a global visited variable is problematic, unless you reset it each time you restart another recursive search. But I'll leave those to you!
Use classes for your states! This should make things much easier. To get you started. Don't want to post the whole solution right now, but this makes things much easier.
#example usage
cur = initialPuzzle
for k in range(0,5): # for 5 iterations. this will cycle through, so there is some coding to do
allsucc = cur.succ() # get all successors as puzzle instances
cur = allsucc[0] # expand first
print 'expand ',cur
import copy
class puzzle:
'''
orientation
[0, 1, 2
3, 4, 5
6, 7, 8]
'''
def __init__(self,p):
self.p = p
def z(self):
''' returns the location of the blank cell, which is represented by 0 '''
return self.p.index(0)
def swap(self,a,b):
self.p[a] = self.p[b]
self.p[b] = 0
def left(self):
self.swap(self.z(),self.z()+1) #FIXME: raise exception if not allowed
def up(self):
self.swap(self.z(),self.z()+3)
def right(self):
self.swap(self.z(),self.z()-1)
def down(self):
self.swap(self.z(),self.z()-3)
def __str__(self):
return str(self.p)
def copyApply(self,func):
cpy = self.copy()
func(cpy)
return cpy
def makeCopies(self,s):
''' some bookkeeping '''
flist = list()
if 'U' in s:
flist.append(self.copyApply(puzzle.up))
if 'L' in s:
flist.append(self.copyApply(puzzle.left))
if 'R' in s:
flist.append(self.copyApply(puzzle.right))
if 'D' in s:
flist.append(self.copyApply(puzzle.down))
return flist
def succ(self):
# return all successor states for this puzzle state
# short hand of allowed success states
m = ['UL','ULR','UR','UDR','ULRD','UDL','DL','LRD','DR']
ss= self.makeCopies(m[self.z()]) # map them to copies of puzzles
return ss
def copy(self):
return copy.deepcopy(self)
# some initial state
p1 = [0,1,2,3,4,5,6,7,8]
print '*'*20
pz = puzzle(p1)
print pz
a,b = pz.succ()
print a,b
Related
I'm trying to solve the leetcode question https://leetcode.com/problems/open-the-lock/ with BFS and came up with the approach below.
def openLock(self, deadends: List[str], target: str): -> int
def getNeighbors(node) ->List[str]:
neighbors = []
dirs = [-1, 1]
for i in range(len(node)):
for direction in dirs:
x = (int(node[i]) + direction) % 10
neighbors.append(node[:i] + str(x) + node[i+1:])
return neighbors
start = '0000'
if start in deadends:
return -1
queue = deque()
queue.append((start, 0))
turns = 0
visited = set()
deadends = set(deadends)
while queue:
state, turns = queue.popleft()
visited.add(state)
if state == target:
return turns
for nb in getNeighbors(state):
if nb not in visited and nb not in deadends:
queue.append((nb, turns+1))
return -1
However this code runs into a Time Limit Exceeded exception and it only passes 2/48 of the test cases. For ex. with a testcase where the deadends are ["8887","8889","8878","8898","8788","8988","7888","9888"] and the target is "8888" (the start is always "0000") my solution TLE. I notice that if I essentially change one line and add neighboring nodes to the visited set in my inner loop, my solution passes that test case and all other test cases.
for nb in getNeighbors(state):
if nb not in visited and nb not in deadends:
visited.add(nb)
queue.append((nb, turns+1))
My while loop then becomes
while queue:
state, turns = queue.popleft()
if state == target:
return turns
for nb in getNeighbors(state):
if nb not in visited and nb not in deadends:
visited.add(nb)
queue.append((nb, turns+1))
Can someone explain why the first approach runs into TLE while the second doesn't?
Yes. Absolutely. You need to add the item to visited when it gets added to the queue the first time, not when it gets removed from the queue. Otherwise your queue is going to grow exponentially.
Let's look at say 1111. Your code is going to add 1000, 0100, 0010, 0001 (and others) to the queue. Then your code is going to add each of 1100, 1010, 1001, 0110, 0101, 0011, and others twice because they are neighbors of two elements in the queue, and they haven't been visited yet. And then you add each of 1110, 1101, 1011, 0111 four times. If instead you're searching for 5555, you'll have many many duplicates on your queue.
Change the name of your visited variable to seen. Notice that once an item has been put on the queue, it never needs to be put on the queue again.
Seriously, try searching for 5555 with no dead ends. When you find it, look at the queue and see how many elements are on the queue, versus how many distinct elements are on the queue.
You are adding next states to try (neighboring states) to the queue, but since you only check if new states have either already been visited, or are dead ends, you end up adding states you haven't tried yet to the queue many times. (i.e. you have no way of telling if a new state was already queued)
Basically, you're getting a bit too fancy here. A simpler implementation of the same idea:
def openLock(dead_ends: list[str], target: str, start: str = '0000') -> int:
# you could create locks with hexadecimal, alphabetic etc. disks
symbols = list(map(str, [*range(10)]))
if start in dead_ends or target in dead_ends:
return -1
elif start == target:
return 0
assert all(l == len(start) == len(target) for l in map(len, dead_ends)), \
'length of start, target and dead ends must match'
assert all(all(s in symbols for s in code) for code in dead_ends + [start, target]), \
f'all symbols in start, target and dead ends must be in {symbols}'
try_neighbors = [start]
tries = {start: 0}
while try_neighbors:
state = try_neighbors.pop(0)
for i, s in enumerate(state):
for step in (-1, +1):
neighbor = state[:i] + symbols[(symbols.index(s) + step) % len(symbols)] + state[i+1:]
if neighbor in tries:
continue
if neighbor == target:
return tries[state] + 1
elif neighbor not in dead_ends:
tries[neighbor] = tries[state] + 1
try_neighbors.append(neighbor)
return -1
# Provides the answer `-1` because it cannot be solved.
print(openLock(["8887","8889","8878","8898","8788","8988","7888","9888"], "8888"))
# Provides the answer `8` steps
print(openLock(["8887","8889","8878","8898","8788","8988","7888"], "8888"))
Your own solution with the problems fixed:
from collections import deque
def openLock(deadends: list[str], target: str) -> int:
def getNeighbors(node) -> list[str]:
neighbors = []
dirs = [-1, 1]
for i in range(len(node)):
for direction in dirs:
x = (int(node[i]) + direction) % 10
neighbors.append(node[:i] + str(x) + node[i + 1:])
return neighbors
start = '0000'
if start in deadends:
return -1
queue = deque()
queue.append((start, 0))
# keep track of all you've seen
seen = set()
deadends = set(deadends)
while queue:
state, turns = queue.popleft()
seen.add(state)
for nb in getNeighbors(state):
# why wait for it to come up in the queue?
if nb == target:
return turns + 1
if nb not in seen and nb not in deadends:
queue.append((nb, turns + 1))
# add everything you've queued up to seen
seen.add(nb)
return -1
print(openLock(["8887","8889","8878","8898","8788","8988","7888","9888"], "8888"))
this is my code to find the height of a tree of up to 10^5 nodes. May I know why I get the following error?
Warning, long feedback: only the beginning and the end of the feedback message is shown, and the middle was replaced by " ... ". Failed case #18/24: time limit exceeded
Input:
100000
Your output:
stderr:
(Time used: 6.01/3.00, memory used: 24014848/2147483648.)
Is there a way to speed up this algo?
This is the exact problem description:
Problem Description
Task. You are given a description of a rooted tree. Your task is to compute and output its height. Recall
that the height of a (rooted) tree is the maximum depth of a node, or the maximum distance from a
leaf to the root. You are given an arbitrary tree, not necessarily a binary tree.
Input Format. The first line contains the number of nodes π. The second line contains π integer numbers
from β1 to π β 1 β parents of nodes. If the π-th one of them (0 β€ π β€ π β 1) is β1, node π is the root,
otherwise itβs 0-based index of the parent of π-th node. It is guaranteed that there is exactly one root.
It is guaranteed that the input represents a tree.
Constraints. 1 β€ π β€ 105
Output Format. Output the height of the tree.
# python3
import sys, threading
from collections import deque, defaultdict
sys.setrecursionlimit(10**7) # max depth of recursion
threading.stack_size(2**27) # new thread will get stack of such size
class TreeHeight:
def read(self):
self.n = int(sys.stdin.readline())
self.parent = list(map(int, sys.stdin.readline().split()))
def compute_height(self):
height = 0
nodes = [[] for _ in range(self.n)]
for child_index in range(self.n):
if self.parent[child_index] == -1:
# child_index = child value
root = child_index
nodes[0].append(root)
# do not add to index
else:
parent_index = None
counter = -1
updating_child_index = child_index
while parent_index != -1:
parent_index = self.parent[updating_child_index]
updating_child_index = parent_index
counter += 1
nodes[counter].append(child_index)
# nodes[self.parent[child_index]].append(child_index)
nodes2 = list(filter(lambda x: x, nodes))
height = len(nodes2)
return(height)
def main():
tree = TreeHeight()
tree.read()
print(tree.compute_height())
threading.Thread(target=main).start()
First, why are you using threading? Threading isn't good. It is a source of potentially hard to find race conditions and confusing complexity. Plus in Python, thanks to the GIL, you often don't get any performance win.
That said, your algorithm essentially looks like this:
for each node:
travel all the way to the root
record its depth
If the tree is entirely unbalanced and has 100,000 nodes, then for each of 100,000 nodes you have to visit an average of 50,000 other nodes for roughly 5,000,000,000 operations. This takes a while.
What you need to do is stop constantly traversing the tree back to the root to find the depths. Something like this should work.
import sys
class TreeHeight:
def read(self):
self.n = int(sys.stdin.readline())
self.parent = list(map(int, sys.stdin.readline().split()))
def compute_height(self):
height = [None for _ in self.parent]
todo = list(range(self.n))
while 0 < len(todo):
node = todo.pop()
if self.parent[node] == -1:
height[node] = 1
elif height[node] is None:
if height[self.parent[node]] is None:
# We will try again after doing our parent
todo.append(node)
todo.append(self.parent[node])
else:
height[node] = height[self.parent[node]] + 1
return max(height)
if __name__ == "__main__":
tree = TreeHeight()
tree.read()
print(tree.compute_height())
(Note, I switched to a standard indent, and then made that indent 4. See this classic study for evidence that an indent in the 2-4 range is better for comprehension than an indent of 8. And, of course, the pep8 standard for Python specifies 4 spaces.)
Here is the same code showing how to handle accidental loops, and hardcode a specific test case.
import sys
class TreeHeight:
def read(self):
self.n = int(sys.stdin.readline())
self.parent = list(map(int, sys.stdin.readline().split()))
def compute_height(self):
height = [None for _ in self.parent]
todo = list(range(self.n))
in_redo = set()
while 0 < len(todo):
node = todo.pop()
if self.parent[node] == -1:
height[node] = 1
elif height[node] is None:
if height[self.parent[node]] is None:
if node in in_redo:
# This must be a cycle, lie about its height.
height[node] = -100000
else:
in_redo.add(node)
# We will try again after doing our parent
todo.append(node)
todo.append(self.parent[node])
else:
height[node] = height[self.parent[node]] + 1
return max(height)
if __name__ == "__main__":
tree = TreeHeight()
# tree.read()
tree.n = 5
tree.parent = [-1, 0, 4, 0, 3]
print(tree.compute_height())
I'm a newbee to python and computing. This is the problem that I want to implement by using Python:Three couples want to cross a river, from east to west. A boat found there can take only two persons. How to cross the river if a wife can't stay with other men without her husband?
First I created all the possible states.
def genStates():
"""
A function to generate all the possible states.
Input: None
Output: all the possible states
"""
states = []
side = ("E","W")
for a in side:
for b in side:
for c in side:
for d in side:
for e in side:
for f in side:
z = a+b+c+d+e+f
states.append(z)
return states
Then I created a graph to put legal states and their possible moves.
def genGraph(S):
"""
A function to generate a graph that contains all the legal states and their next possible states to move
Input: All the possible states
Output: A graph of all the legal states and their next possible states to move
"""
G = []
graph = {}
for i in range(len(S)):
if isLegal(S[i]) == True:
G.append(S[i])
for i in range(len(G)):
result1 = nextStates(G[i], G)
graph.update({G[i]: result1[1:]}) # add possible states to each legal states, put it in graph
return graph
I created two functions that are used in genGraph.
def isLegal(S):
"""
A function to check if a state is legal or not.
Input: None
Output: Return True if a state is legal, else return False.
"""
if S[0] != S[1]:
if S[1] == S[2] or S[1] == S[4]:
return False
elif S[2] != S[3]:
if S[3] == S[0] or S[3] == S[4]:
return False
elif S[4] != S[5]:
if S[5] == S[0] or S[5] == S[2]:
return False
return True
and
def nextStates(startnode, allstates):
"""
A function to return a set of states that a state can move to.
Input: A state and all the possible states
Output: a set of states that a state can move to
"""
possible = [startnode]
count1 = startnode.count("E")
count2 = startnode.count("W")
for i in range(1, len(allstates)):
if allstates[i].count("E") - count1 <= 2 and allstates[i].count("E") - count1 >= -2 and allstates[i].count(
"W") - count2 <= 2 and allstates[i].count("W") - count2 >= -2:
count = 0
for j in range(0,len(startnode)):
if allstates[i][j] == startnode[j]:
count += 1
if count >= 4: # Unlike MCGW problem, single unit's journey is unnecessary
possible.append(allstates[i])
return possible
and finally, I used a function from a python website to get the shortest path to implement this problem.
def genShortestPath(graph, start, end, path=[]):
"""
A function to find the shortest path in a graph, from the start to the destination.
Input: A graph of all the legal states and their states next possible states to move, the starting point and the destination
Output: the shortest path of the graph
"""
path = path + [start]
if start == end:
return path
if not (start in graph):
return None
shortestPath = None
for node in graph[start]:
if node not in path:
newpath = genShortestPath(graph, node, end, path)
if newpath:
if not shortestPath or len(newpath) < len(shortestPath):
shortestPath = newpath
return shortestPath
And finally, when I implement this problem, it can't find the shortest solution for some reasons. I printed out the graph, and next states functions to check if there's any problems for these, but it was working pretty well. What is the problem here? This is the implementing code:
def Solver():
S = genStates()
G = genGraph(S)
s = "EEEEEE" # source node
d = "WWWWWW" # destination node
result = genShortestPath(G, s, d)
print(result)
I am trying to implement dijkstra's algorithm (on an undirected graph) to find the shortest path and my code is this.
Note: I am not using heap/priority queue or anything but an adjacency list, a dictionary to store weights and a bool list to avoid cycling in the loops/recursion forever. Also, the algorithm works for most test cases but fails for this particular one here: https://ideone.com/iBAT0q
Important : Graph can have multiple edges from v1 to v2 (or vice versa), you have to use the minimum weight.
import sys
sys.setrecursionlimit(10000)
def findMin(n):
for i in x[n]:
cost[n] = min(cost[n],cost[i]+w[(n,i)])
def dik(s):
for i in x[s]:
if done[i]:
findMin(i)
done[i] = False
dik(i)
return
q = int(input())
for _ in range(q):
n,e = map(int,input().split())
x = [[] for _ in range(n)]
done = [True]*n
w = {}
cost = [1000000000000000000]*n
for k in range(e):
i,j,c = map(int,input().split())
x[i-1].append(j-1)
x[j-1].append(i-1)
try: #Avoiding multiple edges
w[(i-1,j-1)] = min(c,w[(i-1,j-1)])
w[(j-1,i-1)] = w[(i-1,j-1)]
except:
try:
w[(i-1,j-1)] = min(c,w[(j-1,i-1)])
w[(j-1,i-1)] = w[(i-1,j-1)]
except:
w[(j-1,i-1)] = c
w[(i-1,j-1)] = c
src = int(input())-1
#for i in sorted(w.keys()):
# print(i,w[i])
done[src] = False
cost[src] = 0
dik(src) #First iteration assigns possible minimum to all nodes
done = [True]*n
dik(src) #Second iteration to ensure they are minimum
for val in cost:
if val == 1000000000000000000:
print(-1,end=' ')
continue
if val!=0:
print(val,end=' ')
print()
The optimum isn't always found in the second pass. If you add a third pass to your example, you get closer to the expected result and after the fourth iteration, you're there.
You could iterate until no more changes are made to the cost array:
done[src] = False
cost[src] = 0
dik(src)
while True:
ocost = list(cost) # copy for comparison
done = [True]*n
dik(src)
if cost == ocost:
break
I can't seem to figure out why I keep getting this error. The command line error traceback looks like this:
The point of the following code is to essentially give artificial intelligence to Pacman; keep him away from unscared ghosts, while eating all the food and capsules on a map. Most of code for this was given by the prof for an AI class, which can be found here.
The evaluationFunction method returns a very simple heuristic value that takes into consideration the distances to ghosts, food and capsules. The getAction function is found within my ExpectimaxAgent class (passed arg is MulitAgentSearchAgent), and it gathers all relevant information together, iterates through all possible actions and passes the info along to expectimax. The expectimax function is supposed to calculate a heuristic value which when returned to getAction is compared to the other action-heuristic values, and the one with the highest heuristic is chosen as the best action.
This should be all the relevant code for this error (if not I'll add more, also a quick apology for the noob mistakes in this question, I'm first time poster):
class ReflexAgent(Agent):
def getAction(self, gameState):
# Collect legal moves and successor states
legalMoves = gameState.getLegalActions()
# Choose one of the best actions
scores = [self.evaluationFunction(gameState, action) for action in legalMoves]
bestScore = max(scores)
bestIndices = [index for index in range(len(scores)) if scores[index] == bestScore]
chosenIndex = random.choice(bestIndices) # Pick randomly among the best
return legalMoves[chosenIndex]
def evaluationFunction(self, currentGameState, action):
successorGameState = currentGameState.generatePacmanSuccessor(action)
oldPos = currentGameState.getPacmanPosition()
newPos = successorGameState.getPacmanPosition()
newFood = successorGameState.getFood()
newGhostStates = successorGameState.getGhostStates()
# heuristic baseline
heuristic = 0.0
# ghost heuristic
for ghost in newGhostStates:
ghostDist = manhattanDistance(ghost.getPosition(), newPos)
if ghostDist <= 1:
if ghost.scaredTimer != 0:
heuristic += 2000
else:
heuristic -= 200
# capsule heuristic
for capsule in currentGameState.getCapsules():
capsuleDist = manhattanDistance(capsule, newPos)
if capsuleDist == 0:
heuristic += 100
else:
heuristic += 10.0/capsuleDist
# food heuristic
for x in xrange(newFood.width):
for y in xrange(newFood.height):
if (newFood[x][y]):
foodDist = manhattanDistance(newPos, (x,y))
if foodDist == 0:
heuristic += 100
else:
heuristic += 1.0/(foodDist ** 2)
if currentGameState.getNumFood() > successorGameState.getNumFood():
heuristic += 100
if action == Directions.STOP:
heuristic -= 5
return heuristic
def scoreEvaluationFunction(currentGameState):
return currentGameState.getScore()
class MultiAgentSearchAgent(Agent):
def __init__(self, evalFn = 'scoreEvaluationFunction', depth = '2'):
self.index = 0 # Pacman is always agent index 0
self.evaluationFunction = util.lookup(evalFn, globals())
self.depth = int(depth)
class ExpectimaxAgent(MultiAgentSearchAgent):
def getAction(self, gameState):
# Set v to smallest float value (-infinity)
v = float("-inf")
bestAction = []
# Pacman is agent == 0
agent = 0
# All legal actions which Pacman can make from his current location
actions = gameState.getLegalActions(agent)
# All successors determined from all the legal actions
successors = [(action, gameState.generateSuccessor(agent, action)) for action in actions]
# Iterate through all successors
for successor in successors:
# Expectimax function call (actor = 1, agentList = total number of agents, state = successor[1], depth = self.depth, evalFunct = self.evaluationFunction)
temp = expectimax(1, range(gameState.getNumAgents()), successor[1], self.depth, self.evaluationFunction)
# temp is greater than -infinity (or previously set value)
if temp > v:
# Set v to the new value of temp
v = temp
# Make the best action equal to successor[0]
bestAction = successor[0]
return bestAction
def expectimax(agent, agentList, state, depth, evalFunct):
# Check if won, lost or depth is less than/equal to 0
if depth <= 0 or state.isWin() == True or state.isLose() == True:
# return evalFunct
return evalFunct
# Check to see if agent is Pacman
if agent == 0:
# Set v to smallest float value (-infinity)
v = float("-inf")
# Otherwise, agent is ghost
else:
# Set v to 0
v = 0
# All possible legal actions for Pacman/Ghost(s)
actions = state.getLegalActions(agent)
# All successors determined from all the legal actions for the passed actor (either Pacman or Ghost(s))
successors = [state.generateSuccessor(agent, action) for action in actions]
# Find the inverse of the length of successors
p = 1.0/len(successors)
# Iterate through the length of successors
for j in range(len(successors)):
# Temp var to store the current successor at location j
successor = successors[j]
# Check if agent is Pacman
if agent == 0:
# Set v to the max of its previous value or recursive call to expectimax
v = max(v, expectimax(agentList[agent + 1], agentList, successor, depth, evalFunct))
# Check if agent is equal to ghost 2
elif agent == agentList[-1]:
# Increment v by the recursive call to p times expectimax (with agent=agentlist[0], agentList, state=successor, depth-=1, evalFunct)
v += expectimax(agentList[0], agentList, successor, depth - 1, evalFunct) * p
# Otherwise
else:
# Increment v by p times the recursive call to expectimax (with agent=agentList[agent+1], agentList, state=successor, depth, evalFunct)
v += expectimax(agentList[agent + 1], agentList, successor, depth, evalFunct) * p
return v
I've looked at a few other posts on here, and around the inter-webs, but have found nothing that seems to be similar to my issue. I've tried to pass the value to a temp variable, even tried to move the multiplication before the function call, but those changes have given me the exact same error, on the exact same line.
The error was the first return inside of the expectimax function. Instead of:
def expectimax(agent, agentList, state, depth, evalFunct):
# Check if won, lost or depth is less than/equal to 0
if depth <= 0 or state.isWin() == True or state.isLose() == True:
# return evalFunct
return evalFunct <--- cause of the error
It should have been:
return evalFunct(state)
This is because (as was pointed out) evalFunct only points to the evaluation function the user chose (from the command line arguments).