Skip to content

Commit 11cc2cc

Browse files
ad71norvig
authored andcommitted
Refactored EightPuzzle class (aimacode#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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy