Skip to content

Commit 11cc2cc

Browse files
ad71norvig
authored andcommitted
Refactored EightPuzzle class (#807)
* Refactor EightPuzzle class * return instead of print * Added tests for EightPuzzle * Review fixes * Review fixes * Fixed tests * Update inverted commas in docstrings
1 parent fa7440f commit 11cc2cc

File tree

2 files changed

+108
-76
lines changed

2 files changed

+108
-76
lines changed

search.py

Lines changed: 49 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -411,102 +411,75 @@ def astar_search(problem, h=None):
411411

412412
class EightPuzzle(Problem):
413413

414-
"""The problem of sliding tiles numbered from 1 to 8 on a 3x3 board,
414+
""" The problem of sliding tiles numbered from 1 to 8 on a 3x3 board,
415415
where one of the squares is a blank. A state is represented as a 3x3 list,
416-
where element at index i,j represents the tile number (0 if it's an empty square)."""
416+
where element at index i,j represents the tile number (0 if it's an empty square) """
417417

418-
def __init__(self, initial, goal=None):
419-
if goal:
420-
self.goal = goal
421-
else:
422-
self.goal = [ [0,1,2],
423-
[3,4,5],
424-
[6,7,8] ]
418+
def __init__(self, initial, goal=(1, 2, 3, 4, 5, 6, 7, 8, 0)):
419+
""" Define goal state and initialize a problem """
420+
421+
self.goal = goal
425422
Problem.__init__(self, initial, goal)
426423

427424
def find_blank_square(self, state):
428425
"""Return the index of the blank square in a given state"""
429-
for row in len(state):
430-
for column in len(row):
431-
if state[row][column] == 0:
432-
index_blank_square = (row, column)
433-
return index_blank_square
426+
427+
return state.index(0)
434428

435429
def actions(self, state):
436-
"""Return the actions that can be executed in the given state.
430+
""" Return the actions that can be executed in the given state.
437431
The result would be a list, since there are only four possible actions
438-
in any given state of the environment."""
439-
440-
possible_actions = list()
432+
in any given state of the environment """
433+
434+
possible_actions = ['UP', 'DOWN', 'LEFT', 'RIGHT']
441435
index_blank_square = self.find_blank_square(state)
442436

443-
if index_blank_square(0) == 0:
444-
possible_actions += ['DOWN']
445-
elif index_blank_square(0) == 1:
446-
possible_actions += ['UP', 'DOWN']
447-
elif index_blank_square(0) == 2:
448-
possible_actions += ['UP']
449-
450-
if index_blank_square(1) == 0:
451-
possible_actions += ['RIGHT']
452-
elif index_blank_square(1) == 1:
453-
possible_actions += ['LEFT', 'RIGHT']
454-
elif index_blank_square(1) == 2:
455-
possible_actions += ['LEFT']
437+
if index_blank_square % 3 == 0:
438+
possible_actions.remove('LEFT')
439+
if index_blank_square < 3:
440+
possible_actions.remove('UP')
441+
if index_blank_square % 3 == 2:
442+
possible_actions.remove('RIGHT')
443+
if index_blank_square > 5:
444+
possible_actions.remove('DOWN')
456445

457446
return possible_actions
458447

459448
def result(self, state, action):
460-
"""Given state and action, return a new state that is the result of the action.
461-
Action is assumed to be a valid action in the state."""
462-
463-
blank_square = self.find_blank_square(state)
464-
new_state = [row[:] for row in state]
465-
466-
if action=='UP':
467-
new_state[blank_square(0)][blank_square(1)] = new_state[blank_square(0)-1][blank_square(1)]
468-
new_state[blank_square(0)-1][blank_square(1)] = 0
469-
elif action=='LEFT':
470-
new_state[blank_square(0)][blank_square(1)] = new_state[blank_square(0)][blank_square(1)-1]
471-
new_state[blank_square(0)][blank_square(1)-1] = 0
472-
elif action=='DOWN':
473-
new_state[blank_square(0)][blank_square(1)] = new_state[blank_square(0)+1][blank_square(1)]
474-
new_state[blank_square(0)+1][blank_square(1)] = 0
475-
elif action=='RIGHT':
476-
new_state[blank_square(0)][blank_square(1)] = new_state[blank_square(0)][blank_square(1)+1]
477-
new_state[blank_square(0)][blank_square(1)+1] = 0
478-
else:
479-
print("Invalid Action!")
480-
return new_state
449+
""" Given state and action, return a new state that is the result of the action.
450+
Action is assumed to be a valid action in the state """
451+
452+
# blank is the index of the blank square
453+
blank = self.find_blank_square(state)
454+
new_state = list(state)
455+
456+
delta = {'UP':-3, 'DOWN':3, 'LEFT':-1, 'RIGHT':1}
457+
neighbor = blank + delta[action]
458+
new_state[blank], new_state[neighbor] = new_state[neighbor], new_state[blank]
459+
460+
return tuple(new_state)
481461

482462
def goal_test(self, state):
483-
"""Given a state, return True if state is a goal state or False, otherwise"""
484-
for row in len(state):
485-
for column in len(row):
486-
if state[row][col] != self.goal[row][column]:
487-
return False
488-
return True
489-
490-
def checkSolvability(self, state):
463+
""" Given a state, return True if state is a goal state or False, otherwise """
464+
465+
return state == self.goal
466+
467+
def check_solvability(self, state):
468+
""" Checks if the given state is solvable """
469+
491470
inversion = 0
492471
for i in range(len(state)):
493-
for j in range(i, len(state)):
494-
if (state[i] > state[j] and state[j] != 0):
495-
inversion += 1
496-
check = True
497-
if inversion%2 != 0:
498-
check = False
499-
print(check)
472+
for j in range(i, len(state)):
473+
if (state[i] > state[j] and state[j] != 0):
474+
inversion += 1
475+
476+
return (inversion % 2 == 0)
500477

501-
def h(self, state):
502-
"""Return the heuristic value for a given state. Heuristic function used is
503-
h(n) = number of misplaced tiles."""
504-
num_misplaced_tiles = 0
505-
for row in len(state):
506-
for column in len(row):
507-
if state[row][col] != self.goal[row][column]:
508-
num_misplaced_tiles += 1
509-
return num_misplaced_tiles
478+
def h(self, node):
479+
""" Return the heuristic value for a given state. Default heuristic function used is
480+
h(n) = number of misplaced tiles """
481+
482+
return sum(s != g for (s, g) in zip(node.state, self.goal))
510483

511484
# ______________________________________________________________________________
512485
# Other search algorithms

tests/test_search.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
romania_problem = GraphProblem('Arad', 'Bucharest', romania_map)
66
vacumm_world = GraphProblemStochastic('State_1', ['State_7', 'State_8'], vacumm_world)
77
LRTA_problem = OnlineSearchProblem('State_3', 'State_5', one_dim_state_space)
8+
eight_puzzle = EightPuzzle((1, 2, 3, 4, 5, 7, 8, 6, 0))
9+
eight_puzzle2 = EightPuzzle((1, 0, 6, 8, 7, 5, 4, 2), (0, 1, 2, 3, 4, 5, 6, 7, 8))
810

911
def test_find_min_edge():
1012
assert romania_problem.find_min_edge() == 70
@@ -64,6 +66,63 @@ def test_bidirectional_search():
6466

6567
def test_astar_search():
6668
assert astar_search(romania_problem).solution() == ['Sibiu', 'Rimnicu', 'Pitesti', 'Bucharest']
69+
assert astar_search(eight_puzzle).solution() == ['LEFT', 'LEFT', 'UP', 'RIGHT', 'RIGHT', 'DOWN', 'LEFT', 'UP', 'LEFT', 'DOWN', 'RIGHT', 'RIGHT']
70+
assert astar_search(EightPuzzle((1, 2, 3, 4, 5, 6, 0, 7, 8))).solution() == ['RIGHT', 'RIGHT']
71+
72+
73+
def test_find_blank_square():
74+
assert eight_puzzle.find_blank_square((0, 1, 2, 3, 4, 5, 6, 7, 8)) == 0
75+
assert eight_puzzle.find_blank_square((6, 3, 5, 1, 8, 4, 2, 0, 7)) == 7
76+
assert eight_puzzle.find_blank_square((3, 4, 1, 7, 6, 0, 2, 8, 5)) == 5
77+
assert eight_puzzle.find_blank_square((1, 8, 4, 7, 2, 6, 3, 0, 5)) == 7
78+
assert eight_puzzle.find_blank_square((4, 8, 1, 6, 0, 2, 3, 5, 7)) == 4
79+
assert eight_puzzle.find_blank_square((1, 0, 6, 8, 7, 5, 4, 2, 3)) == 1
80+
assert eight_puzzle.find_blank_square((1, 2, 3, 4, 5, 6, 7, 8, 0)) == 8
81+
82+
83+
def test_actions():
84+
assert eight_puzzle.actions((0, 1, 2, 3, 4, 5, 6, 7, 8)) == ['DOWN', 'RIGHT']
85+
assert eight_puzzle.actions((6, 3, 5, 1, 8, 4, 2, 0, 7)) == ['UP', 'LEFT', 'RIGHT']
86+
assert eight_puzzle.actions((3, 4, 1, 7, 6, 0, 2, 8, 5)) == ['UP', 'DOWN', 'LEFT']
87+
assert eight_puzzle.actions((1, 8, 4, 7, 2, 6, 3, 0, 5)) == ['UP', 'LEFT', 'RIGHT']
88+
assert eight_puzzle.actions((4, 8, 1, 6, 0, 2, 3, 5, 7)) == ['UP', 'DOWN', 'LEFT', 'RIGHT']
89+
assert eight_puzzle.actions((1, 0, 6, 8, 7, 5, 4, 2, 3)) == ['DOWN', 'LEFT', 'RIGHT']
90+
assert eight_puzzle.actions((1, 2, 3, 4, 5, 6, 7, 8, 0)) == ['UP', 'LEFT']
91+
92+
93+
def test_result():
94+
assert eight_puzzle.result((0, 1, 2, 3, 4, 5, 6, 7, 8), 'DOWN') == (3, 1, 2, 0, 4, 5, 6, 7, 8)
95+
assert eight_puzzle.result((6, 3, 5, 1, 8, 4, 2, 0, 7), 'LEFT') == (6, 3, 5, 1, 8, 4, 0, 2, 7)
96+
assert eight_puzzle.result((3, 4, 1, 7, 6, 0, 2, 8, 5), 'UP') == (3, 4, 0, 7, 6, 1, 2, 8, 5)
97+
assert eight_puzzle.result((1, 8, 4, 7, 2, 6, 3, 0, 5), 'RIGHT') == (1, 8, 4, 7, 2, 6, 3, 5, 0)
98+
assert eight_puzzle.result((4, 8, 1, 6, 0, 2, 3, 5, 7), 'LEFT') == (4, 8, 1, 0, 6, 2, 3, 5, 7)
99+
assert eight_puzzle.result((1, 0, 6, 8, 7, 5, 4, 2, 3), 'DOWN') == (1, 7, 6, 8, 0, 5, 4, 2, 3)
100+
assert eight_puzzle.result((1, 2, 3, 4, 5, 6, 7, 8, 0), 'UP') == (1, 2, 3, 4, 5, 0, 7, 8, 6)
101+
assert eight_puzzle.result((4, 8, 1, 6, 0, 2, 3, 5, 7), 'RIGHT') == (4, 8, 1, 6, 2, 0, 3, 5, 7)
102+
103+
104+
def test_goal_test():
105+
assert eight_puzzle.goal_test((0, 1, 2, 3, 4, 5, 6, 7, 8)) == False
106+
assert eight_puzzle.goal_test((6, 3, 5, 1, 8, 4, 2, 0, 7)) == False
107+
assert eight_puzzle.goal_test((3, 4, 1, 7, 6, 0, 2, 8, 5)) == False
108+
assert eight_puzzle.goal_test((1, 2, 3, 4, 5, 6, 7, 8, 0)) == True
109+
assert eight_puzzle2.goal_test((4, 8, 1, 6, 0, 2, 3, 5, 7)) == False
110+
assert eight_puzzle2.goal_test((3, 4, 1, 7, 6, 0, 2, 8, 5)) == False
111+
assert eight_puzzle2.goal_test((1, 2, 3, 4, 5, 6, 7, 8, 0)) == False
112+
assert eight_puzzle2.goal_test((0, 1, 2, 3, 4, 5, 6, 7, 8)) == True
113+
114+
115+
def test_check_solvability():
116+
assert eight_puzzle.check_solvability((0, 1, 2, 3, 4, 5, 6, 7, 8)) == True
117+
assert eight_puzzle.check_solvability((6, 3, 5, 1, 8, 4, 2, 0, 7)) == True
118+
assert eight_puzzle.check_solvability((3, 4, 1, 7, 6, 0, 2, 8, 5)) == True
119+
assert eight_puzzle.check_solvability((1, 8, 4, 7, 2, 6, 3, 0, 5)) == True
120+
assert eight_puzzle.check_solvability((4, 8, 1, 6, 0, 2, 3, 5, 7)) == True
121+
assert eight_puzzle.check_solvability((1, 0, 6, 8, 7, 5, 4, 2, 3)) == True
122+
assert eight_puzzle.check_solvability((1, 2, 3, 4, 5, 6, 7, 8, 0)) == True
123+
assert eight_puzzle.check_solvability((1, 2, 3, 4, 5, 6, 8, 7, 0)) == False
124+
assert eight_puzzle.check_solvability((1, 0, 3, 2, 4, 5, 6, 7, 8)) == False
125+
assert eight_puzzle.check_solvability((7, 0, 2, 8, 5, 3, 6, 4, 1)) == False
67126

68127

69128
def test_recursive_best_first_search():

0 commit comments

Comments
 (0)