-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathexam4.py
394 lines (320 loc) · 12.8 KB
/
exam4.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
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
"""
UNIT 4: Search
Your task is to maneuver a car in a crowded parking lot. This is a kind of
puzzle, which can be represented with a diagram like this:
| | | | | | | |
| G G . . . Y |
| P . . B . Y |
| P * * B . Y @
| P . . B . . |
| O . . . A A |
| O . S S S . |
| | | | | | | |
A '|' represents a wall around the parking lot, a '.' represents an empty square,
and a letter or asterisk represents a car. '@' marks a goal square.
Note that there are long (3 spot) and short (2 spot) cars.
Your task is to get the car that is represented by '**' out of the parking lot
(on to a goal square). Cars can move only in the direction they are pointing.
In this diagram, the cars GG, AA, SSS, and ** are pointed right-left,
so they can move any number of squares right or left, as long as they don't
bump into another car or wall. In this diagram, GG could move 1, 2, or 3 spots
to the right; AA could move 1, 2, or 3 spots to the left, and ** cannot move
at all. In the up-down direction, BBB can move one up or down, YYY can move
one down, and PPP and OO cannot move.
You should solve this puzzle (and ones like it) using search. You will be
given an initial state like this diagram and a goal location for the ** car;
in this puzzle the goal is the '.' empty spot in the wall on the right side.
You should return a path -- an alternation of states and actions -- that leads
to a state where the car overlaps the goal.
An action is a move by one car in one direction (by any number of spaces).
For example, here is a successor state where the AA car moves 3 to the left:
| | | | | | | |
| G G . . . Y |
| P . . B . Y |
| P * * B . Y @
| P . . B . . |
| O A A . . . |
| O . . . . . |
| | | | | | | |
And then after BBB moves 2 down and YYY moves 3 down, we can solve the puzzle
by moving ** 4 spaces to the right:
| | | | | | | |
| G G . . . . |
| P . . . . . |
| P . . . . * *
| P . . B . Y |
| O A A B . Y |
| O . . B . Y |
| | | | | | | |
You will write the function
solve_parking_puzzle(start, N=N)
where 'start' is the initial state of the puzzle and 'N' is the length of a side
of the square that encloses the pieces (including the walls, so N=8 here).
We will represent the grid with integer indexes. Here we see the
non-wall index numbers (with the goal at index 31):
| | | | | | | |
| 9 10 11 12 13 14 |
| 17 18 19 20 21 22 |
| 25 26 27 28 29 30 31
| 33 34 35 36 37 38 |
| 41 42 43 44 45 46 |
| 49 50 51 52 53 54 |
| | | | | | | |
The wall in the upper left has index 0 and the one in the lower right has 63.
We represent a state of the problem with one big tuple of (object, locations)
pairs, where each pair is a tuple and the locations are a tuple. Here is the
initial state for the problem above in this format:
"""
puzzle1 = (
('@', (31,)),
('*', (26, 27)),
('G', (9, 10)),
('Y', (14, 22, 30)),
('P', (17, 25, 33)),
('O', (41, 49)),
('B', (20, 28, 36)),
('A', (45, 46)),
('|', (0, 1, 2, 3, 4, 5, 6, 7, 8, 15, 16, 23, 24, 32, 39,
40, 47, 48, 55, 56, 57, 58, 59, 60, 61, 62, 63)))
# A solution to this puzzle is as follows:
# path = solve_parking_puzzle(puzzle1, N=8)
# path_actions(path) == [('A', -3), ('B', 16), ('Y', 24), ('*', 4)]
# That is, move car 'A' 3 spaces left, then 'B' 2 down, then 'Y' 3 down,
# and finally '*' moves 4 spaces right to the goal.
# Your task is to define solve_parking_puzzle:
N = 8
def solve_parking_puzzle(start, N=N):
"""Solve the puzzle described by the starting position (a tuple
of (object, locations) pairs). Return a path of [state, action, ...]
alternating items; an action is a pair (object, distance_moved),
such as ('B', 16) to move 'B' two squares down on the N=8 grid."""
return shortest_path_search(start, psuccessors, is_goal)
def psuccessors(state):
from copy import deepcopy
import itertools
cars = {}
walls = []
cars_pos = []
other_car_pos = {}
for item_name, item_pos in state:
if item_name not in ["@", "|"]:
cars.setdefault(item_name, item_pos)
cars_pos.extend(item_pos)
if item_name == "|":
walls.extend(item_pos)
for car_name, _ in state:
if car_name not in ["@", "|"]:
other_car_pos.setdefault(car_name, [pos for pos in cars_pos if (pos not in cars[car_name] and pos not in walls)])
#print cars
def distant_to_boarder(car, one_direction_step):
find_boarder = False
current_pos = cars[car]
if one_direction_step == 1:
length_tweak = len(cars[car]) - 1
else:
length_tweak = 0
if abs(one_direction_step) == N:
v_find = True
else:
v_find = False
if not v_find:
start_pos = cars[car][0]
while not find_boarder:
start_pos += one_direction_step
if start_pos in walls or start_pos in other_car_pos[car]:
find_boarder = True
return start_pos - cars[car][0] - length_tweak
else:
#vertical find
results = []
for pos in current_pos:
v_pos = pos
while not find_boarder:
v_pos += one_direction_step
if v_pos in walls or v_pos in other_car_pos[car] or v_pos < 1 or v_pos > 64:
find_boarder = True
results.append(v_pos - pos)
return max(results)
def has_clear_path_direction(car, one_direction, abs_step):
near_by_grid_is_blocked = any((pos+(one_direction*abs_step) in walls or pos+(one_direction*abs_step) in other_car_pos[car] for pos in cars[car]))
if near_by_grid_is_blocked:
return False
origin_car_pos = cars[car]
if one_direction < 0:
step = 0-abs_step
else:
step = abs_step
#should not be one_direction+step, one_direction+step only searchs next grid
other_car_in_the_path = any(((pos+grid) in other_car_pos[car] or (pos+grid) in walls for grid in xrange(0, distant_to_boarder(car, step), step) for pos in origin_car_pos[:1]))
#other_car_in_the_path = any(((pos+grid) in other_car_pos[car] or (pos+grid) in walls for grid in xrange(0, one_direction+step, step) for pos in origin_car_pos[:1]))
if other_car_in_the_path:
return False
return True
def has_clear_path(car, move):
#other_cars_pos = [pos for pos in cars_pos if (pos not in cars[car] or pos in walls)] #bottle neck?
origin_car_pos = cars[car]
one_direction = get_car_action(car, move)
if one_direction % N == 0:
step_unit = 8
else:
step_unit = 1
if one_direction < 0:
step = 0-step_unit
else:
step = step_unit
other_car_in_the_path = any(((pos+grid) in other_car_pos[car] or (pos+grid) in walls for grid in xrange(0, one_direction+step, step) for pos in origin_car_pos[:1]))
if other_car_in_the_path:
return False
return True
def legible(car, move):
#if not has_clear_path(car, move):
# return False
return True
def get_car_action(car_name, moved_pos):
origin_pos = cars[car_name]
return moved_pos[0] - origin_pos[0]
state_action_pair = {}
#print distant_to_boarder("A", 45, -1)
for car, car_pos in cars.iteritems():
#Only +-(N-car_pos) - 2, +-N*(N-1)
#make fewer directions
h_directions = [d for d in [1,-1] if has_clear_path_direction(car, d, 1)]
v_directions = [d for d in [1, -1] if has_clear_path_direction(car, d, N)]
#if car == "A":
# print h_directions, v_directions
steps = [i*direction for i in range(1,N-1) for direction in h_directions]
step_v = [i*direction for i in range(N, N*(N-1-2), N) for direction in v_directions]
steps.extend(step_v)
step_moved_pos = [tuple(((p + s) for p in car_pos if p+s not in other_car_pos[car] and p+s not in walls)) for s in steps]
step_moved_pos = [s for s in step_moved_pos if s and len(s) == len(car_pos)]
#filtered out all illegible destination pos
#if car == "Y":
# print "car %s step_moved_pos" % car, step_moved_pos
fesible_step_moved_pos = [move for move in step_moved_pos if legible(car, move)]
#if car == "Y":
# print "car %s" % car, fesible_step_moved_pos
def get_new_state_with_car_move(car_name, moved_pos):
ret_state = list(state)
ret = []
for s_car_name, pos in ret_state:
if s_car_name != car_name:
ret.append((s_car_name, pos))
else:
ret.append((car_name, moved_pos))
return tuple(ret)
#return {state:action} pair
for step in fesible_step_moved_pos:
state_action_pair.setdefault(get_new_state_with_car_move(car, step), (car, get_car_action(car, step)))
return state_action_pair
# But it would also be nice to have a simpler format to describe puzzles,
# and a way to visualize states.
# You will do that by defining the following two functions:
def locs(start, n, incr=1):
"Return a tuple of n locations, starting at start and incrementing by incr."
return tuple(tuple((start + (x)*incr for x in xrange(n))))
def grid(cars, N=N):
"""Return a tuple of (object, locations) pairs -- the format expected for
this puzzle. This function includes a wall pair, ('|', (0, ...)) to
indicate there are walls all around the NxN grid, except at the goal
location, which is the middle of the right-hand wall; there is a goal
pair, like ('@', (31,)), to indicate this. The variable 'cars' is a
tuple of pairs like ('*', (26, 27)). The return result is a big tuple
of the 'cars' pairs along with the walls and goal pairs."""
ret = [x for x in xrange(N*N)]
for x in xrange(N):
ret[x] = "|"
ret[x + N * (N-1)] = "|"
for x in xrange(1, N-1):
ret[x * N] = "|"
ret[x * N + N - 1] = "|"
for (c, squares) in cars:
for s in squares:
ret[s] = c
ret[(N-1) + ((N/2)-1) * N] = "@"
ret_dict = {}
for i, c in enumerate(ret):
if not str(c).isdigit():
ret_dict.setdefault(c, [])
ret_dict[c].append(i)
result = []
for k,v in ret_dict.iteritems():
result.append((k,tuple(v)))
return tuple(result)
"""0 1 2
3 4 5
6 7 8"""
def show(state, N=N):
"Print a representation of a state as an NxN grid."
# Initialize and fill in the board.
board = ['.'] * N**2
for (c, squares) in state:
for s in squares:
board[s] = c
# Now print it out
for i,s in enumerate(board):
print s,
if i % N == N - 1: print
# Here we see the grid and locs functions in use:
puzzle1 = grid((
('*', locs(26, 2)),
('G', locs(9, 2)),
('Y', locs(14, 3, N)),
('P', locs(17, 3, N)),
('O', locs(41, 2, N)),
('B', locs(20, 3, N)),
('A', locs(45, 2))))
puzzle2 = grid((
('*', locs(26, 2)),
('B', locs(20, 3, N)),
('P', locs(33, 3)),
('O', locs(41, 2, N)),
('Y', locs(51, 3))))
puzzle3 = grid((
('*', locs(25, 2)),
('B', locs(19, 3, N)),
('P', locs(36, 3)),
('O', locs(45, 2, N)),
('Y', locs(49, 3))))
#print puzzle1
#print show(puzzle1)
#print psuccessors(puzzle1)
#for state, action in psuccessors(puzzle1).iteritems():
# print state
# print action
def is_goal(state):
target_car_pos = []
for item_name, item_pos in state:
if item_name == "*":
target_car_pos.extend(item_pos)
if item_name == "@":
goal = item_pos[0]
return goal in target_car_pos
# Here are the shortest_path_search and path_actions functions from the unit.
# You may use these if you want, but you don't have to.
def shortest_path_search(start, successors, is_goal):
"""Find the shortest path from start state to a state
such that is_goal(state) is true."""
if is_goal(start):
return [start]
explored = set() # set of states we have visited
frontier = [ [start] ] # ordered list of paths we have blazed
while frontier:
path = frontier.pop(0)
s = path[-1]
for (state, action) in successors(s).items():
if state not in explored:
explored.add(state)
path2 = path + [action, state]
if is_goal(state):
return path2
else:
frontier.append(path2)
return []
def path_actions(path):
"Return a list of actions in this path."
return path[1::2]
print path_actions(solve_parking_puzzle(puzzle1))
for r in solve_parking_puzzle(puzzle1)[::2]:
print show(r)
import cProfile
cProfile.run('solve_parking_puzzle(puzzle1)')