Advertisement
Guest User

Untitled

a guest
Apr 25th, 2014
413
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 15.16 KB | None | 0 0
  1. __author__ = 'padraic'
  2. # 6.00.2x Problem Set 5
  3. # Graph optimization
  4. # Finding shortest paths through MIT buildings
  5. #
  6.  
  7. import string
  8.  
  9. # 6.00.2x Problem Set 5
  10. # Graph optimization
  11. #
  12. # A set of data structures to represent graphs
  13. #
  14.  
  15. class Node(object):
  16.     def __init__(self, name):
  17.         self.name = str(name)
  18.     def getName(self):
  19.         return self.name
  20.     def __str__(self):
  21.         return self.name
  22.     def __repr__(self):
  23.         return (self.name)
  24.     def __eq__(self, other):
  25.         return self.name == other.name
  26.     def __ne__(self, other):
  27.         return not self.__eq__(other)
  28.     def __hash__(self):
  29.         # Override the default hash method
  30.         # Think: Why would we want to do this?
  31.         return self.name.__hash__()
  32.  
  33. class Edge(object):
  34.     def __init__(self, src, dest):
  35.         self.src = src
  36.         self.dest = dest
  37.     def getSource(self):
  38.         return self.src
  39.     def getDestination(self):
  40.         return self.dest
  41.     def __str__(self):
  42.         return '{0}->{1}'.format(self.src, self.dest)
  43.  
  44. class Digraph(object):
  45.     """
  46.    A directed graph
  47.    """
  48.     def __init__(self):
  49.         # A Python Set is basically a list that doesn't allow duplicates.
  50.         # Entries into a set must be hashable (where have we seen this before?)
  51.         # Because it is backed by a hashtable, lookups are O(1) as opposed to the O(n) of a list (nifty!)
  52.         # See http://docs.python.org/2/library/stdtypes.html#set-types-set-frozenset
  53.         self.nodes = set([])
  54.         self.edges = {}
  55.     def addNode(self, node):
  56.         #print node
  57.         if node in self.nodes:
  58.             # Even though self.nodes is a Set, we want to do this to make sure we
  59.             # don't add a duplicate entry for the same node in the self.edges list.
  60.             raise ValueError('Duplicate node')
  61.         else:
  62.             self.nodes.add(node)
  63.             self.edges[node] = []
  64.     def addEdge(self, edge):
  65.         src = edge.getSource()
  66.         dest = edge.getDestination()
  67.         if not(src in self.nodes and dest in self.nodes):
  68.             raise ValueError('Node not in graph')
  69.         self.edges[src].append(dest)
  70.     def childrenOf(self, node):
  71.         return self.edges[node]
  72.     def hasNode(self, node):
  73.         return node in self.nodes
  74.     def __str__(self):
  75.         res = ''
  76.         for k in self.edges:
  77.             for d in self.edges[str(k)]:
  78.                 res = '{0}{1}->{2}\n'.format(res, k, d)
  79.         return res[:-1]
  80.  
  81.  
  82.  
  83.  
  84. class WeightedEdge(Edge):
  85.     def __init__(self,src,dest,total_distance,outside_distance):
  86.         Edge.__init__(self,src,dest)
  87.         self.node_1 = src
  88.         self.node_2 =  dest
  89.         self.total_distance = total_distance
  90.         self.outside_distance= outside_distance
  91.  
  92.     def getTotalDistance(self):
  93.         return (self.total_distance)
  94.  
  95.     def getOutdoorDistance(self):
  96.         return (self.outside_distance)
  97.  
  98.     def __str__(self):
  99.         return '{0}->{1} {2}'.format(self.node_1, self.node_2,self.total_distance, self.outside_distance)
  100.  
  101.  
  102. class WeightedDigraph(Digraph):
  103.     def __init__(self):
  104.         Digraph.__init__(self)
  105.     def addEdge(self, weighted_edge):
  106.         if not(weighted_edge.node_1 in self.nodes and weighted_edge.node_2 in self.nodes):
  107.             raise ValueError('Node not in graph')
  108.         self.edges[weighted_edge.getSource()].append(weighted_edge)
  109.     def childrenOf(self, node):
  110.         return self.edges[node]
  111.     def __str__(self):
  112.         res = ''
  113.         for k in self.edges:
  114.             for d in self.edges[k]:
  115.                 res = '{0}{1}->{2} ({3}, {4})''\n'.format(res, k, (d.dest),float(d.total_distance),float(d.outside_distance))
  116.         return res
  117.  
  118.  
  119. def load_map(mapFilename):
  120.     """
  121.    Parses the map file and constructs a directed graph
  122.  
  123.    Parameters:
  124.        mapFilename : name of the map file
  125.  
  126.    Assumes:
  127.        Each entry in the map file consists of the following four positive
  128.        integers, separated by a blank space:
  129.            From To TotalDistance DistanceOutdoors
  130.        e.g.
  131.            32 76 54 23
  132.        This entry would become an edge from 32 to 76.
  133.  
  134.    Returns:
  135.        a directed graph representing the map
  136.    """
  137.     digraph = WeightedDigraph()
  138.     with open(mapFilename, "r") as mit:
  139.         for line in mit:
  140.             (start_loc,
  141.                  end_loc,
  142.                  tot_dist,
  143.                  out_dist) = line.split()
  144.             n_1,n_2=Node(start_loc),Node(end_loc)
  145.  
  146.             if not digraph.hasNode(n_1):
  147.                 digraph.addNode(n_1)#
  148.  
  149.             if not digraph.hasNode(n_2):
  150.                 digraph.addNode(n_2)
  151.  
  152.             edge = WeightedEdge(n_1,n_2,tot_dist,out_dist)
  153.             digraph.addEdge(edge)
  154.  
  155.         return  digraph
  156.  
  157.  
  158. import time
  159. def timeit(func):
  160.     def _timeit(*args, **kwargs):
  161.         start = time.time()
  162.         try:
  163.             result = func(*args, **kwargs)
  164.         except ValueError:
  165.             raise
  166.         else:
  167.             return result
  168.         finally:
  169.             duration = time.time() - start
  170.             print '{0} ran for: {1:0.5f} secs.'.format(func.__name__, duration)
  171.     return _timeit
  172.  
  173.  
  174. @timeit
  175. def bruteForceSearch(digraph, start, end, maxTotalDist, maxDistOutdoors):
  176.  
  177.     """
  178.    Finds the shortest path from start to end using brute-force approach.
  179.    The total distance travelled on the path must not exceed maxTotalDist, and
  180.    the distance spent outdoor on this path must not exceed maxDisOutdoors.
  181.  
  182.    Parameters:
  183.        digraph: instance of class Digraph or its subclass
  184.        start, end: start & end building numbers (strings)
  185.        maxTotalDist : maximum total distance on a path (integer)
  186.        maxDistOutdoors: maximum distance spent outdoors on a path (integer)
  187.  
  188.    Assumes:
  189.        start and end are numbers for existing buildings in graph
  190.  
  191.    Returns:
  192.        The shortest-path from start to end, represented by
  193.        a list of building numbers (in strings), [n_1, n_2, ..., n_k],
  194.        where there exists an edge from n_i to n_(i+1) in digraph,
  195.        for all 1 <= i < k.
  196.  
  197.        If there exists no path that satisfies maxTotalDist and
  198.        maxDistOutdoors constraints, then raises a ValueError.
  199.    """
  200.     stack = [[edge] for edge in digraph.childrenOf(Node(start))]
  201.     valid_paths = []
  202.     end=Node(end)
  203.     while stack:
  204.         path = stack.pop()
  205.         current_end=path[-1].dest
  206.         # if end is reached and within max outdoor distance
  207.         if current_end == end and sum(int(edge.outside_distance) for edge in path) <= maxDistOutdoors:
  208.             valid_paths.append(path)
  209.             continue
  210.         for edge in digraph.childrenOf(current_end):
  211.             # make sure not already in path
  212.             if edge.dest not in  [x.src for x in path]: # loops
  213.                 new_path = path + [edge]
  214.                  # find all paths under the max distance outdoors constraint first
  215.                 if sum(int(edge.outside_distance) for edge in path) <= maxDistOutdoors:
  216.                     stack.append(new_path)
  217.     shortest, min_distance = None, maxTotalDist
  218.     for path in valid_paths:
  219.         sum_p=sum(int(edge.total_distance) for edge in path)
  220.         # sum total distance for each path, set shortest to path with lowest cost
  221.         if sum_p <= min_distance:
  222.             shortest, min_distance = path, sum_p
  223.     if shortest is None:
  224.         raise ValueError()
  225.     return  [str(x.src) for x in shortest] +[str(end)]
  226.  
  227.  
  228.  
  229. from heapq import *
  230. import  pdb
  231.  
  232. @timeit
  233. def directedDFS(digraph, start, end, maxTotalDist, maxDistOutdoors):
  234.     """
  235.    Finds the shortest path from start to end using directed depth-first.
  236.    search approach. The total distance travelled on the path must not
  237.    exceed maxTotalDist, and the distance spent outdoor on this path must
  238.    not exceed maxDistOutdoors.
  239.  
  240.    Parameters:
  241.    digraph: instance of class Digraph or its subclass
  242.    start, end: start & end building numbers (strings)
  243.    maxTotalDist : maximum total distance on a path (integer)
  244.    maxDistOutdoors: maximum distance spent outdoors on a path (integer)
  245.  
  246.    Assumes:
  247.    start and end are numbers for existing buildings in graph
  248.  
  249.    Returns:
  250.    The shortest-path from start to end, represented by
  251.    a list of building numbers (in strings), [n_1, n_2, ..., n_k],
  252.    where there exists an edge from n_i to n_(i+1) in digraph,
  253.    for all 1 <= i < k.
  254.  
  255.    If there exists no path that satisfies maxTotalDist and
  256.    maxDistOutdoors constraints, then raises a ValueError.
  257.    """
  258.     stack = [[edge] for edge in digraph.childrenOf(Node(start))]
  259.     end=Node(end)
  260.     best = None
  261.     best_path = None
  262.     while stack:
  263.         path =  heappop(stack)
  264.         current_end = path[-1].dest
  265.         # if end is reached and within max outdoor distance
  266.         sum_out_p =sum(int(edge.outside_distance) for edge in path)
  267.         sum_tot_p=sum(int(edge.total_distance) for edge in path)
  268.         if current_end == end  and sum_out_p <= maxDistOutdoors and sum_tot_p  <= maxTotalDist:
  269.             # if we find a shorter path  make best_path that path and best the new best distance
  270.              if best == None or sum_tot_p < best:
  271.                 best = sum_tot_p
  272.                 best_path = path
  273.              continue
  274.         for edge in digraph.childrenOf(current_end):
  275.             # make sure not already in path
  276.             if edge.dest not in  [x.src for x in path]: # avoid loops
  277.                 new_path = path + [edge]
  278.                 # if  new_path under both the max distance constraints
  279.                 sum_out_p = sum(int(edge.outside_distance) for edge in new_path)
  280.                 sum_tot_p= sum(int(edge.total_distance) for edge in new_path)
  281.                # pdb.set_trace()
  282.                 if sum_out_p <= maxDistOutdoors and \
  283.                         sum_tot_p <=maxTotalDist and sum_tot_p:
  284.                     # check if total so far is still less than best path total and add it to the stack
  285.                     if best == None or sum_tot_p < best:
  286.                         heappush(stack,new_path)
  287.                     # if not continue
  288.                     continue
  289.      # if we found a path
  290.     if best_path:
  291.         return [str(x.src) for x in best_path]+[str(end)]
  292.     # no appropriate path found
  293.     raise ValueError("No path found")
  294. #
  295. # Uncomment below when ready to test
  296. #### NOTE! These tests may take a few minutes to run!! ####
  297. if __name__ == '__main__':
  298. #     Test cases
  299.  
  300.     mitMap = load_map("mit_map.txt")
  301.     print isinstance(mitMap, Digraph)
  302.     print isinstance(mitMap, WeightedDigraph)
  303.     print 'nodes', mitMap.nodes
  304.     print 'edges', mitMap.edges
  305.  
  306. #    print mitMap.__str__()
  307.  
  308. #
  309.     LARGE_DIST = 10000
  310. #
  311. #    # Test case 1
  312.     print "---------------"
  313.     print "Test case 1:"
  314.     print "Find the shortest-path from Building 32 to 56"
  315.     expectedPath1 = ['32', '56']
  316.     brutePath1 =bruteForceSearch(mitMap, '32', '56', LARGE_DIST, LARGE_DIST)
  317.     dfsPath1 = directedDFS(mitMap, '32', '56', 100,100 )
  318.     print "Expected: ", expectedPath1
  319.     print "Brute-force: ", brutePath1
  320.     print "DFS: ", dfsPath1
  321.     print "Correct? BFS: {0}; DFS: {1}".format(expectedPath1 == brutePath1, expectedPath1 == dfsPath1)
  322.  
  323.  
  324.     # #
  325. # # #     Test case 2
  326.     print "---------------"
  327.     print "Test case 2:"
  328.     print "Find the shortest-path from Building 32 to 56 without going outdoors"
  329.     expectedPath2 = ['32', '36', '26', '16', '56']
  330.     brutePath2 = bruteForceSearch(mitMap, '32', '56', LARGE_DIST, 0)
  331.     dfsPath2 = directedDFS(mitMap, '32', '56', LARGE_DIST, 0)
  332.     print "Expected: ", expectedPath2
  333.     print "Brute-force: ", brutePath2
  334.     print "DFS: ", dfsPath2
  335.     print "Correct? BFS: {0}; DFS: {1}".format(expectedPath2 == brutePath2, expectedPath2 == dfsPath2)
  336.     #
  337.     # # Test case 3
  338.     print "---------------"
  339.     print "Test case 3:"
  340.     print "Find the shortest-path from Building 2 to 9"
  341.     expectedPath3 = ['2', '3', '7', '9']
  342.     brutePath3 = bruteForceSearch(mitMap, '2', '9', LARGE_DIST, LARGE_DIST)
  343.     dfsPath3 = directedDFS(mitMap, '2', '9', LARGE_DIST, LARGE_DIST)
  344.     print "Expected: ", expectedPath3
  345.     print "Brute-force: ", brutePath3
  346.     print "DFS: ", dfsPath3
  347.     print "Correct? BFS: {0}; DFS: {1}".format(expectedPath3 == brutePath3, expectedPath3 == dfsPath3)
  348. #
  349. #   ##  Test case 4
  350.     print "---------------"
  351.     print "Test case 4:"
  352.     print "Find the shortest-path from Building 2 to 9 without going outdoors"
  353.     expectedPath4 = ['2', '4', '10', '13', '9']
  354.     brutePath4 = bruteForceSearch(mitMap, '2', '9', LARGE_DIST, 0)
  355.     dfsPath4 = directedDFS(mitMap, '2', '9', LARGE_DIST, 0)
  356.     print "Expected: ", expectedPath4
  357.     print "Brute-force: ", brutePath4
  358.     print "DFS: ", dfsPath4
  359.     print "Correct? BFS: {0}; DFS: {1}".format(expectedPath4 == brutePath4, expectedPath4 == dfsPath4)
  360.  
  361.     ###Test case 5.
  362.     print "---------------"
  363.     print "Test case 5:"
  364.     print "Find the shortest-path from Building 1 to 32"
  365.     expectedPath5 = ['1', '4', '12', '32']
  366.     brutePath5 = bruteForceSearch(mitMap, '1', '32', LARGE_DIST, LARGE_DIST)
  367.     dfsPath5 = directedDFS(mitMap, '1', '32', LARGE_DIST, LARGE_DIST)
  368.     print "Expected: ", expectedPath5
  369.     print "Brute-force: ", brutePath5
  370.     print "DFS: ", dfsPath5
  371.     print "Correct? BFS: {0}; DFS: {1}".format(expectedPath5 == brutePath5, expectedPath5 == dfsPath5)
  372.  
  373.     ###Test case 6
  374.     print "---------------"
  375.     print "Test case 6:"
  376.     print "Find the shortest-path from Building 1 to 32 without going outdoors"
  377.     expectedPath6 = ['1', '3', '10', '4', '12', '24', '34', '36', '32']
  378.     brutePath6 = bruteForceSearch(mitMap, '1', '32', LARGE_DIST, 0)
  379.     dfsPath6 = directedDFS(mitMap, '1', '32', LARGE_DIST, 0)
  380.     print "Expected: ", expectedPath6
  381.     print "Brute-force: ", brutePath6
  382.     print "DFS: ", dfsPath6
  383.     print "Correct? BFS: {0}; DFS: {1}".format(expectedPath6 == brutePath6, expectedPath6 == dfsPath6)
  384.  
  385.  
  386.     ###Test case 7
  387.     print "---------------"
  388.     print "Test case 7:"
  389.     print "Find the shortest-path from Building 8 to 50 without going outdoors"
  390.     bruteRaisedErr = 'No'
  391.     dfsRaisedErr = 'No'
  392.     try:
  393.         bruteForceSearch(mitMap, '8', '50', LARGE_DIST, 0)
  394.     except ValueError:
  395.         bruteRaisedErr = 'Yes'
  396.  
  397.     try:
  398.         directedDFS(mitMap, '8', '50', LARGE_DIST, 0)
  399.     except ValueError:
  400.         dfsRaisedErr = 'Yes'
  401.  
  402.     print "Expected: No such path! Should throw a value error."
  403.     print "Did brute force search raise an error?", bruteRaisedErr
  404.     print "Did DFS search raise an error?", dfsRaisedErr
  405.  
  406.     #####Test case 8
  407.     print "---------------"
  408.     print "Test case 8:"
  409.     print "Find the shortest-path from Building 10 to 32 without walking"
  410.     print "more than 100 meters in total"
  411.     bruteRaisedErr = 'No'
  412.     dfsRaisedErr = 'No'
  413.     try:
  414.         bruteForceSearch(mitMap, '10', '32', 100, LARGE_DIST)
  415.     except ValueError:
  416.         bruteRaisedErr = 'Yes'
  417.  
  418.     try:
  419.         directedDFS(mitMap, '10', '32', 100, LARGE_DIST)
  420.     except ValueError:
  421.         dfsRaisedErr = 'Yes'
  422.  
  423.     print "Expected: No such path! Should throw a value error."
  424.     print "Did brute force search raise an error?", bruteRaisedErr
  425.     print "Did DFS search raise an error?", dfsRaisedErr
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement