Last active
December 10, 2015 18:38
-
-
Save n0phx/4476005 to your computer and use it in GitHub Desktop.
edX MIT 6.00x Problem Set 10 solution / approach 2
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 6.00 Problem Set 10 | |
# Graph optimization | |
# | |
# A set of data structures to represent graphs | |
# | |
class Node(object): | |
def __init__(self, name): | |
self.name = str(name) | |
def getName(self): | |
return self.name | |
def __str__(self): | |
return self.name | |
def __repr__(self): | |
return self.name | |
def __eq__(self, other): | |
return self.name == other.name | |
def __ne__(self, other): | |
return not self.__eq__(other) | |
class SmartNode(Node): | |
def __hash__(self): | |
return int(self.name) | |
class Edge(object): | |
def __init__(self, src, dest): | |
self.src = src | |
self.dest = dest | |
def getSource(self): | |
return self.src | |
def getDestination(self): | |
return self.dest | |
def __str__(self): | |
return '%s->%s' % (str(self.src), str(self.dest)) | |
class WeightedEdge(Edge): | |
def __init__(self, src, dest, total_distance, distance_outdoors): | |
super(WeightedEdge, self).__init__(src, dest) | |
self.total_distance = total_distance | |
self.distance_outdoors = distance_outdoors | |
class Path(object): | |
def __init__(self, nodes_visited, total_distance, distance_outdoors): | |
self._nodes_visited = list(nodes_visited) | |
self.total_distance = total_distance | |
self.distance_outdoors = distance_outdoors | |
def is_node_visited(self, node): | |
return node in self._nodes_visited | |
def is_end_reached(self, end): | |
return self._nodes_visited[-1].getName() == end | |
def get_node_list(self): | |
return [str(node) for node in self._nodes_visited] | |
def get_current_position(self): | |
return self._nodes_visited[-1] | |
def add_edge(self, edge): | |
self.total_distance += edge.total_distance | |
self.distance_outdoors += edge.distance_outdoors | |
self._nodes_visited.append(edge.getDestination()) | |
def clone(self): | |
return Path(self._nodes_visited, | |
self.total_distance, | |
self.distance_outdoors) | |
def __str__(self): | |
return ' => '.join(str(node) for node in self._nodes_visited) | |
class Digraph(object): | |
""" | |
A directed graph | |
""" | |
def __init__(self): | |
self.nodes = set([]) | |
self.edges = {} | |
def addNode(self, node): | |
if node in self.nodes: | |
raise ValueError('Duplicate node') | |
else: | |
self.nodes.add(node) | |
self.edges[node] = [] | |
def addEdge(self, edge): | |
src = edge.getSource() | |
dest = edge.getDestination() | |
if not(src in self.nodes and dest in self.nodes): | |
raise ValueError('Node not in graph') | |
self.edges[src].append(dest) | |
def childrenOf(self, node): | |
return self.edges[node] | |
def hasNode(self, node): | |
return node in self.nodes | |
def __str__(self): | |
res = '' | |
for k in self.edges: | |
for d in self.edges[str(k)]: | |
res = '%s%s->%s\n' % (res, str(k), str(d)) | |
return res[:-1] | |
class SmartDigraph(Digraph): | |
def addEdge(self, edge): | |
src = edge.getSource() | |
dest = edge.getDestination() | |
if not(src in self.nodes and dest in self.nodes): | |
raise ValueError('Node not in graph') | |
self.edges[src].append(edge) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 6.00 Problem Set 10 | |
# Graph optimization | |
# Finding shortest paths through MIT buildings | |
# | |
# Name: | |
# Collaborators: | |
# | |
import time | |
from graph import SmartNode, WeightedEdge, Path, SmartDigraph | |
# | |
# Problem 2: Building up the Campus Map | |
# | |
# Write a couple of sentences describing how you will model the | |
# problem as a graph | |
# | |
def load_map(mapFilename): | |
""" | |
Parses the map file and constructs a directed graph | |
Parameters: | |
mapFilename : name of the map file | |
Assumes: | |
Each entry in the map file consists of the following four positive | |
integers, separated by a blank space: | |
From To TotalDistance DistanceOutdoors | |
e.g. | |
32 76 54 23 | |
This entry would become an edge from 32 to 76. | |
Returns: | |
a directed graph representing the map | |
""" | |
print "Loading map from file..." | |
campus_graph = SmartDigraph() | |
with open(mapFilename, 'r') as map_file: | |
for line in map_file.readlines(): | |
(start_loc, | |
end_loc, | |
total_distance, | |
distance_outdoors) = line.split() | |
start_node = SmartNode(start_loc) | |
end_node = SmartNode(end_loc) | |
if not campus_graph.hasNode(start_node): | |
campus_graph.addNode(start_node) | |
if not campus_graph.hasNode(end_node): | |
campus_graph.addNode(end_node) | |
edge = WeightedEdge(start_node, | |
end_node, | |
int(total_distance), | |
int(distance_outdoors)) | |
campus_graph.addEdge(edge) | |
return campus_graph | |
# | |
# Problem 3: Finding the Shortest Path using Brute Force Search | |
# | |
# State the optimization problem as a function to minimize | |
# and the constraints | |
# | |
def timeit(func): | |
def _timeit(*args, **kwargs): | |
start = time.time() | |
try: | |
result = func(*args, **kwargs) | |
except ValueError: | |
raise | |
else: | |
return result | |
finally: | |
duration = time.time() - start | |
print '{0} ran for: {1:0.5f} secs.'.format(func.__name__, duration) | |
return _timeit | |
@timeit | |
def bruteForceSearch(digraph, start, end, maxTotalDist, maxDistOutdoors): | |
""" | |
Finds the shortest path from start to end using brute-force approach. | |
The total distance travelled on the path must not exceed maxTotalDist, and | |
the distance spent outdoor on this path must not exceed maxDisOutdoors. | |
Parameters: | |
digraph: instance of class Digraph or its subclass | |
start, end: start & end building numbers (strings) | |
maxTotalDist : maximum total distance on a path (integer) | |
maxDistOutdoors: maximum distance spent outdoors on a path (integer) | |
Assumes: | |
start and end are numbers for existing buildings in graph | |
Returns: | |
The shortest-path from start to end, represented by | |
a list of building numbers (in strings), [n_1, n_2, ..., n_k], | |
where there exists an edge from n_i to n_(i+1) in digraph, | |
for all 1 <= i < k. | |
If there exists no path that satisfies maxTotalDist and | |
maxDistOutdoors constraints, then raises a ValueError. | |
""" | |
stack = [Path([SmartNode(start)], 0, 0)] | |
valid_paths = [] | |
while stack: | |
path = stack.pop(-1) | |
for edge in digraph.childrenOf(path.get_current_position()): | |
if not path.is_node_visited(edge.getDestination()): | |
new_path = path.clone() | |
new_path.add_edge(edge) | |
# "...consider first finding all valid paths that satisfy | |
# the max distance outdoors constraint,..." | |
if new_path.distance_outdoors <= maxDistOutdoors: | |
if new_path.is_end_reached(end): | |
valid_paths.append(new_path) | |
else: | |
stack.append(new_path) | |
# "... and then going through those paths and returning the shortest, | |
# rather than trying to fulfill both constraints at once..." | |
valid_paths = filter(lambda path: path.total_distance <= maxTotalDist, | |
valid_paths) | |
# min will raise a ValueError if an empty sequence is passed to it | |
shortest = min(valid_paths, key=lambda x: x.total_distance) | |
return shortest.get_node_list() | |
# | |
# Problem 4: Finding the Shorest Path using Optimized Search Method | |
# | |
@timeit | |
def directedDFS(digraph, start, end, maxTotalDist, maxDistOutdoors): | |
""" | |
Finds the shortest path from start to end using directed depth-first. | |
search approach. The total distance travelled on the path must not | |
exceed maxTotalDist, and the distance spent outdoor on this path must | |
not exceed maxDisOutdoors. | |
Parameters: | |
digraph: instance of class Digraph or its subclass | |
start, end: start & end building numbers (strings) | |
maxTotalDist : maximum total distance on a path (integer) | |
maxDistOutdoors: maximum distance spent outdoors on a path (integer) | |
Assumes: | |
start and end are numbers for existing buildings in graph | |
Returns: | |
The shortest-path from start to end, represented by | |
a list of building numbers (in strings), [n_1, n_2, ..., n_k], | |
where there exists an edge from n_i to n_(i+1) in digraph, | |
for all 1 <= i < k. | |
If there exists no path that satisfies maxTotalDist and | |
maxDistOutdoors constraints, then raises a ValueError. | |
""" | |
stack = [Path([SmartNode(start)], 0, 0)] | |
while stack: | |
path = stack.pop(-1) | |
for edge in digraph.childrenOf(path.get_current_position()): | |
if not path.is_node_visited(edge.getDestination()): | |
new_path = path.clone() | |
new_path.add_edge(edge) | |
if (new_path.distance_outdoors <= maxDistOutdoors and | |
new_path.total_distance <= maxTotalDist): | |
if new_path.is_end_reached(end): | |
return new_path.get_node_list() | |
else: | |
stack.append(new_path) | |
raise ValueError() | |
# Uncomment below when ready to test | |
if __name__ == '__main__': | |
# Test cases | |
digraph = load_map("mit_map.txt") | |
LARGE_DIST = 1000000 | |
# Test case 1 | |
print "---------------" | |
print "Test case 1:" | |
print "Find the shortest-path from Building 32 to 56" | |
expectedPath1 = ['32', '56'] | |
brutePath1 = bruteForceSearch(digraph, '32', '56', LARGE_DIST, LARGE_DIST) | |
dfsPath1 = directedDFS(digraph, '32', '56', LARGE_DIST, LARGE_DIST) | |
print "Expected: ", expectedPath1 | |
print "Brute-force: ", brutePath1 | |
print "DFS: ", dfsPath1 | |
# Test case 2 | |
print "---------------" | |
print "Test case 2:" | |
print "Find the shortest-path from Building 32 to 56 without going out" | |
expectedPath2 = ['32', '36', '26', '16', '56'] | |
brutePath2 = bruteForceSearch(digraph, '32', '56', LARGE_DIST, 0) | |
dfsPath2 = directedDFS(digraph, '32', '56', LARGE_DIST, 0) | |
print "Expected: ", expectedPath2 | |
print "Brute-force: ", brutePath2 | |
print "DFS: ", dfsPath2 | |
# Test case 3 | |
print "---------------" | |
print "Test case 3:" | |
print "Find the shortest-path from Building 2 to 9" | |
expectedPath3 = ['2', '3', '7', '9'] | |
brutePath3 = bruteForceSearch(digraph, '2', '9', LARGE_DIST, LARGE_DIST) | |
dfsPath3 = directedDFS(digraph, '2', '9', LARGE_DIST, LARGE_DIST) | |
print "Expected: ", expectedPath3 | |
print "Brute-force: ", brutePath3 | |
print "DFS: ", dfsPath3 | |
# Test case 4 | |
print "---------------" | |
print "Test case 4:" | |
print "Find the shortest-path from Building 2 to 9 without going outdoors" | |
expectedPath4 = ['2', '4', '10', '13', '9'] | |
brutePath4 = bruteForceSearch(digraph, '2', '9', LARGE_DIST, 0) | |
dfsPath4 = directedDFS(digraph, '2', '9', LARGE_DIST, 0) | |
print "Expected: ", expectedPath4 | |
print "Brute-force: ", brutePath4 | |
print "DFS: ", dfsPath4 | |
# Test case 5 | |
print "---------------" | |
print "Test case 5:" | |
print "Find the shortest-path from Building 1 to 32" | |
expectedPath5 = ['1', '4', '12', '32'] | |
brutePath5 = bruteForceSearch(digraph, '1', '32', LARGE_DIST, LARGE_DIST) | |
dfsPath5 = directedDFS(digraph, '1', '32', LARGE_DIST, LARGE_DIST) | |
print "Expected: ", expectedPath5 | |
print "Brute-force: ", brutePath5 | |
print "DFS: ", dfsPath5 | |
# Test case 6 | |
print "---------------" | |
print "Test case 6:" | |
print "Find the shortest-path from Building 1 to 32 without going outdoors" | |
expectedPath6 = ['1', '3', '10', '4', '12', '24', '34', '36', '32'] | |
brutePath6 = bruteForceSearch(digraph, '1', '32', LARGE_DIST, 0) | |
dfsPath6 = directedDFS(digraph, '1', '32', LARGE_DIST, 0) | |
print "Expected: ", expectedPath6 | |
print "Brute-force: ", brutePath6 | |
print "DFS: ", dfsPath6 | |
# Test case 7 | |
print "---------------" | |
print "Test case 7:" | |
print "Find the shortest-path from Building 8 to 50 without going outdoors" | |
bruteRaisedErr = 'No' | |
dfsRaisedErr = 'No' | |
try: | |
bruteForceSearch(digraph, '8', '50', LARGE_DIST, 0) | |
except ValueError: | |
bruteRaisedErr = 'Yes' | |
try: | |
directedDFS(digraph, '8', '50', LARGE_DIST, 0) | |
except ValueError: | |
dfsRaisedErr = 'Yes' | |
print "Expected: No such path! Should throw a value error." | |
print "Did brute force search raise an error?", bruteRaisedErr | |
print "Did DFS search raise an error?", dfsRaisedErr | |
# Test case 8 | |
print "---------------" | |
print "Test case 8:" | |
print "Find the shortest-path from Building 10 to 32 without walking" | |
print "more than 100 meters in total" | |
bruteRaisedErr = 'No' | |
dfsRaisedErr = 'No' | |
try: | |
bruteForceSearch(digraph, '10', '32', 100, LARGE_DIST) | |
except ValueError: | |
bruteRaisedErr = 'Yes' | |
try: | |
directedDFS(digraph, '10', '32', 100, LARGE_DIST) | |
except ValueError: | |
dfsRaisedErr = 'Yes' | |
print "Expected: No such path! Should throw a value error." | |
print "Did brute force search raise an error?", bruteRaisedErr | |
print "Did DFS search raise an error?", dfsRaisedErr |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment