forked from arjavibahety/3234_group3
-
Notifications
You must be signed in to change notification settings - Fork 0
/
CS3243_P1_LinearConflict.py
270 lines (241 loc) · 10.5 KB
/
CS3243_P1_LinearConflict.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
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
import os
import sys
import heapq
import math
import time
class Puzzle(object):
def __init__(self, init_state, goal_state):
# you may add more attributes if you think is useful
self.init_state = init_state
self.goal_state = goal_state
self.n = len(init_state)
# self.actions = list() - Don't really need this.
def solve(self):
#TODO
# implement your search algorithm here
# Get unmodifiable states
start_time = time.time()
print('Solving...')
unmodifiable_initial_state = tuple(tuple(i) for i in self.init_state)
unmodifiable_goal_state = tuple(tuple(i) for i in self.goal_state)
# Exit if not solvable
if not self.__is_solvable(unmodifiable_initial_state):
return ["UNSOLVABLE"]
start_node = Node(unmodifiable_initial_state, 0, False, False)
frontier = []
heapq.heapify(frontier)
heapq.heappush(frontier, (0, start_node))
visited = {}
goal_found = False
goal_node = False
while len(frontier) > 0 and not goal_found:
current = heapq.heappop(frontier)
if (current[1].state in visited):
continue
else:
visited[current[1].state] = 1
if (current[1].has_state_equals_to(unmodifiable_goal_state)):
goal_found = True
goal_node = current[1]
break
successors = current[1].get_successors()
# Successors are a list of Nodes.
for successor in successors:
if successor.state not in visited:
fn = successor.get_actual_cost() + 1.0001*successor.get_heuristic_cost()
heapq.heappush(frontier, (fn, successor))
path = self.__get_path(unmodifiable_initial_state, goal_node)
print(self.__apply_moves_on_state(unmodifiable_initial_state, path))
print('Execution time: {duration}'.format(duration=(time.time() - start_time)))
return path
# Function to test if algorithm produces the correct goal state.
# Input is a list of moves e.g ["UP", "DOWN", "LEFT"], returns the state
# After executing all the moves on the initial_state.
def __apply_moves_on_state(self, initial_state, moves):
if len(moves) == 0:
return initial_state
else:
zero_row = -1
zero_col = -1
for i in range (0, self.n):
for j in range(0, self.n):
if initial_state[i][j] == 0:
zero_row = i
zero_col = j
break
modifiable_state = [list(k) for k in initial_state]
move = moves[0]
if move == "LEFT":
modifiable_state[zero_row][zero_col] = modifiable_state[zero_row][zero_col + 1]
modifiable_state[zero_row][zero_col + 1] = 0
elif move == "RIGHT":
modifiable_state[zero_row][zero_col] = modifiable_state[zero_row][zero_col - 1]
modifiable_state[zero_row][zero_col - 1] = 0
elif move == "DOWN":
modifiable_state[zero_row][zero_col] = modifiable_state[zero_row - 1][zero_col]
modifiable_state[zero_row - 1][zero_col] = 0
else:
modifiable_state[zero_row][zero_col] = modifiable_state[zero_row + 1][zero_col]
modifiable_state[zero_row + 1][zero_col] = 0
next_state = tuple(tuple(k) for k in modifiable_state)
return self.__apply_moves_on_state(next_state, moves[1::])
# Function to check if the puzzle is solvable
def __is_solvable(self, initial_state):
inversions = 0
one_dimensional_list = []
row_where_zero_is = -1
for i in range(0, self.n):
for j in range (0, self.n):
if not initial_state[i][j] == 0:
one_dimensional_list.append(initial_state[i][j])
else:
row_where_zero_is = i
for i in range(0, len(one_dimensional_list)):
for j in range (i, len(one_dimensional_list)):
if one_dimensional_list[i] > one_dimensional_list[j]:
inversions += 1
# If n is odd, the puzzle is solvable if the number of inversions is even.
# Otherwise, the puzzle is solvable if
# 1) The number of inversions is odd and the row index of 0 is even.
# 2) The number of inversions is even and the row index of 0 is odd.
if len(initial_state) % 2 == 1:
return inversions % 2 == 0
else:
return (inversions % 2 == 0 and row_where_zero_is % 2 == 1) or (inversions % 2 == 1 and row_where_zero_is % 2 == 0)
# you may add more functions if you think is useful
def __get_path(self, init_state, goal_node):
path = []
def recursive_backtrack(node):
if not node.has_state_equals_to(init_state):
recursive_backtrack(node.parent)
path.append(node.direction)
recursive_backtrack(goal_node)
return path
class Node(object):
def __init__(self, state, g, parent, direction):
self.state = state
self.g = g
self.n = len(state)
self.parent = parent
self.direction = direction
def __lt__(self, other):
return self.g < other.g
def has_state_equals_to(self, other_state):
is_equals = True
for i in range (0, self.n):
for j in range (0, self.n):
if self.state[i][j] != other_state[i][j]:
is_equals = False
break
return is_equals
def get_heuristic_cost(self):
# Implement the heuristics. Try Manhatten distance with linear conflict.
return self.__get_manhatten() + 2 * self.__get_linear_conflict()
def __get_manhatten(self):
distance = 0
for i in range (0, self.n):
for j in range(0, self.n):
value = self.state[i][j]
if value != 0:
correct_index = [math.floor((value - 1)/self.n), (value - 1) % self.n]
distance += abs(correct_index[0] - i) + abs(correct_index[1] - j)
else:
distance += (self.n - i - 1) + (self.n - j - 1)
return distance
def __get_linear_conflict(self):
linear_conflict = 0
# Count linear conflicts
for i in range(0, self.n):
for j in range(0, self.n):
value = self.state[i][j]
# If value is in the correct row.
if value != 0 and (value - 1)//self.n == i:
for k in range(j + 1, self.n):
if self.state[i][k] != 0 and (self.state[i][k] - 1)//self.n == i and value > self.state[i][k]:
linear_conflict += 1
#print('conflict at {v1}, {v2}'.format(v1 = value, v2 = self.state[i][k]))
# If value is in the correct col.
if value != 0 and (value - 1)%self.n == j:
for k in range(i + 1, self.n):
if self.state[k][j] != 0 and (self.state[k][j] - 1)%self.n == j and value > self.state[k][j]:
linear_conflict += 1
#print('conflict at {v1}, {v2}'.format(v1 = value, v2 = self.state[k][j]))
'''print(self.state)
print('has linear conflict: {value}'.format(value=linear_conflict))
print('\n')'''
return linear_conflict
def is_opposite_direction(self, direction1, direction2):
opposite_directions = { "LEFT": "RIGHT",
"RIGHT": "LEFT",
"UP": "DOWN",
"DOWN": "UP"}
return direction1 == opposite_directions[direction2]
def get_actual_cost(self):
return self.g
def get_successors(self):
successors = []
zero_row = -1
zero_col = -1
for i in range (0, self.n):
for j in range (0, self.n):
if self.state[i][j] == 0:
zero_row = i
zero_col = j
break
# Obtain next set of states.
up_index = [zero_row - 1, zero_col, "DOWN"]
left_index = [zero_row, zero_col - 1, "RIGHT"]
right_index = [zero_row, zero_col + 1, "LEFT"]
down_index = [zero_row + 1, zero_col, "UP"]
indices = [up_index, left_index, right_index, down_index]
for index in indices:
# Ignore if indices out of bound.
if (index[0] < 0 or index[0] >= self.n) or (index[1] < 0 or index[1] >= self.n):
continue
# Don't go back to previous state (opposite direction).
if self.is_opposite_direction(self.direction, index[2]):
continue
next_state = [list(i) for i in self.state]
next_state[zero_row][zero_col] = next_state[index[0]][index[1]]
next_state[index[0]][index[1]] = 0
successors.append(Node(tuple(tuple(i) for i in next_state), self.g + 1, self, index[2]))
return successors
if __name__ == "__main__":
# do NOT modify below
# argv[0] represents the name of the file that is being executed
# argv[1] represents name of input file
# argv[2] represents name of destination output file
if len(sys.argv) != 3:
raise ValueError("Wrong number of arguments!")
try:
f = open(sys.argv[1], 'r')
except IOError:
raise IOError("Input file not found!")
lines = f.readlines()
# n = num rows in input file
n = len(lines)
# max_num = n to the power of 2 - 1
max_num = n ** 2 - 1
# Instantiate a 2D list of size n x n
init_state = [[0 for i in range(n)] for j in range(n)]
goal_state = [[0 for i in range(n)] for j in range(n)]
i,j = 0, 0
for line in lines:
for number in line.split(" "):
if number == '':
continue
value = int(number , base = 10)
if 0 <= value <= max_num:
init_state[i][j] = value
j += 1
if j == n:
i += 1
j = 0
for i in range(1, max_num + 1):
goal_state[(i-1)//n][(i-1)%n] = i
goal_state[n - 1][n - 1] = 0
puzzle = Puzzle(init_state, goal_state)
ans = puzzle.solve()
with open(sys.argv[2], 'a') as f:
for answer in ans:
f.write(answer+'\n')