Skip to content

Instantly share code, notes, and snippets.

@max-giro
Forked from n0phx/graph.py
Created January 6, 2013 14:32
Show Gist options
  • Save max-giro/4467547 to your computer and use it in GitHub Desktop.
Save max-giro/4467547 to your computer and use it in GitHub Desktop.
# 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, outdoors_distance):
super(WeightedEdge, self).__init__(src, dest)
self.total_distance = total_distance
self.outdoors_distance = outdoors_distance
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)
# 6.00 Problem Set 10
# Graph optimization
# Finding shortest paths through MIT buildings
#
# Name:
# Collaborators:
#
import time
from graph import SmartNode, WeightedEdge, 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,
outdoors_distance) = 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(outdoors_distance))
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 calc_total_distance(path):
return sum(edge.total_distance for edge in path)
def calc_distance_outdoors(path):
return sum(edge.outdoors_distance for edge in path)
def is_node_visited(node, path):
for edge in path:
if node in (edge.getSource(), edge.getDestination()):
return True
return False
def get_node_list(path):
visited = [str(edge.getSource()) for edge in path]
visited.append(str(path[-1].getDestination()))
return visited
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 = [[edge] for edge in digraph.childrenOf(SmartNode(start))]
valid_paths = []
while stack:
path = stack.pop(-1)
is_end_reached = path[-1].getDestination().getName() == end
if is_end_reached and calc_distance_outdoors(path) <= maxDistOutdoors:
valid_paths.append(path)
continue
for edge in digraph.childrenOf(path[-1].getDestination()):
if not is_node_visited(edge.getDestination(), path):
new_path = path + [edge]
# "...consider first finding all valid paths that satisfy
# the max distance outdoors constraint,..."
if calc_distance_outdoors(new_path) <= maxDistOutdoors:
stack.append(new_path)
shortest, min_distance = None, maxTotalDist
for path in valid_paths:
# "... and then going through those paths and returning the shortest,
# rather than trying to fulfill both constraints at once..."
distance = calc_total_distance(path)
if distance < min_distance:
shortest, min_distance = path, distance
if shortest is None:
raise ValueError()
return get_node_list(shortest)
#
# 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 = [[edge] for edge in digraph.childrenOf(SmartNode(start))]
while stack:
path = stack.pop(-1)
is_end_reached = path[-1].getDestination().getName() == end
if (is_end_reached and
calc_distance_outdoors(path) <= maxDistOutdoors and
calc_total_distance(path) <= maxTotalDist):
return get_node_list(path)
for edge in digraph.childrenOf(path[-1].getDestination()):
if not is_node_visited(edge.getDestination(), path):
new_path = path + [edge]
if (calc_distance_outdoors(new_path) <= maxDistOutdoors and
calc_total_distance(new_path) <= maxTotalDist):
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