feat(curriculum): add shortest path algorithm workshop (#61147)

Co-authored-by: Ilenia <26656284+ilenia-magoni@users.noreply.github.com>
Co-authored-by: Dario <105294544+Dario-DC@users.noreply.github.com>
Co-authored-by: Ilenia M <nethleen@gmail.com>
This commit is contained in:
Hillary Nyakundi
2025-11-06 15:25:59 +03:00
committed by GitHub
parent ea42bfc6e5
commit c7a4138c18
31 changed files with 2367 additions and 2 deletions
+4 -2
View File
@@ -4787,8 +4787,10 @@
]
},
"workshop-shortest-path-algorithm": {
"title": "Build a Shortest Path Algorithm",
"intro": [""]
"title": "Implement the Shortest Path Algorithm",
"intro": [
"In this workshop you will implement the shortest path algorithm to find the shortest path between two nodes in a graph."
]
},
"lab-adjacency-list-to-matrix-converter": {
"title": "Build an Adjacency List to Matrix Converter",
@@ -0,0 +1,8 @@
---
title: Introduction to the Implement the Shortest Path Algorithm
block: workshop-shortest-path-algorithm
---
## Introduction to the Implement the Shortest Path Algorithm
In this workshop, you will learn how to implement the shortest path algorithm using Dijkstra's algorithm to find the shortest path between two nodes in a graph.
@@ -0,0 +1,50 @@
---
id: 6853e58d52ec64349a5760ca
title: Step 1
challengeType: 20
dashedName: step-1
---
# --description--
In this workshop, you will implement the shortest path algorithm. You will write a Python function that computes the shortest path between the nodes in a graph, and also returns the path taken.
For example, given a graph where cities are connected by roads with different distances, the algorithm will find the shortest route from one city to another. If you want to travel from City A to City D, the algorithm might find that going A ⇨ B ⇨ C ⇨ D (total: 15km) is shorter than going directly A ⇨ D (20km).
To get started, define a variable named `INF` and assign it the value `float('inf')`, which represents positive infinity. Later, you'll use it to indicate an infinite distance between two nodes.
# --hints--
You should define the variable `INF` at the top of your file.
```js
({
test: () => {
assert(runPython(`_Node(_code).has_variable("INF")`));
}
});
```
You should assign `float('inf')` to the variable `INF`.
```js
({
test: () => {
assert(
runPython(
`_Node(_code).find_variable("INF").is_equivalent("INF = float('inf')")`
)
);
}
});
```
# --seed--
## --seed-contents--
```py
--fcc-editable-region--
--fcc-editable-region--
```
@@ -0,0 +1,58 @@
---
id: 686251fb49ebc489ba60ccb5
title: Step 2
challengeType: 20
dashedName: step-2
---
# --description--
You will need to create a 2D list to represent the adjacency matrix of the graph. This matrix will be used to represent the weights of the edges between nodes in the graph.
Create a variable named `adj_matrix` and assign it a 2D list representing the graph with the following weights:
```py
[0, 5, 3, INF, 11, INF],
[5, 0, 1, INF, INF, 2],
[3, 1, 0, 1, 5, INF],
[INF, INF, 1, 0, 9, 3],
[11, INF, 5, 9, 0, INF],
[INF, 2, INF, 3, INF, 0]
```
# --hints--
You should create a variable named `adj_matrix`.
```js
({
test: () => {
assert(runPython(`_Node(_code).has_variable("adj_matrix")`));
}
});
```
`adj_matrix` should be a 2D list containing the provided values.
```js
({
test: () => {
assert(
runPython(
`_Node(_code).find_variable("adj_matrix").is_equivalent("adj_matrix = [[0, 5, 3, INF, 11, INF], [5, 0, 1, INF, INF, 2], [3, 1, 0, 1, 5, INF], [INF, INF, 1, 0, 9, 3], [11, INF, 5, 9, 0, INF], [INF, 2, INF, 3, INF, 0]]")`
)
);
}
});
```
# --seed--
## --seed-contents--
```py
INF = float('inf')
--fcc-editable-region--
--fcc-editable-region--
```
@@ -0,0 +1,62 @@
---
id: 686253f1b051998ad4904e3e
title: Step 3
challengeType: 20
dashedName: step-3
---
# --description--
You will now create the main function that accepts three parameters: the adjacency matrix, the starting node, and an optional target node.
Create a function named `shortest_path` that takes in three parameters: `matrix`, `start_node`, and `target_node`. Assign `None` as the default value for `target_node`.
The `target_node` parameter is set to `None` by default, indicating that if no target node is specified, the function should compute the shortest paths from the starting node to all other nodes in the graph.
Add a `pass` statement inside the function body for now.
# --hints--
You should create a function named `shortest_path`.
```js
({
test: () => {
assert(runPython(`_Node(_code).has_function("shortest_path")`));
}
});
```
The function should take in three parameters: `matrix`, `start_node`, and `target_node` with `target_node` having a default value of `None`.
```js
({
test: () => {
assert(
runPython(
`_Node(_code).find_function("shortest_path").has_args("matrix, start_node, target_node=None")`
)
);
}
});
```
# --seed--
## --seed-contents--
```py
INF = float('inf')
adj_matrix = [
[0, 5, 3, INF, 11, INF],
[5, 0, 1, INF, INF, 2],
[3, 1, 0, 1, 5, INF],
[INF, INF, 1, 0, 9, 3],
[11, INF, 5, 9, 0, INF],
[INF, 2, INF, 3, INF, 0],
]
--fcc-editable-region--
--fcc-editable-region--
```
@@ -0,0 +1,50 @@
---
id: 68625ad34aa3f58d7f3b9b07
title: Step 4
challengeType: 20
dashedName: step-4
---
# --description--
You need to store the number of nodes in the graph. For this you will need a variable that matches the length of the adjacency matrix.
Inside the `shortest_path` function, create a variable `n` and set it to the length of the `matrix`.
# --hints--
You should initialize `n` to the length of the `matrix`.
```js
({
test: () => {
assert(
runPython(
`_Node(_code).find_function("shortest_path").find_variable("n").is_equivalent("n = len(matrix)")`
)
);
}
});
```
# --seed--
## --seed-contents--
```py
INF = float('inf')
adj_matrix = [
[0, 5, 3, INF, 11, INF],
[5, 0, 1, INF, INF, 2],
[3, 1, 0, 1, 5, INF],
[INF, INF, 1, 0, 9, 3],
[11, INF, 5, 9, 0, INF],
[INF, 2, INF, 3, INF, 0],
]
--fcc-editable-region--
def shortest_path(matrix, start_node, target_node=None):
pass
--fcc-editable-region--
```
@@ -0,0 +1,65 @@
---
id: 686b73007fc582db0132c361
title: Step 5
challengeType: 20
dashedName: step-5
---
# --description--
You need to keep track of the shortest known distance from the start node to every other node in the graph.
To do this, create a variable named `distances` and initialize it as a list containing a single element: `INF`.
# --hints--
You should create a variable named `distances`.
```js
({
test: () => {
assert(
runPython(
`_Node(_code).find_function("shortest_path").has_variable("distances")`
)
);
}
});
```
The `distances` list should be initialized to `[INF]`.
```js
({
test: () => {
assert(
runPython(
`_Node(_code).find_function("shortest_path").find_variable("distances").is_equivalent("distances = [INF]")`
)
);
}
});
```
# --seed--
## --seed-contents--
```py
INF = float('inf')
adj_matrix = [
[0, 5, 3, INF, 11, INF],
[5, 0, 1, INF, INF, 2],
[3, 1, 0, 1, 5, INF],
[INF, INF, 1, 0, 9, 3],
[11, INF, 5, 9, 0, INF],
[INF, 2, INF, 3, INF, 0],
]
--fcc-editable-region--
def shortest_path(matrix, start_node, target_node=None):
n = len(matrix)
--fcc-editable-region--
```
@@ -0,0 +1,51 @@
---
id: 686b7848fbaa41e0c2a7efef
title: Step 7
challengeType: 20
dashedName: step-7
---
# --description--
Now that you have your `distances` list initialized, you need to update the distance for the starting node.
Since the distance from the starting node to itself is always `0`, set the value at the `start_node` index in the `distances` list to `0`.
# --hints--
You should set `distances[start_node]` to `0`.
```js
({
test: () => {
assert(
runPython(
`_Node(_code).find_function("shortest_path").has_stmt("distances[start_node] = 0")`
)
);
}
});
```
# --seed--
## --seed-contents--
```py
INF = float('inf')
adj_matrix = [
[0, 5, 3, INF, 11, INF],
[5, 0, 1, INF, INF, 2],
[3, 1, 0, 1, 5, INF],
[INF, INF, 1, 0, 9, 3],
[11, INF, 5, 9, 0, INF],
[INF, 2, INF, 3, INF, 0],
]
--fcc-editable-region--
def shortest_path(matrix, start_node, target_node=None):
n = len(matrix)
distances = [INF] * n
--fcc-editable-region--
```
@@ -0,0 +1,102 @@
---
id: 686b78bc13d518e1267448e4
title: Step 8
challengeType: 20
dashedName: step-8
---
# --description--
In addition to tracking distances, you also need to keep track of the actual paths taken to reach each node.
You'll create a list where each entry stores the path taken to reach that node. Initially, each node's path will just contain itself.
List comprehensions provide a concise way to create lists. For example:
```py
[x * 2 for x in range(3)]
```
Create a variable named `paths` and initialize it using a list comprehension that creates a list containing `[node_no]` for each `node_no` in `range(n)`.
# --hints--
You should have a variable called `paths`.
```js
({
test: () => {
assert(
runPython(
`_Node(_code).find_function("shortest_path").has_variable("paths")`
)
);
}
});
```
You should initialize `paths` using a list comprehension.
```js
({
test: () => {
runPython(`
import ast
assert isinstance(_Node(_code).find_function("shortest_path").find_variable("paths").tree.value, ast.ListComp)
`);
}
});
```
Your list comprehension should use `node_no` to iterate over `range(n)`.
```js
({
test: () => {
runPython(`
v = _Node(_code).find_function("shortest_path").find_variable("paths")
assert len(v.find_comp_targets()) == 1
assert v.find_comp_targets()[0].is_equivalent("node_no")
assert len(v.find_comp_iters()) == 1
assert v.find_comp_iters()[0].is_equivalent("range(n)")
`);
}
});
```
Your list comprehension should evaluate `[node_no]` for each `node_no` in `range(n)`.
```js
({
test: () => {
runPython(`
v = _Node(_code).find_function("shortest_path").find_variable("paths")
assert v.find_comp_expr().is_equivalent("[node_no]")
`);
}
});
```
# --seed--
## --seed-contents--
```py
INF = float('inf')
adj_matrix = [
[0, 5, 3, INF, 11, INF],
[5, 0, 1, INF, INF, 2],
[3, 1, 0, 1, 5, INF],
[INF, INF, 1, 0, 9, 3],
[11, INF, 5, 9, 0, INF],
[INF, 2, INF, 3, INF, 0],
]
--fcc-editable-region--
def shortest_path(matrix, start_node, target_node=None):
n = len(matrix)
distances = [INF] * n
distances[start_node] = 0
--fcc-editable-region--
```
@@ -0,0 +1,67 @@
---
id: 686b82af323098e7a5203bf4
title: Step 9
challengeType: 20
dashedName: step-9
---
# --description--
As the algorithm runs, you need to keep track of which nodes you've already visited, so you don't process them more than once.
To do this, create a list named `visited` and initialize it with `False` for every node.
# --hints--
You should have a list named `visited` inside the `shortest_path` function.
```js
({
test: () => {
assert(
runPython(
`_Node(_code).find_function("shortest_path").has_variable("visited")`
)
);
}
});
```
You should initialize the `visited` list with `False` for every node using `[False] * n`.
```js
({
test: () => {
assert(
runPython(
`_Node(_code).find_function("shortest_path").find_variable("visited").is_equivalent("visited = [False] * n")`
)
);
}
});
```
# --seed--
## --seed-contents--
```py
INF = float('inf')
adj_matrix = [
[0, 5, 3, INF, 11, INF],
[5, 0, 1, INF, INF, 2],
[3, 1, 0, 1, 5, INF],
[INF, INF, 1, 0, 9, 3],
[11, INF, 5, 9, 0, INF],
[INF, 2, INF, 3, INF, 0],
]
--fcc-editable-region--
def shortest_path(matrix, start_node, target_node=None):
n = len(matrix)
distances = [INF] * n
distances[start_node] = 0
paths = [[node_no] for node_no in range(n)]
--fcc-editable-region--
```
@@ -0,0 +1,117 @@
---
id: 68760464eb9c2e79cc913fac
title: Step 10
challengeType: 20
dashedName: step-10
---
# --description--
In this step, you will add a loop that will run once for each node in the graph. This loop will allow the algorithm to update distances and paths over multiple passes.
Create a `for` loop that runs `n` times. Use `_` as the loop variable since you don't need to use the iteration value.
Inside the loop, you need to prepare for selecting the next node to process by creating two variables:
- one to hold the smallest distance found so far in the current iteration
- and another to store the index of the node that has this smallest distance.
Create variables `min_distance` and `current`, and set them to `INF` and `-1`, respectively.
# --hints--
You should have a `for` loop within the `shortest_path` function.
```js
({
test: () => {
assert(
runPython(
`_Node(_code).find_function("shortest_path").find_for_loops()[0]`
)
);
}
});
```
Your `for` loop should iterate over `range(n)`.
```js
({
test: () => {
assert(
runPython(
`_Node(_code).find_function("shortest_path").find_for_loops()[0].find_for_iter().is_equivalent("range(n)")`
)
);
}
});
```
Your for loop should use `_` as the iteration variable.
```js
({
test: () => {
assert(
runPython(
`_Node(_code).find_function("shortest_path").find_for_loops()[0].find_for_vars().is_equivalent("_")`
)
);
}
});
```
You should create a variable named `min_distance` and set it to `INF` inside the for loop.
```js
({
test: () => {
assert(
runPython(
`_Node(_code).find_function("shortest_path").find_for_loops()[0].find_bodies()[0].has_stmt("min_distance = INF")`
)
);
}
});
```
You should create a variable named `current` and set it to `-1` inside the for loop.
```js
({
test: () => {
assert(
runPython(
`_Node(_code).find_function("shortest_path").find_for_loops()[0].find_bodies()[0].has_stmt("current = -1")`
)
);
}
});
```
# --seed--
## --seed-contents--
```py
INF = float('inf')
adj_matrix = [
[0, 5, 3, INF, 11, INF],
[5, 0, 1, INF, INF, 2],
[3, 1, 0, 1, 5, INF],
[INF, INF, 1, 0, 9, 3],
[11, INF, 5, 9, 0, INF],
[INF, 2, INF, 3, INF, 0],
]
def shortest_path(matrix, start_node, target_node=None):
n = len(matrix)
distances = [INF] * n
distances[start_node] = 0
paths = [[node_no] for node_no in range(n)]
visited = [False] * n
--fcc-editable-region--
--fcc-editable-region--
```
@@ -0,0 +1,78 @@
---
id: 68760671d950767b055bf2f9
title: Step 11
challengeType: 20
dashedName: step-11
---
# --description--
Now you need to check every node to find the one with the smallest known distance that has not been visited yet.
To do this, add a `for` loop inside the main loop.
The loop should iterate through each `node_no` in `range(n)`, where `n` is the number of nodes in the graph. Add `pass` as the body of the loop for now.
# --hints--
You should have a second `for` loop within the `shortest_path` function.
```js
({
test: () => {
assert(
runPython(`
func = _Node(_code).find_function("shortest_path")
outer_loop = func.find_for_loops()[0]
len(outer_loop.find_bodies()[0].find_for_loops()) >= 1
`)
);
}
});
```
Your `for` loop should iterate over `range(n)` with `node_no` as the iteration variable.
```js
({
test: () => {
runPython(`
func = _Node(_code).find_function("shortest_path")
outer_loop = func.find_for_loops()[0]
inner_loop = outer_loop.find_bodies()[0].find_for_loops()[0]
assert inner_loop.find_for_vars().is_equivalent("node_no")
assert inner_loop.find_for_iter().is_equivalent("range(n)")
`);
}
});
```
# --seed--
## --seed-contents--
```py
INF = float('inf')
adj_matrix = [
[0, 5, 3, INF, 11, INF],
[5, 0, 1, INF, INF, 2],
[3, 1, 0, 1, 5, INF],
[INF, INF, 1, 0, 9, 3],
[11, INF, 5, 9, 0, INF],
[INF, 2, INF, 3, INF, 0],
]
def shortest_path(matrix, start_node, target_node=None):
n = len(matrix)
distances = [INF] * n
distances[start_node] = 0
paths = [[node_no] for node_no in range(n)]
visited = [False] * n
--fcc-editable-region--
for _ in range(n):
min_distance = INF
current = -1
--fcc-editable-region--
```
@@ -0,0 +1,81 @@
---
id: 6876089f7a500b7c782e5b83
title: Step 12
challengeType: 20
dashedName: step-12
---
# --description--
You need to decide whether the current node is a better choice than the one you've already found (if any). To do this, you'll add a conditional statement inside the loop.
The condition should do two things:
- Check if the node has not been visited yet.
- Compare the known distance to the current `min_distance`.
Inside the inner `for` loop, add an `if` statement that checks whether `node_no` has not been visited **and** whether `distances[node_no]` is less than `min_distance`.
Add `pass` as a placeholder inside the conditional block.
# --hints--
You should add an `if` statement inside your inner `for` loop.
```js
({
test: () => {
assert(
runPython(
`_Node(_code).find_function("shortest_path").find_for_loops()[0].find_bodies()[0].find_for_loops()[0].find_bodies()[0].find_ifs()[0]`
)
);
}
});
```
Your `if` statement should check if the node is unvisited (`visited[node_no]` is falsy) and `distances[node_no]` is less than `min_distance`.
```js
({
test: () => {
assert(
runPython(
`_Node(_code).find_function("shortest_path").find_for_loops()[0].find_bodies()[0].find_for_loops()[0].find_bodies()[0].find_ifs()[0].find_conditions()[0].is_equivalent("not visited[node_no] and distances[node_no] < min_distance")`
)
);
}
});
```
# --seed--
## --seed-contents--
```py
INF = float('inf')
adj_matrix = [
[0, 5, 3, INF, 11, INF],
[5, 0, 1, INF, INF, 2],
[3, 1, 0, 1, 5, INF],
[INF, INF, 1, 0, 9, 3],
[11, INF, 5, 9, 0, INF],
[INF, 2, INF, 3, INF, 0],
]
def shortest_path(matrix, start_node, target_node=None):
n = len(matrix)
distances = [INF] * n
distances[start_node] = 0
paths = [[node_no] for node_no in range(n)]
visited = [False] * n
--fcc-editable-region--
for _ in range(n):
min_distance = INF
current = -1
for node_no in range(n):
pass
--fcc-editable-region--
```
@@ -0,0 +1,75 @@
---
id: 68760b53e250f982473e1808
title: Step 13
challengeType: 20
dashedName: step-13
---
# --description--
If the conditional you just added is true, that means the current node is the best unvisited option you've found so far and you need to update your variables to reflect that.
Inside the `if` block, update `min_distance` to `distances[node_no]` and set `current` to `node_no`.
# --hints--
You should update the `min_distance` variable to `distances[node_no]`.
```js
({
test: () => {
assert(
runPython(
`_Node(_code).find_function("shortest_path").find_for_loops()[0].find_bodies()[0].find_for_loops()[0].find_bodies()[0].find_ifs()[0].find_bodies()[0].has_stmt("min_distance = distances[node_no]")`
)
);
}
});
```
You should set `current` to `node_no`.
```js
({
test: () => {
assert(
runPython(
`_Node(_code).find_function("shortest_path").find_for_loops()[0].find_bodies()[0].find_for_loops()[0].find_bodies()[0].find_ifs()[0].find_bodies()[0].has_stmt("current = node_no")`
)
);
}
});
```
# --seed--
## --seed-contents--
```py
INF = float('inf')
adj_matrix = [
[0, 5, 3, INF, 11, INF],
[5, 0, 1, INF, INF, 2],
[3, 1, 0, 1, 5, INF],
[INF, INF, 1, 0, 9, 3],
[11, INF, 5, 9, 0, INF],
[INF, 2, INF, 3, INF, 0],
]
def shortest_path(matrix, start_node, target_node=None):
n = len(matrix)
distances = [INF] * n
distances[start_node] = 0
paths = [[node_no] for node_no in range(n)]
visited = [False] * n
--fcc-editable-region--
for _ in range(n):
min_distance = INF
current = -1
for node_no in range(n):
if not visited[node_no] and distances[node_no] < min_distance:
pass
--fcc-editable-region--
```
@@ -0,0 +1,76 @@
---
id: 68760c4c2648e7832c5eef16
title: Step 14
challengeType: 20
dashedName: step-14
---
# --description--
After the loop that finds the nearest unvisited node, you need to check whether a valid node was actually found.
If no such node exists, that means the remaining nodes are unreachable from the start node, and the algorithm should stop early.
On the same level as the nested `for` loop, add an `if` statement that checks if `current == -1` and breaks out of the loop if true.
# --hints--
You should have an `if` statement that checks if `current` is still `-1` after the inner `for` loop.
```js
({
test: () => {
runPython(`
cond = _Node(_code).find_function("shortest_path").find_for_loops()[0].find_bodies()[0].find_ifs()[0].find_conditions()[0]
assert cond.is_equivalent("current == -1") or cond.is_equivalent("-1 == current")
`);
}
});
```
You should use a `break` statement to break out of the loop.
```js
({
test: () => {
runPython(`
assert _Node(_code).find_function("shortest_path").find_for_loops()[0].find_bodies()[0].find_ifs()[0].find_bodies()[0].has_stmt("break")
`);
}
});
```
# --seed--
## --seed-contents--
```py
INF = float('inf')
adj_matrix = [
[0, 5, 3, INF, 11, INF],
[5, 0, 1, INF, INF, 2],
[3, 1, 0, 1, 5, INF],
[INF, INF, 1, 0, 9, 3],
[11, INF, 5, 9, 0, INF],
[INF, 2, INF, 3, INF, 0],
]
def shortest_path(matrix, start_node, target_node=None):
n = len(matrix)
distances = [INF] * n
distances[start_node] = 0
paths = [[node_no] for node_no in range(n)]
visited = [False] * n
for _ in range(n):
min_distance = INF
current = -1
for node_no in range(n):
if not visited[node_no] and distances[node_no] < min_distance:
min_distance = distances[node_no]
current = node_no
--fcc-editable-region--
--fcc-editable-region--
```
@@ -0,0 +1,67 @@
---
id: 687774e17e0972345de2527a
title: Step 15
challengeType: 20
dashedName: step-15
---
# --description--
If a valid node was found in the pass, you need to mark it as visited so it won't be considered again in future iterations.
After the `if` statement you added in the previous step, set `visited[current]` to `True`.
# --hints--
You should set `visited[current]` to `True` after your last `if` statement.
```js
({
test: () => {
runPython(`
block = _Node(_code).find_function("shortest_path").find_for_loops()[0].find_bodies()[0]
if_stmt = """if current == -1:
break"""
target_stmt = "visited[current] = True"
assert block.is_ordered(if_stmt, target_stmt)
`);
}
});
```
# --seed--
## --seed-contents--
```py
INF = float('inf')
adj_matrix = [
[0, 5, 3, INF, 11, INF],
[5, 0, 1, INF, INF, 2],
[3, 1, 0, 1, 5, INF],
[INF, INF, 1, 0, 9, 3],
[11, INF, 5, 9, 0, INF],
[INF, 2, INF, 3, INF, 0],
]
def shortest_path(matrix, start_node, target_node=None):
n = len(matrix)
distances = [INF] * n
distances[start_node] = 0
paths = [[node_no] for node_no in range(n)]
visited = [False] * n
for _ in range(n):
min_distance = INF
current = -1
for node_no in range(n):
if not visited[node_no] and distances[node_no] < min_distance:
min_distance = distances[node_no]
current = node_no
--fcc-editable-region--
if current == -1:
break
--fcc-editable-region--
```
@@ -0,0 +1,115 @@
---
id: 687775bed85144353d298665
title: Step 16
challengeType: 20
dashedName: step-16
---
# --description--
Now that you've selected and marked a node as visited, it's time to look at all of its neighbors to see if you can find shorter paths to them.
After the line `visited[current] = True`, add a `for` loop that iterates through `node_no` in `range(n)`.
Inside this loop, create a variable `distance` and set it to `matrix[current][node_no]`. This will give you the distance from the current node to the neighbor node.
# --hints--
You should add a `for` loop after marking the current node as visited.
```js
({
test: () => {
assert(
runPython(`
func = _Node(_code).find_function("shortest_path")
outer_loop = func.find_for_loops()[0]
len(outer_loop.find_bodies()[0].find_for_loops()) >= 2
`)
);
}
});
```
The loop should iterate through `node_no` in `range(n)`.
```js
({
test: () => {
runPython(`
loop = _Node(_code).find_function("shortest_path").find_for_loops()[0].find_bodies()[0].find_for_loops()[1]
assert loop.find_for_vars().is_equivalent("node_no")
assert loop.find_for_iter().is_equivalent("range(n)")
`);
}
});
```
You should create a variable `distance` inside the loop.
```js
({
test: () => {
assert(
runPython(`
func = _Node(_code).find_function("shortest_path")
outer_loop = func.find_for_loops()[0]
neighbor_loop = outer_loop.find_bodies()[0].find_for_loops()[1]
neighbor_loop.find_bodies()[0].has_variable("distance")
`)
);
}
});
```
The variable `distance` should be set to `matrix[current][node_no]`.
```js
({
test: () => {
assert(
runPython(`_Node(_code).find_function("shortest_path").find_for_loops()[0].find_bodies()[0].find_for_loops()[1].find_bodies()[0].is_equivalent("distance = matrix[current][node_no]")
`)
);
}
});
```
# --seed--
## --seed-contents--
```py
INF = float('inf')
adj_matrix = [
[0, 5, 3, INF, 11, INF],
[5, 0, 1, INF, INF, 2],
[3, 1, 0, 1, 5, INF],
[INF, INF, 1, 0, 9, 3],
[11, INF, 5, 9, 0, INF],
[INF, 2, INF, 3, INF, 0],
]
def shortest_path(matrix, start_node, target_node=None):
n = len(matrix)
distances = [INF] * n
distances[start_node] = 0
paths = [[node_no] for node_no in range(n)]
visited = [False] * n
for _ in range(n):
min_distance = INF
current = -1
for node_no in range(n):
if not visited[node_no] and distances[node_no] < min_distance:
min_distance = distances[node_no]
current = node_no
if current == -1:
break
visited[current] = True
--fcc-editable-region--
--fcc-editable-region--
```
@@ -0,0 +1,101 @@
---
id: 68777816e95d4936f129819f
title: Step 17
challengeType: 20
dashedName: step-17
---
# --description--
Before trying to update the distance to a neighbor, you need to verify that the neighbor is both reachable and unvisited. Then you'll calculate what the total distance would be to reach that neighbor through the current node.
Inside the `for` loop, add an `if` statement that checks:
- The `distance` is not equal to `INF` (meaning there's an edge between the nodes)
- The neighbor `node_no` has not been visited yet
Inside the `if` block, create a variable named `new_distance` and assign it the sum of `distances[current]` (the shortest distance to the current node) and `distance` (the distance from the current node to the neighbor).
# --hints--
You should have an `if` statement that checks if `distance` is not equal to `INF` and if the neighbor node has not been visited.
```js
({
test: () => {
assert(
runPython(`_Node(_code).find_function("shortest_path").find_for_loops()[0].find_bodies()[0].find_for_loops()[1].find_bodies()[0].find_ifs()[0].find_conditions()[0].is_equivalent("distance != INF and not visited[node_no]")
`)
);
}
});
```
You should create a variable named `new_distance` inside the `if` block.
```js
({
test: () => {
assert(
runPython(
`_Node(_code).find_function("shortest_path").find_for_loops()[0].find_bodies()[0].find_for_loops()[1].find_bodies()[0].find_ifs()[0].find_bodies()[0].has_variable("new_distance")`
)
);
}
});
```
You should assign `new_distance` the sum of `distances[current]` and `distance`.
```js
({
test: () => {
assert(
runPython(
`_Node(_code).find_function("shortest_path").find_for_loops()[0].find_bodies()[0].find_for_loops()[1].find_bodies()[0].find_ifs()[0].find_bodies()[0].find_variable("new_distance").is_equivalent("new_distance = distances[current] + distance")`
)
);
}
});
```
# --seed--
## --seed-contents--
```py
INF = float('inf')
adj_matrix = [
[0, 5, 3, INF, 11, INF],
[5, 0, 1, INF, INF, 2],
[3, 1, 0, 1, 5, INF],
[INF, INF, 1, 0, 9, 3],
[11, INF, 5, 9, 0, INF],
[INF, 2, INF, 3, INF, 0],
]
def shortest_path(matrix, start_node, target_node=None):
n = len(matrix)
distances = [INF] * n
distances[start_node] = 0
paths = [[node_no] for node_no in range(n)]
visited = [False] * n
for _ in range(n):
min_distance = INF
current = -1
for node_no in range(n):
if not visited[node_no] and distances[node_no] < min_distance:
min_distance = distances[node_no]
current = node_no
if current == -1:
break
visited[current] = True
--fcc-editable-region--
for node_no in range(n):
distance = matrix[current][node_no]
--fcc-editable-region--
```
@@ -0,0 +1,85 @@
---
id: 687b3926419dec576850b814
title: Step 18
challengeType: 20
dashedName: step-18
---
# --description--
Now that you've calculated the new possible distance to the neighbor, check if it's better than the one currently stored in the `distances` list. If it is, update the distance.
Inside the existing `if` block, add an `if` statement that checks if `new_distance` is less than `distances[node_no]`.
Inside this new conditional block, update `distances[node_no]` to store the `new_distance`.
# --hints--
You should have a nested `if` statement that checks if `new_distance` is less than `distances[node_no]`.
```js
({
test: () => {
runPython(`
cond = _Node(_code).find_function("shortest_path").find_for_loops()[0].find_bodies()[0].find_for_loops()[1].find_bodies()[0].find_ifs()[0].find_bodies()[0].find_ifs()[0].find_conditions()[0]
assert cond.is_equivalent("new_distance < distances[node_no]") or cond.is_equivalent("distances[node_no] > new_distance")
`);
}
});
```
You should update `distances[node_no]` to store the `new_distance`.
```js
({
test: () => {
assert(
runPython(`_Node(_code).find_function("shortest_path").find_for_loops()[0].find_bodies()[0].find_for_loops()[1].find_bodies()[0].find_ifs()[0].find_bodies()[0].find_ifs()[0].find_bodies()[0].has_stmt("distances[node_no] = new_distance")
`)
);
}
});
```
# --seed--
## --seed-contents--
```py
INF = float('inf')
adj_matrix = [
[0, 5, 3, INF, 11, INF],
[5, 0, 1, INF, INF, 2],
[3, 1, 0, 1, 5, INF],
[INF, INF, 1, 0, 9, 3],
[11, INF, 5, 9, 0, INF],
[INF, 2, INF, 3, INF, 0],
]
def shortest_path(matrix, start_node, target_node=None):
n = len(matrix)
distances = [INF] * n
distances[start_node] = 0
paths = [[node_no] for node_no in range(n)]
visited = [False] * n
for _ in range(n):
min_distance = INF
current = -1
for node_no in range(n):
if not visited[node_no] and distances[node_no] < min_distance:
min_distance = distances[node_no]
current = node_no
if current == -1:
break
visited[current] = True
--fcc-editable-region--
for node_no in range(n):
distance = matrix[current][node_no]
if distance != INF and not visited[node_no]:
new_distance = distances[current] + distance
--fcc-editable-region--
```
@@ -0,0 +1,75 @@
---
id: 687b3c7a3af0df59b363712e
title: Step 19
challengeType: 20
dashedName: step-19
---
# --description--
When you find a shorter path to a node, you also need to update the actual path taken to reach it.
Inside the same conditional block, update the `paths` list at the neighbor's index to reflect the new, shorter path.
You should assign `paths[node_no]` to be the current path to the `current` node, with the `node_no` (the neighbor) added at the end.
# --hints--
You should update `paths[node_no]` to be the current path plus the neighbor node.
```js
({
test: () => {
assert(
runPython(
`_Node(_code).find_function("shortest_path").find_for_loops()[0].find_bodies()[0].find_for_loops()[1].find_bodies()[0].find_ifs()[0].find_bodies()[0].find_ifs()[0].find_bodies()[0].has_stmt("paths[node_no] = paths[current] + [node_no]")`
)
);
}
});
```
# --seed--
## --seed-contents--
```py
INF = float('inf')
adj_matrix = [
[0, 5, 3, INF, 11, INF],
[5, 0, 1, INF, INF, 2],
[3, 1, 0, 1, 5, INF],
[INF, INF, 1, 0, 9, 3],
[11, INF, 5, 9, 0, INF],
[INF, 2, INF, 3, INF, 0],
]
def shortest_path(matrix, start_node, target_node=None):
n = len(matrix)
distances = [INF] * n
distances[start_node] = 0
paths = [[node_no] for node_no in range(n)]
visited = [False] * n
for _ in range(n):
min_distance = INF
current = -1
for node_no in range(n):
if not visited[node_no] and distances[node_no] < min_distance:
min_distance = distances[node_no]
current = node_no
if current == -1:
break
visited[current] = True
--fcc-editable-region--
for node_no in range(n):
distance = matrix[current][node_no]
if distance != INF and not visited[node_no]:
new_distance = distances[current] + distance
if new_distance < distances[node_no]:
distances[node_no] = new_distance
--fcc-editable-region--
```
@@ -0,0 +1,119 @@
---
id: 687b3ef0c2fe185b2abc4654
title: Step 20
challengeType: 20
dashedName: step-20
---
# --description--
Once the algorithm has finished running, you need to decide which node(s) to display results for.
If a specific `target_node` was provided, you'll only show the distance and path to that node. Otherwise, you'll show results for all nodes.
After the outer `for _ in range(n):` loop ends, create a variable named `targets`. Use a conditional expression to assign it `[target_node]` if `target_node` is not `None`, otherwise assign it `list(range(n))`.
# --hints--
You should create a variable named `targets`.
```js
({
test: () => {
assert(
runPython(
`_Node(_code).find_function("shortest_path").has_variable("targets")`
)
);
}
});
```
Your `targets` variable should contain a conditional expression.
```js
({
test: () => {
runPython(`
import ast
val =_Node(_code).find_function("shortest_path").find_variable("targets").tree.value
assert isinstance(val, ast.IfExp)
`);
}
});
```
The conditional expression should check if `target_node is not None`.
```js
({
test: () => {
runPython(`
compare = _Node(_Node(_code).find_function("shortest_path").find_variable("targets").tree.value.test)
assert compare.is_equivalent("target_node is not None")
`);
}
});
```
You should assign `[target_node]` when a target is provided and `range(n)` otherwise.
```js
({
test: () => {
runPython(`
import ast
val = _Node(_code).find_function("shortest_path").find_variable("targets").tree.value
assert _Node(val.body).is_equivalent("[target_node]")
assert _Node(val.orelse).is_equivalent("range(n)")
`);
}
});
```
# --seed--
## --seed-contents--
```py
INF = float('inf')
adj_matrix = [
[0, 5, 3, INF, 11, INF],
[5, 0, 1, INF, INF, 2],
[3, 1, 0, 1, 5, INF],
[INF, INF, 1, 0, 9, 3],
[11, INF, 5, 9, 0, INF],
[INF, 2, INF, 3, INF, 0],
]
def shortest_path(matrix, start_node, target_node=None):
n = len(matrix)
distances = [INF] * n
distances[start_node] = 0
paths = [[node_no] for node_no in range(n)]
visited = [False] * n
for _ in range(n):
min_distance = INF
current = -1
for node_no in range(n):
if not visited[node_no] and distances[node_no] < min_distance:
min_distance = distances[node_no]
current = node_no
if current == -1:
break
visited[current] = True
for node_no in range(n):
distance = matrix[current][node_no]
if distance != INF and not visited[node_no]:
new_distance = distances[current] + distance
if new_distance < distances[node_no]:
distances[node_no] = new_distance
paths[node_no] = paths[current] + [node_no]
--fcc-editable-region--
--fcc-editable-region--
```
@@ -0,0 +1,76 @@
---
id: 687b426c3dabfe5cf80fad80
title: Step 21
challengeType: 20
dashedName: step-21
---
# --description--
Now that you've defined which nodes you want to display results for, you need to loop through them.
Add a `for` loop that iterates through each `node_no` in the `targets` list. Add `pass` as a placeholder inside the loop body.
# --hints--
You should create a `for` loop that uses `node_no` to iterate over `targets`.
```js
({
test: () => {
runPython(`
loop = _Node(_code).find_function("shortest_path").find_for_loops()[1]
assert loop.find_for_iter().is_equivalent("targets")
assert loop.find_for_vars().is_equivalent("node_no")
`);
}
});
```
# --seed--
## --seed-contents--
```py
INF = float('inf')
adj_matrix = [
[0, 5, 3, INF, 11, INF],
[5, 0, 1, INF, INF, 2],
[3, 1, 0, 1, 5, INF],
[INF, INF, 1, 0, 9, 3],
[11, INF, 5, 9, 0, INF],
[INF, 2, INF, 3, INF, 0],
]
def shortest_path(matrix, start_node, target_node=None):
n = len(matrix)
distances = [INF] * n
distances[start_node] = 0
paths = [[node_no] for node_no in range(n)]
visited = [False] * n
for _ in range(n):
min_distance = INF
current = -1
for node_no in range(n):
if not visited[node_no] and distances[node_no] < min_distance:
min_distance = distances[node_no]
current = node_no
if current == -1:
break
visited[current] = True
for node_no in range(n):
distance = matrix[current][node_no]
if distance != INF and not visited[node_no]:
new_distance = distances[current] + distance
if new_distance < distances[node_no]:
distances[node_no] = new_distance
paths[node_no] = paths[current] + [node_no]
--fcc-editable-region--
targets = [target_node] if target_node is not None else range(n)
--fcc-editable-region--
```
@@ -0,0 +1,118 @@
---
id: 687b43c0e20d695e0b8cca5b
title: Step 22
challengeType: 20
dashedName: step-22
---
# --description--
At this point, you only want to display results for nodes that are not the start node and are reachable from it.
Add a conditional that checks if `node_no` equals `start_node` **or** if `distances[node_no]` equals `INF`.
If either condition is true, use `continue` to skip to the next iteration of the loop.
# --hints--
You should add a conditional statement inside the loop.
```js
({
test: () => {
runPython(`
func = _Node(_code).find_function("shortest_path")
last_loop = func.find_for_loops()[-1]
assert last_loop.find_bodies()[0].find_ifs()[0]
`);
}
});
```
Your `if` statement should check if `node_no` is equal to `start_node` *or* if `distances[node_no]` is equal to `INF`.
```js
({
test: () => {
runPython(`
func = _Node(_code).find_function("shortest_path")
if_cond = func.find_for_loops()[-1].find_bodies()[0].find_ifs()[0].find_conditions()[0]
conditions = [
'node_no == start_node or distances[node_no] == INF',
'start_node == node_no or distances[node_no] == INF',
'start_node == node_no or INF == distances[node_no]',
'node_no == start_node or INF == distances[node_no]',
'distances[node_no] == INF or node_no == start_node',
'distances[node_no] == INF or start_node == node_no',
'INF == distances[node_no] or start_node == node_no',
'INF == distances[node_no] or node_no == start_node',
]
assert any(if_cond.is_equivalent(condition) for condition in conditions)
`);
}
});
```
You should use `continue` to skip to the next iteration of the loop.
```js
({
test: () => {
runPython(`
func = _Node(_code).find_function("shortest_path")
if_stmt = func.find_for_loops()[-1].find_bodies()[0].find_ifs()[0].find_bodies()[0]
assert if_stmt.has_stmt("continue")
`);
}
});
```
# --seed--
## --seed-contents--
```py
INF = float('inf')
adj_matrix = [
[0, 5, 3, INF, 11, INF],
[5, 0, 1, INF, INF, 2],
[3, 1, 0, 1, 5, INF],
[INF, INF, 1, 0, 9, 3],
[11, INF, 5, 9, 0, INF],
[INF, 2, INF, 3, INF, 0],
]
def shortest_path(matrix, start_node, target_node=None):
n = len(matrix)
distances = [INF] * n
distances[start_node] = 0
paths = [[node_no] for node_no in range(n)]
visited = [False] * n
for _ in range(n):
min_distance = INF
current = -1
for node_no in range(n):
if not visited[node_no] and distances[node_no] < min_distance:
min_distance = distances[node_no]
current = node_no
if current == -1:
break
visited[current] = True
for node_no in range(n):
distance = matrix[current][node_no]
if distance != INF and not visited[node_no]:
new_distance = distances[current] + distance
if new_distance < distances[node_no]:
distances[node_no] = new_distance
paths[node_no] = paths[current] + [node_no]
--fcc-editable-region--
targets = [target_node] if target_node is not None else range(n)
for node_no in targets:
pass
--fcc-editable-region--
```
@@ -0,0 +1,148 @@
---
id: 687b45e16f38ad5f4e4bd799
title: Step 23
challengeType: 20
dashedName: step-23
---
# --description--
Now that you've determined a node should be displayed, you need to format its path so it can be printed clearly. For this you will use a generator expression.
A generator expression is similar to a list comprehension, but instead of creating a list, it generates each value one at a time. It uses parentheses `()` instead of square brackets `[]`. For example:
```py
numbers = [1, 2, 3]
squared = (x**2 for x in numbers) # Generator expression
```
Inside the loop after the `if` statement, create a variable called `string_path`. Assign it a generator expression that converts each node number in `paths[node_no]` to a string using `str()`.
The generator expression should iterate over each node number `n` in `paths[node_no]`.
# --hints--
You should create a variable called `string_path`.
```js
({
test: () => {
assert(
runPython(`
func = _Node(_code).find_function("shortest_path")
for_loop = func.find_for_loops()[-1]
for_loop.find_bodies()[0].has_variable("string_path")
`)
);
}
});
```
You should assign a generator expression to `string_path`.
```js
({
test: () => {
runPython(`
import ast
assert isinstance(_Node(_code).find_function("shortest_path").find_for_loops()[1].find_bodies()[0].find_variable("string_path").tree.value, ast.GeneratorExp)
`)
}
})
```
Your generator expression should iterate over `paths[node_no]`.
```js
({
test: () => {
runPython(`
func = _Node(_code).find_function("shortest_path")
for_loop = func.find_for_loops()[-1]
path_var = for_loop.find_bodies()[0].find_variable("string_path")
assert path_var.find_comp_iters()[0].is_equivalent("paths[node_no]")
`);
}
});
```
Your generator expression should use `n` as the iteration variable to iterate over `paths[node_no]`.
```js
({
test: () => {
runPython(`
func = _Node(_code).find_function("shortest_path")
for_loop = func.find_for_loops()[-1]
string_path_var = for_loop.find_bodies()[0].find_variable("string_path")
assert string_path_var.find_comp_targets()[0].is_equivalent("n")
`);
}
});
```
Your generator expression should evaluate `str(n)` for each `n` in `paths[node_no]`.
```js
({
test: () => {
runPython(`
func = _Node(_code).find_function("shortest_path")
for_loop = func.find_for_loops()[-1]
string_path_var = for_loop.find_bodies()[0].find_variable("string_path")
assert string_path_var.find_comp_expr().is_equivalent("str(n)")
`);
}
});
```
# --seed--
## --seed-contents--
```py
INF = float('inf')
adj_matrix = [
[0, 5, 3, INF, 11, INF],
[5, 0, 1, INF, INF, 2],
[3, 1, 0, 1, 5, INF],
[INF, INF, 1, 0, 9, 3],
[11, INF, 5, 9, 0, INF],
[INF, 2, INF, 3, INF, 0],
]
def shortest_path(matrix, start_node, target_node=None):
n = len(matrix)
distances = [INF] * n
distances[start_node] = 0
paths = [[node_no] for node_no in range(n)]
visited = [False] * n
for _ in range(n):
min_distance = INF
current = -1
for node_no in range(n):
if not visited[node_no] and distances[node_no] < min_distance:
min_distance = distances[node_no]
current = node_no
if current == -1:
break
visited[current] = True
for node_no in range(n):
distance = matrix[current][node_no]
if distance != INF and not visited[node_no]:
new_distance = distances[current] + distance
if new_distance < distances[node_no]:
distances[node_no] = new_distance
paths[node_no] = paths[current] + [node_no]
--fcc-editable-region--
targets = [target_node] if target_node is not None else range(n)
for node_no in targets:
if node_no == start_node or distances[node_no] == INF:
continue
--fcc-editable-region--
```
@@ -0,0 +1,94 @@
---
id: 687b48ccb46c5e6126d30bc6
title: Step 25
challengeType: 20
dashedName: step-25
---
# --description--
Finally, print the results for the current node.
Your output should show two things, the distance from the `start_node` to `node_no` and the path taken to get there.
Use an `f-string` to format the output to show both the distance and the full path using the format:
```md
\n[starting node]-[node number] distance: [distance]\nPath: [path]
```
Where:
- `[starting node]` is the `start_node`
- `[node number]` is the `node_no`
- `[distance]` is the distance from the `start_node` to `node_no`
- `[path]` is the path taken to get to `node_no`
# --hints--
You should print the correct output using `print(f'\n{start_node}-{node_no} distance: {distances[node_no]}\nPath: {path}')`.
```js
({
test: () => {
assert(
runPython(
`_Node(_code).find_function('shortest_path').find_for_loops()[1].has_call('print(f"\\\\n{start_node}-{node_no} distance: {distances[node_no]}\\\\nPath: {path}")')`
)
);
}
});
```
# --seed--
## --seed-contents--
```py
INF = float('inf')
adj_matrix = [
[0, 5, 3, INF, 11, INF],
[5, 0, 1, INF, INF, 2],
[3, 1, 0, 1, 5, INF],
[INF, INF, 1, 0, 9, 3],
[11, INF, 5, 9, 0, INF],
[INF, 2, INF, 3, INF, 0],
]
def shortest_path(matrix, start_node, target_node=None):
n = len(matrix)
distances = [INF] * n
distances[start_node] = 0
paths = [[node_no] for node_no in range(n)]
visited = [False] * n
for _ in range(n):
min_distance = INF
current = -1
for node_no in range(n):
if not visited[node_no] and distances[node_no] < min_distance:
min_distance = distances[node_no]
current = node_no
if current == -1:
break
visited[current] = True
for node_no in range(n):
distance = matrix[current][node_no]
if distance != INF and not visited[node_no]:
new_distance = distances[current] + distance
if new_distance < distances[node_no]:
distances[node_no] = new_distance
paths[node_no] = paths[current] + [node_no]
--fcc-editable-region--
targets = [target_node] if target_node is not None else range(n)
for node_no in targets:
if node_no == start_node or distances[node_no] == INF:
continue
string_path = (str(n) for n in paths[node_no])
path = ' -> '.join(string_path)
--fcc-editable-region--
```
@@ -0,0 +1,93 @@
---
id: 687b499774fc9061b32e13cd
title: Step 26
challengeType: 20
dashedName: step-26
---
# --description--
At the end of your function, return the data you've computed so it can be used outside the function.
Return both the list of shortest `distances` from the `start_node` to all other nodes and the list of `paths` that lead to each node
# --hints--
You should have a `return` statement at the end of your function.
```js
({
test: () => {
runPython(`assert not _Node(_code).find_function("shortest_path").find_return().is_empty()`
);
}
});
```
You should return both `distances` and `paths`.
```js
({
test: () => {
assert(
runPython(
`_Node(_code).find_function("shortest_path").find_return().is_equivalent("return distances, paths")`
)
);
}
});
```
# --seed--
## --seed-contents--
```py
INF = float('inf')
adj_matrix = [
[0, 5, 3, INF, 11, INF],
[5, 0, 1, INF, INF, 2],
[3, 1, 0, 1, 5, INF],
[INF, INF, 1, 0, 9, 3],
[11, INF, 5, 9, 0, INF],
[INF, 2, INF, 3, INF, 0],
]
def shortest_path(matrix, start_node, target_node=None):
n = len(matrix)
distances = [INF] * n
distances[start_node] = 0
paths = [[node_no] for node_no in range(n)]
visited = [False] * n
for _ in range(n):
min_distance = INF
current = -1
for node_no in range(n):
if not visited[node_no] and distances[node_no] < min_distance:
min_distance = distances[node_no]
current = node_no
if current == -1:
break
visited[current] = True
for node_no in range(n):
distance = matrix[current][node_no]
if distance != INF and not visited[node_no]:
new_distance = distances[current] + distance
if new_distance < distances[node_no]:
distances[node_no] = new_distance
paths[node_no] = paths[current] + [node_no]
--fcc-editable-region--
targets = [target_node] if target_node is not None else range(n)
for node_no in targets:
if node_no == start_node or distances[node_no] == INF:
continue
string_path = (str(n) for n in paths[node_no])
path = ' -> '.join(string_path)
print(f'\n{start_node}-{node_no} distance: {distances[node_no]}\nPath: {path}')
--fcc-editable-region--
```
@@ -0,0 +1,136 @@
---
id: 687b4bd2e4c7c962ec08310c
title: Step 27
challengeType: 20
dashedName: step-27
---
# --description--
Now you will demonstrate that your function works. At the end of your program, call your function `shortest_path` with the `adj_matrix` and a `start_node` of `0` and a `target_node` of `5` as arguments.
With that, the shortest path algorithm is complete!
# --hints--
You should call the `shortest_path` function with three arguments: `adj_matrix`, `0`, and `5`.
```js
({
test: () => {
assert(
runPython(`_Node(_code).has_call('shortest_path(adj_matrix, 0, 5)')`)
);
}
});
```
# --seed--
## --seed-contents--
```py
INF = float('inf')
adj_matrix = [
[0, 5, 3, INF, 11, INF],
[5, 0, 1, INF, INF, 2],
[3, 1, 0, 1, 5, INF],
[INF, INF, 1, 0, 9, 3],
[11, INF, 5, 9, 0, INF],
[INF, 2, INF, 3, INF, 0],
]
def shortest_path(matrix, start_node, target_node=None):
n = len(matrix)
distances = [INF] * n
distances[start_node] = 0
paths = [[node_no] for node_no in range(n)]
visited = [False] * n
for _ in range(n):
min_distance = INF
current = -1
for node_no in range(n):
if not visited[node_no] and distances[node_no] < min_distance:
min_distance = distances[node_no]
current = node_no
if current == -1:
break
visited[current] = True
for node_no in range(n):
distance = matrix[current][node_no]
if distance != INF and not visited[node_no]:
new_distance = distances[current] + distance
if new_distance < distances[node_no]:
distances[node_no] = new_distance
paths[node_no] = paths[current] + [node_no]
--fcc-editable-region--
targets = [target_node] if target_node is not None else range(n)
for node_no in targets:
if node_no == start_node or distances[node_no] == INF:
continue
string_path = (str(n) for n in paths[node_no])
path = ' -> '.join(string_path)
print(f'\n{start_node}-{node_no} distance: {distances[node_no]}\nPath: {path}')
return distances, paths
--fcc-editable-region--
```
# --solutions--
```py
INF = float('inf')
adj_matrix = [
[0, 5, 3, INF, 11, INF],
[5, 0, 1, INF, INF, 2],
[3, 1, 0, 1, 5, INF],
[INF, INF, 1, 0, 9, 3],
[11, INF, 5, 9, 0, INF],
[INF, 2, INF, 3, INF, 0],
]
def shortest_path(matrix, start_node, target_node=None):
n = len(matrix)
distances = [INF] * n
distances[start_node] = 0
paths = [[node_no] for node_no in range(n)]
visited = [False] * n
for _ in range(n):
min_distance = INF
current = -1
for node_no in range(n):
if not visited[node_no] and distances[node_no] < min_distance:
min_distance = distances[node_no]
current = node_no
if current == -1:
break
visited[current] = True
for node_no in range(n):
distance = matrix[current][node_no]
if distance != INF and not visited[node_no]:
new_distance = distances[current] + distance
if new_distance < distances[node_no]:
distances[node_no] = new_distance
paths[node_no] = paths[current] + [node_no]
targets = [target_node] if target_node is not None else range(n)
for node_no in targets:
if node_no == start_node or distances[node_no] == INF:
continue
string_path = (str(n) for n in paths[node_no])
path = ' -> '.join(string_path)
print(f'\n{start_node}-{node_no} distance: {distances[node_no]}\nPath: {path}')
return distances, paths
shortest_path(adj_matrix, 0, 5)
```
@@ -0,0 +1,53 @@
---
id: 68e4c0ff9edcc92771d7549a
title: Step 6
challengeType: 20
dashedName: step-6
---
# --description--
Right now, the `distances` list only has one element. However, you need one distance value for each node in the graph.
In Python, you can multiply a list by an integer to repeat it. For example, `[0] * 3` creates `[0, 0, 0]`.
Update the `distances` list to contain `n` copies of `INF` by multiplying `[INF]` by `n`.
# --hints--
You should update `distances` to contain `n` copies of `INF`.
```js
({
test: () => {
assert(
runPython(
`_Node(_code).find_function("shortest_path").find_variable("distances").is_equivalent("distances = [INF] * n")`
)
);
}
});
```
# --seed--
## --seed-contents--
```py
INF = float('inf')
adj_matrix = [
[0, 5, 3, INF, 11, INF],
[5, 0, 1, INF, INF, 2],
[3, 1, 0, 1, 5, INF],
[INF, INF, 1, 0, 9, 3],
[11, INF, 5, 9, 0, INF],
[INF, 2, INF, 3, INF, 0],
]
--fcc-editable-region--
def shortest_path(matrix, start_node, target_node=None):
n = len(matrix)
distances = [INF]
--fcc-editable-region--
```
@@ -0,0 +1,103 @@
---
id: 69046179fba35c03dec84fb6
title: Step 24
challengeType: 20
dashedName: step-24
---
# --description--
Now you'll use the `join()` method to combine the string representations of the node numbers into a single readable path.
The `join()` method takes an iterable (like your generator expression) and combines all elements into one string, placing the separator between each element. For example:
```py
numbers = ['1', '2', '3']
route = ' -> '.join(numbers) # route will be '1 -> 2 -> 3'
```
Create a variable called `path` and assign it the result of joining `string_path` using `' -> '` as the separator.
# --hints--
You should create a variable called `path`.
```js
({
test: () => {
assert(
runPython(`
func = _Node(_code).find_function("shortest_path")
for_loop = func.find_for_loops()[-1]
for_loop.find_bodies()[0].has_variable("path")
`)
);
}
});
```
You use `join` to join the items in `string_path` with `' -> '` as the separator and assign the result to `path`.
```js
({
test: () => {
runPython(`
func = _Node(_code).find_function("shortest_path")
for_loop = func.find_for_loops()[-1]
assert for_loop.find_bodies()[0].find_variable("path").is_equivalent('path = " -> ".join(string_path)')
`);
}
});
```
# --seed--
## --seed-contents--
```py
INF = float('inf')
adj_matrix = [
[0, 5, 3, INF, 11, INF],
[5, 0, 1, INF, INF, 2],
[3, 1, 0, 1, 5, INF],
[INF, INF, 1, 0, 9, 3],
[11, INF, 5, 9, 0, INF],
[INF, 2, INF, 3, INF, 0],
]
def shortest_path(matrix, start_node, target_node=None):
n = len(matrix)
distances = [INF] * n
distances[start_node] = 0
paths = [[node_no] for node_no in range(n)]
visited = [False] * n
for _ in range(n):
min_distance = INF
current = -1
for node_no in range(n):
if not visited[node_no] and distances[node_no] < min_distance:
min_distance = distances[node_no]
current = node_no
if current == -1:
break
visited[current] = True
for node_no in range(n):
distance = matrix[current][node_no]
if distance != INF and not visited[node_no]:
new_distance = distances[current] + distance
if new_distance < distances[node_no]:
distances[node_no] = new_distance
paths[node_no] = paths[current] + [node_no]
--fcc-editable-region--
targets = [target_node] if target_node is not None else range(n)
for node_no in targets:
if node_no == start_node or distances[node_no] == INF:
continue
string_path = (str(n) for n in paths[node_no])
--fcc-editable-region--
```
@@ -0,0 +1,39 @@
{
"name": "Implement the Shortest Path Algorithm",
"blockLayout": "challenge-grid",
"blockLabel": "workshop",
"isUpcomingChange": true,
"dashedName": "workshop-shortest-path-algorithm",
"helpCategory": "Python",
"usesMultifileEditor": true,
"hasEditableBoundaries": true,
"challengeOrder": [
{ "id": "6853e58d52ec64349a5760ca", "title": "Step 1" },
{ "id": "686251fb49ebc489ba60ccb5", "title": "Step 2" },
{ "id": "686253f1b051998ad4904e3e", "title": "Step 3" },
{ "id": "68625ad34aa3f58d7f3b9b07", "title": "Step 4" },
{ "id": "686b73007fc582db0132c361", "title": "Step 5" },
{ "id": "68e4c0ff9edcc92771d7549a", "title": "Step 6" },
{ "id": "686b7848fbaa41e0c2a7efef", "title": "Step 7" },
{ "id": "686b78bc13d518e1267448e4", "title": "Step 8" },
{ "id": "686b82af323098e7a5203bf4", "title": "Step 9" },
{ "id": "68760464eb9c2e79cc913fac", "title": "Step 10" },
{ "id": "68760671d950767b055bf2f9", "title": "Step 11" },
{ "id": "6876089f7a500b7c782e5b83", "title": "Step 12" },
{ "id": "68760b53e250f982473e1808", "title": "Step 13" },
{ "id": "68760c4c2648e7832c5eef16", "title": "Step 14" },
{ "id": "687774e17e0972345de2527a", "title": "Step 15" },
{ "id": "687775bed85144353d298665", "title": "Step 16" },
{ "id": "68777816e95d4936f129819f", "title": "Step 17" },
{ "id": "687b3926419dec576850b814", "title": "Step 18" },
{ "id": "687b3c7a3af0df59b363712e", "title": "Step 19" },
{ "id": "687b3ef0c2fe185b2abc4654", "title": "Step 20" },
{ "id": "687b426c3dabfe5cf80fad80", "title": "Step 21" },
{ "id": "687b43c0e20d695e0b8cca5b", "title": "Step 22" },
{ "id": "687b45e16f38ad5f4e4bd799", "title": "Step 23" },
{ "id": "69046179fba35c03dec84fb6", "title": "Step 24" },
{ "id": "687b48ccb46c5e6126d30bc6", "title": "Step 25" },
{ "id": "687b499774fc9061b32e13cd", "title": "Step 26" },
{ "id": "687b4bd2e4c7c962ec08310c", "title": "Step 27" }
]
}
@@ -784,6 +784,7 @@
"comingSoon": true,
"blocks": [
"lecture-understanding-graphs-and-trees",
"workshop-shortest-path-algorithm",
"lab-adjacency-list-to-matrix-converter",
"workshop-breadth-first-search",
"lab-depth-first-search",