# Java Program to Create Cheapest Path Assignment Solution.

## Instructions

Objective
Write a java assignment program to create cheapest path.

## Requirements and Specifications

Cheapest Path
In this notebook, we will implement an algorithm for finding the cheapest path between a source node and a destination node in a graph.
For every node $x$, we will keep its set of successors $s(x)$, and its set of predecessors $p(x)$; these will be represented in code as dictionaries mapping nodes to sets of nodes.
Every edge $(x, y)$ (that is, going from $x$ to $y$) has a cost $c(x, y)$, which we will also keep as a dictinary mapping pairs of nodes to their cost.
You can find the cheapest path in two steps.
## Finding the minimum cost of going from every node to the destination
First, you need to compute the cost of going from every node to the destination. In code, this happens in response to the method call
g.compute_cost(z), where g is the graph, and z is the destination.
You can compute the minimum cost $c(x)$ of reaching the destination $z$ from a node $x$ as follows.
Initially, you set $c(z) = 0$, and $c(x) = +\infty$ for all nodes $x \neq z$. In Python, $+\infty$ is simply float("inf"), and yes, it's a number.
Then, for all nodes $x \neq z$ with $s(x) \neq \emptyset$, you update $c(x)$ via:
$$c(x) := min_{y \in s(x)} [c(x, y) + c(y)] .$$
You continue doing the above update until the costs no longer change. If you wish, you can do an optimization of the algorithm in which you only propagate change from the nodes whose cost has changed, as we did in the class notebook. This is up to you.
The initialization $c(x) = +\infty$ for $x \neq z$ ensures that nodes that _cannot_ get to $z$ will have infinite cost (as they should); if it were not for this, also using $c(x) = 0$ as initialization would work.
You need to write a method cost so that g.cost(x) will return the cost $c(x)$ of node $x$; this will be useful in debugging (and grading) your code.
## Finding the cheapest path
The method g.cheapest(w, z) should return the list of nodes along the cheapest path from $w$ to $z$, starting from $w$, and ending with $z$, as a list.
The method should first call self.compute_cost(z), so that all the costs of nodes are known. Then, the algorithm proceeds as follows. First, as a sanity check, if $c(w) = +\infty$, you return None to indicate that there is no path.
Otherwise, the algorithm initializes the path $\sigma$ to $w$, as $\sigma = [w]$.
Then, the algorithm picks the last state $x$ of $\sigma$, and repeatedly:
* If $x = z$, we are done; we can return $\sigma$.
* Otherwise, we append to $\sigma$ the node $y \in s(x)$ such that $c(x, y) + c(y)$ is minimal.
As edge costs are strictly positive, you don't need to worry about loops.
This is it. It's not too hard really, and it is quite related to the problem of timed scheduling as you see.
Here is the class for the graph, with the methods for you to complete.
Source Code
Before you turn this problem in, make sure everything runs as expected. First, **restart the kernel** (in the menubar, select Kernel$\rightarrow$Restart) and then **run all cells** (in the menubar, select Cell$\rightarrow$Run All). Make sure you fill in any place that says YOUR CODE HERE or "YOUR ANSWER HERE", as well as your name and collaborators below: NAME = "" COLLABORATORS = "" --- # CSE 30 Fall 2021 - Homework 12 ## Cheapest Path ### Instructions Please disregard the YOUR NAME and COLLABORATORS above. They are put there atomatically by the grading tool. You can find instructions on how to work on a homework on Canvas. Here is a short summary: ### Submitting your work To submit your work: * First, click on "Runtime > Restart and run all", and check that you get no errors. This enables you to catch any error you might have introduced, and not noticed, due to your running cells out of order. * Second, download the notebook in .ipynb format (File > Download .ipynb) and upload the .ipynb file to [this form](https://docs.google.com/forms/d/e/1FAIpQLSfRaOL643ZtXzI4R8TVpmfGoAcSjgLmXTEeUDqN0ZHoVNO6ow/viewform?usp=sf_link). You can submit multiple times; the last submission before the deadline is the one that counts. ### Homework format For each question in this notebook, there is: * A text description of the problem. * One or more places where you have to insert your solution. You need to complete every place marked:     # YOUR CODE HERE     and you should not modify any other place. * One or more test cells. Each cell is worth some number of points, marked at the top. You should not modify these tests cells. The tests pass if no error is printed out: when there is a statement that says, for instance:     assert x == 2     then the test passes if x has value 2, and fails otherwise. You can insert a print(x) (for this case!) somewhere if you want to debug your work; it is up to you. ### Notes: * Your code will be tested both according to the tests you can see (the assert statements you can see), _and_ additional tests. This prevents you from hard-coding the answer to the particular questions posed. Your code should solve the _general_ intended case, not hard-code the particular answer for the values used in the tests. * **Please do not delete or add cells!** The test is autograded, and if you modify the test by adding or deleting cells, even if you re-add cells you delete, you may not receive credit. * **Please do not import modules that are not part of the [standard library](https://docs.python.org/3/library/index.html).** You do not need any, and they will likely not available in the grading environment, leading your code to fail. * **If you are inactive too long, your notebook might get disconnected from the back-end.** Your work is never lost, but you have to re-run all the cells before you continue. * You can write out print statements in your code, to help you test/debug it. But remember: the code is graded on the basis of what it outputs or returns, not on the basis of what it prints. * **TAs and tutors have access to this notebook,** so if you let them know you need their help, they can look at your work and give you advice. ### Grading Each cell where there are tests is worth a certain number of points. You get the points allocated to a cell only if you pass _all_ the tests in the cell. The tests in a cell include both the tests you can see, and other, similar, tests that are used for grading only. Therefore, you cannot hard-code the solutions: you really have to solve the essence of the problem, to receive the points in a cell. ### Code of Conduct * Work on the test yourself, alone. * You can search documentation on the web, on sites such as the Python documentation sites, Stackoverflow, and similar, and you can use the results. * You cannot share your work with others or solicit their help. # Cheapest Path In this notebook, we will implement an algorithm for finding the cheapest path between a source node and a destination node in a graph. For every node $x$, we will keep its set of successors $s(x)$, and its set of predecessors $p(x)$; these will be represented in code as dictionaries mapping nodes to sets of nodes. Every edge $(x, y)$ (that is, going from $x$ to $y$) has a cost $c(x, y)$, which we will also keep as a dictinary mapping pairs of nodes to their cost. You can find the cheapest path in two steps. ## Finding the minimum cost of going from every node to the destination First, you need to compute the cost of going from every node to the destination. In code, this happens in response to the method call g.compute_cost(z), where g is the graph, and z is the destination. You can compute the minimum cost $c(x)$ of reaching the destination $z$ from a node $x$ as follows. Initially, you set $c(z) = 0$, and $c(x) = +\infty$ for all nodes $x \neq z$. In Python, $+\infty$ is simply float("inf"), and yes, it's a number. Then, for all nodes $x \neq z$ with $s(x) \neq \emptyset$, you update $c(x)$ via: $$c(x) := min_{y \in s(x)} [c(x, y) + c(y)] .$$ You continue doing the above update until the costs no longer change. If you wish, you can do an optimization of the algorithm in which you only propagate change from the nodes whose cost has changed, as we did in the class notebook. This is up to you. The initialization $c(x) = +\infty$ for $x \neq z$ ensures that nodes that _cannot_ get to $z$ will have infinite cost (as they should); if it were not for this, also using $c(x) = 0$ as initialization would work. You need to write a method cost so that g.cost(x) will return the cost $c(x)$ of node $x$; this will be useful in debugging (and grading) your code. ## Finding the cheapest path The method g.cheapest(w, z) should return the list of nodes along the cheapest path from $w$ to $z$, starting from $w$, and ending with $z$, as a list. The method should first call self.compute_cost(z), so that all the costs of nodes are known. Then, the algorithm proceeds as follows. First, as a sanity check, if $c(w) = +\infty$, you return None to indicate that there is no path. Otherwise, the algorithm initializes the path $\sigma$ to $w$, as $\sigma = [w]$. Then, the algorithm picks the last state $x$ of $\sigma$, and repeatedly: * If $x = z$, we are done; we can return $\sigma$. * Otherwise, we append to $\sigma$ the node $y \in s(x)$ such that $c(x, y) + c(y)$ is minimal. As edge costs are strictly positive, you don't need to worry about loops. This is it. It's not too hard really, and it is quite related to the problem of timed scheduling as you see. Here is the class for the graph, with the methods for you to complete. from collections import defaultdict # This is infinity. Not kidding. INFINITY = float("inf") class PricedGraph(object):     def __init__(self):         self.s = defaultdict(set) # Successors         self.p = defaultdict(set) # Predecessors         self.c = {} # Cost of edges         self.computed_vertex = None         self.individual_cost = {}         self.individual_paths = {}         # Below you can put any other initialization you think is necessary.         # YOUR CODE HERE     def add_edge(self, x, y, c):         """Adds an edge from x to y with cost c."""         assert c > 0, "Costs need to be strictly positive."         self.s[x].add(y)         self.p[y].add(x)         self.c[(x, y)] = c         # Below, you can put any other thing you like to do.         # YOUR CODE HERE     def compute_cost(self, z):         """Computes the minimum cost of reaching z from every node.         Store this somewhere."""         self.computed_vertex = z         vertexs = list(self.s.keys()) + list(self.p.keys())         for vertex in vertexs:           D, paths = self.dijkstra(vertex)           self.individual_cost[vertex] = D           self.individual_paths[vertex] = paths     def dijkstra(self, start_vertex):       """         Compute the minimum cost from start vertex to all vertexs       """       visited = []       queue = []       edges = list(self.s.keys()) + list(self.p.keys())       D = {v:INFINITY for v in edges}       D[start_vertex] = 0       paths = []       # Start with first in sucessors       queue.append((0, start_vertex))       while len(queue) > 0:         (dist, vertex) = queue.pop(0)         visited.append(vertex)         for neighbor in self.s[vertex]:           cost = self.c[(vertex, neighbor)]           old_cost = D[neighbor]           new_cost = D[vertex] + cost           if new_cost < old_cost:             queue.append((new_cost, neighbor))             D[neighbor] = new_cost             #paths[neighbor] = vertex             paths.append((vertex, neighbor, cost, self.p[neighbor]))       # Reverse paths       #paths = {v: k for k, v in paths.items()}       return D, paths     def cost(self, x):         """Returns the cost of going from x to z. You should have stored this         cost somewhere in the above method compute_cost, for every x."""         # YOUR CODE HERE         return self.individual_cost[x][self.computed_vertex]     def cheapest_path(self, w, z):         """Returns the cheapest path from w to z, as a list beginning with w         and ending with z. Note: you need to call self.cost(z) first thing         inside the implementation of this method. If you CANNOT reach z,         which is indicated by w having infinite cost, return None."""         if w == z: # initial node is equal to the final           return [w]         self.compute_cost(z) # compute costs from all nodes to end node         if self.cost(w) == INFINITY: # if the cost of the initial node is Infinity, then there is no path           return None         paths = {w: (None, 0)} # dictionary to store the shortest paths         current = w # current node         visited = set()         while current != z: # while current is different from end node           visited.add(current) # add the current to the visited set           destinations = self.s[current]           current_cost = paths[current][1] # cost to current node           for neighbor in destinations:               cost = self.c[(current, neighbor)] + current_cost # cvalculate new cost               if neighbor not in paths:                   paths[neighbor] = (current, cost) # store the cost to neighbor node from current node               else:                   current_cost = paths[neighbor][1]                   if current_cost > cost:                       paths[neighbor] = (current, cost)           next_nodes = {node: paths[node] for node in paths if node not in visited} # get all next nodes that are not visited           if not next_nodes: # If there are not nodes, it means there is not a path from given nodes, so we return None               return None           # Select the node with the lowest cost           current = min(next_nodes, key=lambda k: next_nodes[k][1])       # Finally, reverse the path         path = []         while current != None:           path.append(current)           next = paths[current][0] # get next node           current = next         # Now, reverse         path = path[::-1]         return path Here you can play with your code. ### Here you can play with your code. # YOUR CODE HERE g = PricedGraph() g.add_edge('a', 'b', 2) g.add_edge('b', 'c', 3) g.add_edge('a', 'd', 1) g.add_edge('d', 'a', 3) print(g.cheapest_path('a', 'c')) ### Testing the cost Let's start trying how it works on simple examples. # 5 points g = PricedGraph() g.add_edge('a', 'b', 1) # You can go from a to b g.compute_cost('b') assert g.cost('a') == 1 assert g.cost('b') == 0 # But you can't go from b to a. g.compute_cost('a') assert g.cost('a') == 0 assert g.cost('b') == INFINITY Now, a tiny bit more complicated. # 5 points g = PricedGraph() g.add_edge('a', 'b', 2) g.add_edge('b', 'c', 3) g.add_edge('a', 'c', 6) g.add_edge('d', 'a', 4) g.compute_cost('c') assert g.cost('c') == 0 assert g.cost('b') == 3 assert g.cost('a') == 5 assert g.cost('d') == 9 g = PricedGraph() g.add_edge('a', 'b', 2) g.add_edge('b', 'c', 3) g.add_edge('a', 'c', 4) g.add_edge('d', 'a', 4) g.compute_cost('c') assert g.cost('c') == 0 assert g.cost('b') == 3 assert g.cost('a') == 4 assert g.cost('d') == 8 # 2 points # Let's take you for a loop. g = PricedGraph() g.add_edge('a', 'b', 2) g.add_edge('b', 'c', 3) g.add_edge('a', 'd', 1) g.add_edge('d', 'a', 3) g.compute_cost('c') assert g.cost('c') == 0 assert g.cost('b') == 3 assert g.cost('a') == 5 assert g.cost('d') == 8 # 5 points # Let's do the example from class. g = PricedGraph() g.add_edge('a', 'b', 2) g.add_edge('a', 'c', 1) g.add_edge('a', 'd', 2) g.add_edge('b', 'e', 5) g.add_edge('b', 'f', 1) g.add_edge('c', 'b', 2) g.add_edge('c', 'd', 3) g.add_edge('d', 'f', 2) g.add_edge('e', 'h', 2) g.add_edge('f', 'g', 1) g.add_edge('g', 'h', 2) g.compute_cost('h') assert g.cost('e') == 2 assert g.cost('f') == 3 assert g.cost('g') == 2 assert g.cost('b') == 4 assert g.cost('c') == 6 assert g.cost('d') == 5 assert g.cost('a') == 6 ### Let's test the paths now. # 5 points g = PricedGraph() g.add_edge('a', 'b', 1) assert g.cheapest_path('a', 'a') == ['a'] assert g.cheapest_path('a', 'b') == ['a', 'b'] assert g.cheapest_path('b', 'b') == ['b'] assert g.cheapest_path('b', 'a') == None # 5 points g = PricedGraph() g.add_edge('a', 'b', 2) g.add_edge('b', 'c', 3) g.add_edge('a', 'c', 6) g.add_edge('d', 'a', 4) assert g.cheapest_path('a', 'c') == ['a', 'b', 'c'] assert g.cheapest_path('d', 'c') == ['d', 'a', 'b', 'c'] assert g.cheapest_path('d', 'b') == ['d', 'a', 'b'] g = PricedGraph() g.add_edge('a', 'b', 2) g.add_edge('b', 'c', 3) g.add_edge('a', 'c', 4) g.add_edge('d', 'a', 4) assert g.cheapest_path('a', 'c') == ['a', 'c'] assert g.cheapest_path('d', 'c') == ['d', 'a', 'c'] # 3 points g = PricedGraph() g.add_edge('a', 'b', 2) g.add_edge('b', 'c', 3) g.add_edge('a', 'd', 1) g.add_edge('d', 'a', 3) assert g.cheapest_path('a', 'a') == ['a'] assert g.cheapest_path('d', 'd') == ['d'] assert g.cheapest_path('a', 'd') == ['a', 'd'] assert g.cheapest_path('a', 'c') == ['a', 'b', 'c'] assert g.cheapest_path('c', 'a') == None # 5 points # Let's do the example from class. g = PricedGraph() g.add_edge('a', 'b', 2) g.add_edge('a', 'c', 1) g.add_edge('a', 'd', 2) g.add_edge('b', 'e', 5) g.add_edge('b', 'f', 1) g.add_edge('c', 'b', 2) g.add_edge('c', 'd', 3) g.add_edge('d', 'f', 2) g.add_edge('e', 'h', 2) g.add_edge('f', 'g', 1) g.add_edge('g', 'h', 2) assert g.cheapest_path('a', 'h') == ['a', 'b', 'f', 'g', 'h'] assert g.cheapest_path('c', 'h') == ['c', 'b', 'f', 'g', 'h'] assert g.cheapest_path('d', 'h') == ['d', 'f', 'g', 'h'] # 5 points ## Note that nodes can be anything. g = PricedGraph() g.add_edge(0, 1, 2) g.add_edge(1, 2, 3) g.add_edge(2, (3, 4), 4) g.add_edge((3, 4), 4, 1) assert g.cheapest_path(0, 4) == [0, 1, 2, (3, 4), 4] ## DO NOT MODIFY THE NOTEBOOK BELOW THIS LINE ## And now for some hidden tests. # 5 points # Hidden tests. Do not remove this cell. # 5 points # Hidden tests. Do not remove this cell. # 5 points # Hidden tests. Do not remove this cell. # 5 points # Hidden tests. Do not remove this cell. # 5 points # Hidden tests. Do not remove this cell. # 5 points