### Describe the Issue
In many steps of this project, when the task is assigned…, it is assumed that we already know about things that have in fact not been presented yet. I made a summary of my impressions regarding this. I have also written suggestions that may help you deal with these gaps, if you are willing to improve the course. I hope this may help you to offer future students a better learning experience.
- [ ] **Introduction.** Given the complexity of the algorithm and the fact that there are in the literature a number of different shortest-path algorithms, I suggest that you present in the introduction the flowchart of the shortest-path algorithm that is used in this challenge. I sketched it and this is this [link](https://drive.google.com/file/d/1jOADhOxzhxO97Akl4EOaBGIiIGA0KZc9/view?usp=drive_link).
The algorithm represented by the diagram does not include the operations corresponding to the nested **if** and **else** conditions of the code (*Steps 45-47*). I've tested and seen that the code works well when we erase the nested **if** and **else** blocks and replace them by the assignment `paths[node] = paths[current][:]` alone. In fact, the operation performed by the nested **if** block has the same algorithmic effect as the operation performed by the nested "else" block.
- [ ] **Step 36.** This step uses two previously introduced functionalities combined in one syntaxis formation. The first functionality is the one introduced in *Step 8*, by which a key's value can be changed. The other is the **.append()** method, which here is used to change the key's value. This can be performed because the value type is, in this case, a list.
`name_of_dictionary[key_to_the_value_list].append(item_to_be_appended)`
It would be interesting to point out that the value (i.e., the list) that is to be changed (i.e., appended to) does not appear in the syntax. Instead, it is addressed by the key to which it is related.
I would suggest that this step be divided into two, so that the use of each functionality is more clearly presented and tasks can be performed without students getting stuck.
- [ ] **Step 38.** The only overall description of the algorithm that is given, is given in this step. It's very brief and probably not sufficient for the most part of beginners in programming. Above, I have suggested the flowchart in the *Introduction*.
- [ ] **Step 39.** This step assumes we know how to create a loop that runs while there are items in a list. We could find the solution if we knew that a list will, when prompted, return the `True` boolean value, except when it is empty, in which case it returns `False`.
- [ ] **Steps 40-41.** These steps bring five new concepts, and just two of them are introduced:
1. The **min()** function is introduced in *Step 40*;
2. A key function can be inserted in the argument of another function;
3. The **min()** function can take a key parameter;
4. The **.get()** method is introduced in *Step 41*;
5. Sometimes, the empty parenthesis **()** can be omitted, allowing for an abbreviated syntax.
I suggest that *Step 41* be divided into two steps, so that the key function-key parameter functionality inside the argument of the **min()** function can be introduced and explained in one step, and the **.get()** method, with its abbreviated syntax, in the next.
- [ ] **Steps 45-47.** As previewed above, in the comment on the Introduction, I would suggest that the nested **if/else** blocks built along these steps be replaced by just the `paths[node] = paths[current]` assignment.
- [ ] **Step 50.** The `and` operator must be used to perform the task, but is not introduced or explained. I suggest that it be. I would also like to comment that, here too, the boolean value of a list is used to perform the task, which further justifies my suggestion for *Step 39*.
- [ ] **Step 52.** The slicing functionality and its abbreviated form `[:]` must be used to perform the task, but are not clarified. I suggest that they be.
Well, that would be all for the moment.
### Affected Page
https://www.freecodecamp.org/learn/scientific-computing-with-python/#learn-algorithm-design-by-building-a-shortest-path-algorithm
### Your code
```
# This is exactly the code from the project. I have just added comments as I worked on it.
# They might help the work of the contributor.
my_graph1 = { # This is a weighted graph, where each node is related to two other nodes.
'A': [('B', 3), ('D', 1)], # Therefore, each value that composes the graph
'B': [('A', 3), ('C', 4)], # is defined by a list of 2-item tuples.
'C': [('B', 4), ('D', 7)], # The second element of each tuple is the weigh of that edge.
'D': [('A', 1), ('C', 7)] # In this algorithm, it represents the distance between the nodes
}
def shortest_path(graph, start, target = ''): # "graph" is an input dictionary; "start" is the input starting node;
# "target is used when you want to print
# the path to just one specific node. (Step 53)
unvisited = list(graph) # This solves the initialization of the list "unvisited"
# without the need of a "for" loop.
# It will include only the nodes that have not yet been processed,
# that is, the nodes to which you do not yet know the shortest path.
# This list starts, of course, including all nodes.
distances = {node: 0 if node == start else float('inf') for node in graph} # This initializes
# the dictionary "distances"
# without the need of a for loop.
# Dict comprehension is used.
# "distances" will hold the currently known shortest distance from the "start" node
# to each one of the other nodes (and also to itself, by the way).
# It will be updated each time a distance is calculated, if this distance became shorter, i.e.,
# the update takes place when the condition "calculated distance < known distance" is satisfied.
# This explains why "distances" is start-created with infinite distances.
paths = {node: [] for node in graph} # Dictionary comprehension is used here. To each key from "graph"
# an empty list is assigned. "paths" will keep track of
# the paths between the starting node and each other node.
# Each list will hold the shortest path from the starting
# node to the key node to which the list is related. (Step 34)
paths[start].append(start) # "Since the algorithm begins its assessment from the starting node,
# after creating the "paths" dictionary, you need to add
# the starting node to its own list in the paths dictionary." Step 36
# This completes he initialization of the paths dictionary.
while unvisited: # This "while" loop runs until the list "unvisited" is empty.
# This is the main loop: it systematically explores the nodes in the graph,
# by evaluating all the nodes that neighbor one specific node, at a time.
current = min(unvisited, key=distances.get) # Defines the current node to process.
# It is the node whose neighbors will be assessed.
# The key function returns the value that each node
# in "unvisited" has in "distances", so that the
# result of the "min" comparison is the unvisited node
# that is closest to the starting node.
# (Steps 40 and 41)
# IMPORTANT: When this operation is performed,
# the shortest known distance to this elected node
# is the actual shortest distance to this node.
# This distance will not be updated in the
# "distances" dictionary any more. This fact is
# intrinsic to the algorithm. And by the end of this
# "while" iteration, this "current" node will be
# excluded from the unvisited list.
for node, distance in graph[current]: # Iterates over the list of tuples
# elicited by graph[current],
# expression that bears the dict[key] syntax.
# graph[current] is the list of tuples that describes
# the edges of the node being processed ("current"),
# that is, the distance to each of its neighbor nodes.
# The iterating variable "node" will iterate over
# first elements and the iterating variable
# "distance", over second elements of the 2-D tuples.
# Algorithm: this loop iterates over all the current
# node's neighbor nodes, either visited or unvisited,
# to evatuate the overall distance to each 1 of them,
# when using the path mediated by the current node.
if distance + distances[current] < distances[node]:
# Checks if the distance from the "current" node to
# the neighbor node being processed by the "for" loop
# plus the distance from the starting node to the "current" node
# is less than the presently known overall distance from "start"
# to this node. (Step 43)
distances[node] = distance + distances[current] # Updates the distance from the
# starting node to this node.
# Note: This will have performed a round of "distances" updating,
# once all the tuples have been iterated over.
if paths[node] and paths[node][-1] == node: # Checks if the node being processed
# is the last node of the path
# to the node being processed. (Step 45)
# If this is True, then this node has been
# evaluated before.
# To avoid Error, also checks if
# paths[node] is not empty. (Steps 50)
paths[node] = paths[current][:] # Updates shortest known path, by equaling it
# to the path to "current".
# (Step 46 - no hint on algorithmic meaning)
# Bug correction, by use of [:] slicing
# (Step 52)
else:
paths[node].extend(paths[current]) # Adds the path until the "current" node to
# the (empty) path until the neighbor node.
# (Step 47)
# I think this is the same as assigning
# paths[current] to paths[node]. If the node has not been evaluated yet, then its path is an empty list.
# Therefore, this nested "if/else" block does not make sense to me.
# It could simply be replaced by the assignment operation.
paths[node].append(node) # "Appends the neighbor node to its path." Step 48
# This is to complete the path to that node.
unvisited.remove(current) # After the "for" loop is completed, this declares
# the processed "current" node a visited node. (Step 49)
targets_to_print = [target] if target else graph # Step 54
for node in targets_to_print: # Step 55
if node == start: # Step 56
continue
print(f'\n{start}-{node} distance: {distances[node]}\nPath: {" -> ".join(paths[node])}')
return distances, paths # Step 57
my_graph2 = {
'A': [('B', 5), ('C', 3), ('E', 11)],
'B': [('A', 5), ('C', 1), ('F', 2)],
'C': [('A', 3), ('B', 1), ('D', 1), ('E', 5)],
'D': [('C', 1), ('E', 9), ('F', 3)],
'E': [('A', 11), ('C', 5), ('D', 9)],
'F': [('B', 2), ('D', 3)]
} # Step 58
shortest_path(my_graph2, 'A', 'F') # Step 59
# Note: Steps 53 to 59 configure the printing and tests.
```
### Expected behavior
This was the most challenging Challenge of the Python series for me up to now. And by far the most interesting, too. I expected more clarity in the way the challenge was designed. I have now understood the algorithm, the code and come up with suggestions to make the Challenge more didactic. I am opening this issue to contribute with what I have found out.
### Screenshots
_No response_
### System
- Device: Laptop Microsoft Surface
- OS: Windows 10
- Browser: Edge
- Version: 130.0.2849.80 (Official build) (64-bit)
### Additional context
_No response_