-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Expand file tree
/
Copy pathrobot_paths.py
More file actions
executable file
·147 lines (111 loc) · 4.2 KB
/
Copy pathrobot_paths.py
File metadata and controls
executable file
·147 lines (111 loc) · 4.2 KB
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
#! /usr/bin/env python
# Author: Victor Terron (c) 2014
# Email: `echo vt2rron1iaa32s | tr 132 @.e`
# License: GNU GPLv3
# 9.2. Imagine a robot sitting on the upper left corner of an X by Y grid. The
# robot can only move in two directions: right and down. How many possible
# paths are there for the robot to go from (0, 0) to (X, Y)?
#
# FOLLOW UP
#
# Imagine certain spots are "off limits", such that the robot cannot step on
# them. Design an algorithm to find a path for the robot from the top left to
# the bottom right.
import functools
import unittest
def memoize(f):
""" Minimalistic memoization decorator (*args / **kwargs)
Based on: http://code.activestate.com/recipes/577219/ """
cache = {}
@functools.wraps(f)
def memf(*args, **kwargs):
fkwargs = frozenset(kwargs.items())
if (args, fkwargs) not in cache:
cache[args, fkwargs] = f(*args, **kwargs)
return cache[args, fkwargs]
return memf
class Grid(object):
def __init__(self, x, y):
self.x = x
self.y = y
self.forbidden_coords = set()
def mark_as_forbidden(self, row, column):
""" Mark (x, y) as off-limits """
self.forbidden_coords.add((row, column))
def is_allowed(self, row, column):
""" Check whether (row, column) is a valid spot.
Return True if (row, column) is inside the bounds of the grid and has
not been marked as off-limits via the Grid.mark_as_forbidden() method.
"""
return (0 <= row < self.x and
0 <= column < self.y and
(row, column) not in self.forbidden_coords)
@memoize
def find_path(self, row = 0, column = 0):
""" Return the path from (row, column) to (x - 1, y - 1) """
# Finding a path from (x, y) to the destination is the same as moving
# to its two neighbors, (x - 1, y) and (x, y - 1), and finding the path
# from there: we keep doing this until a path reaches the destination.
# Always try the right neighbor first, then, if it does not gives us a
# valid solution, the bottom one. If the destination cannot be reached,
# we backtrack returning None.
destination = (self.x - 1, self.y - 1)
current = [(row, column)]
if (row, column) == destination:
return current
neighbors = []
neighbors.append((row, column + 1))
neighbors.append((row + 1, column))
for n in neighbors:
if self.is_allowed(*n):
path = self.find_path(*n)
# Subpath reaches bottom right
if path and path[-1] == destination:
return current + path
class GridTest(unittest.TestCase):
def test_find_path_simple_case(self):
# 1 1
# 1 1
grid = Grid(2, 2)
path = grid.find_path()
expected = [(0, 0), (0, 1), (1, 1)]
self.assertEqual(path, expected)
def test_find_path_offlimits_spots(self):
# 1 0
# 1 1
grid = Grid(2, 2)
grid.mark_as_forbidden(0,1)
path = grid.find_path()
expected = [(0, 0), (1, 0), (1, 1)]
self.assertEqual(path, expected)
# 1 0 1
# 1 1 1
# 1 1 1
grid = Grid(3, 3)
grid.mark_as_forbidden(0,1)
path = grid.find_path()
expected = [(0, 0), (1, 0), (1, 1), (1, 2), (2, 2)]
self.assertEqual(path, expected)
def test_find_path_unreachable_destination(self):
# 1 0
# 0 1
grid = Grid(2, 2)
grid.mark_as_forbidden(0,1)
grid.mark_as_forbidden(1,0)
path = grid.find_path()
self.assertEqual(path, None)
def test_find_path_dead_end(self):
# 1 1 0 0 0
# 0 1 1 1 0 <-- dead-end corridor
# 0 1 0 0 0
# 0 1 1 1 1
grid = Grid(4, 5)
offlimits = [(0, 2), (0, 3), (0, 4), (1, 0), (1, 4),
(2, 0), (2, 2), (2, 3), (2, 4), (3, 0)]
for coords in offlimits:
grid.mark_as_forbidden(*coords)
path = grid.find_path()
expected = [(0, 0), (0, 1), (1, 1), (2, 1), (3, 1), (3, 2), (3, 3), (3, 4)]
self.assertEqual(path, expected)
if __name__ == "__main__":
unittest.main()