-
Notifications
You must be signed in to change notification settings - Fork 1k
/
8_Puzzle_Problem_using_BFS.py
227 lines (199 loc) · 7.58 KB
/
8_Puzzle_Problem_using_BFS.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
'''
8 PUZZLE PROBLEM SOLVING USING BREADTH FIRST SEARCH
An instance of the n-puzzle game consists of a board holding n^{2}-1
distinct movable tiles, plus an empty space. The tiles are numbers from
the set 1,..,n^{2}-1. For any such board, the empty space may be legally
swapped with any tile horizontally or vertically adjacent to it. In this
assignment, the blank space is going to be represented with the number 0.
Given an initial state of the board, the combinatorial search problem is
to find a sequence of moves that transitions this state to the goal state;
that is, the configuration with all tiles arranged in ascending order
0,1,..,n^{2}-1.
So, this is the goal state that we want to reach:
[1, 2, 3]
[8, 0, 4]
[7, 6, 5]
The search space is the set of all possible states reachable from the
initial state. The blank space may be swapped with a component in one of
the four directions {‘Up’, ‘Down’, ‘Left’, ‘Right’}, one move at a time.
Algorithm Review:
The search begins by visiting the root node of the search tree, given by
the initial state. Among other book-keeping details, three major things
happen in sequence in order to visit a node:
-First, we remove a node from the frontier set.
-Second, we check the state against the goal state to determine if a
solution has been found.
-Finally, if the result of the check is negative, we then expand the node.
To expand a given node, we generate successor nodes adjacent to the current
node, and add them to the frontier set. Note that if these successor nodes
are already in the frontier, or have already been visited, then they should
not be added to the frontier again.
'''
# importing the necessary libraries
from time import time
from queue import Queue
# creating a class Puzzle
class Puzzle:
# setting the goal state of 8-puzzle
goal_state=[1,2,3,8,0,4,7,6,5]
num_of_instances=0
# constructor to initialize the class members
def __init__(self,state,parent,action):
self.parent=parent
self.state=state
self.action=action
# incrementing the number of instance by 1
Puzzle.num_of_instances+= 1
# function used to display a state of 8-puzzle
def __str__(self):
return str(self.state[0:3])+'\n'+str(self.state[3:6])+'\n'+str(self.state[6:9])
# method to compare the current state with the goal state
def goal_test(self):
# including a condition to compare the current state with the goal state
if Puzzle.goal_state == self.state:
return True
else:
return False
# static method to find the legal action based on the current board position
@staticmethod
def find_legal_actions(i,j):
legal_action = ['U', 'D', 'L', 'R']
if i == 0:
# if row is 0 in board then up is disabled
legal_action.remove('U')
elif i == 2:
# if row is 2 in board then down is disabled
legal_action.remove('D')
if j == 0:
# if column is 0 in board then left is disabled
legal_action.remove('L')
elif j == 2:
# if column is 2 in board then right is disabled
legal_action.remove('R')
return legal_action
# method to generate the child of the current state of the board
def generate_child(self):
# creating an empty list
children=[]
x = self.state.index(0)
i = int(x / 3)
j = int(x % 3)
# calling the method to find the legal actions based on i and j values
legal_actions = self.find_legal_actions(i, j)
# iterating over all legal actions
for action in legal_actions:
new_state = self.state.copy()
# if the legal action is UP
if action is 'U':
# swapping between current index of 0 with its up element on the board
new_state[x], new_state[x-3] = new_state[x-3], new_state[x]
elif action is 'D':
# swapping between current index of 0 with its down element on the board
new_state[x], new_state[x+3] = new_state[x+3], new_state[x]
elif action is 'L':
# swapping between the current index of 0 with its left element on the board
new_state[x], new_state[x-1] = new_state[x-1], new_state[x]
elif action is 'R':
# swapping between the current index of 0 with its right element on the board
new_state[x], new_state[x+1] = new_state[x+1], new_state[x]
children.append(Puzzle(new_state,self,action))
# returning the children
return children
# method to find the solution
def find_solution(self):
solution = []
all_states = []
solution.append(self.action)
all_states.append(self)
path = self
while path.parent != None:
path = path.parent
solution.append(path.action)
all_states.append(path)
solution = solution[:-1]
solution.reverse()
all_states.reverse()
print("\nAll states: ")
for i in all_states:
print(i, "\n")
return solution
# method for breadth first search
# passing the initial_state as parameter to the breadth_first_search method
def breadth_first_search(initial_state):
start_node = Puzzle(initial_state, None, None)
print("Initial state:")
print(start_node)
if start_node.goal_test():
return start_node.find_solution()
q = Queue()
# putting start_node into the Queue
q.put(start_node)
# creating an empty list of explored nodes
explored=[]
# iterating the queue until empty, using the empty() method of Queue
while not(q.empty()):
# getting the current node of a queue, using the get() method of Queue
node=q.get()
# append the state of node in the explored list as node.state
explored.append(node.state)
# calling the generate_child method to generate the child nodes of current node
children = node.generate_child()
# iterating over each child node in children
for child in children:
if child.state not in explored:
if child.goal_test():
return child.find_solution()
q.put(child)
return
# start executing the 8-puzzle with setting up the initial state
# here we have considered 3 initial state intitalized using state variable
state= [1, 3, 4,
8, 6, 2,
7, 0, 5]
# initializing the num_of_instances to zero
Puzzle.num_of_instances = 0
# setting t0 to current time
t0 = time()
bfs = breadth_first_search(state)
# getting the time t1 after executing the breadth_first_search method
t1 = time() - t0
print('BFS:', bfs)
print('space:',Puzzle.num_of_instances)
print('time:',t1)
print()
print('------------------------------------------')
'''
Sample working:
Initial state:
[1, 3, 4]
[8, 6, 2]
[7, 0, 5]
All states:
[1, 3, 4]
[8, 6, 2]
[7, 0, 5]
[1, 3, 4]
[8, 0, 2]
[7, 6, 5]
[1, 3, 4]
[8, 2, 0]
[7, 6, 5]
[1, 3, 0]
[8, 2, 4]
[7, 6, 5]
[1, 0, 3]
[8, 2, 4]
[7, 6, 5]
[1, 2, 3]
[8, 0, 4]
[7, 6, 5]
BFS: ['U', 'R', 'U', 'L', 'D']
space: 66
time: 0.0009894371032714844
------------------------------------------
Complexities:
Time Complexity -> O(b^d)
Space Complexity -> O(b*d)
b -> branch factor
d -> depth factor
'''