-```python
-# https://dodona.be/nl/courses/3363/series/36083/activities/1049251771
-import copy
-from pathlib import Path
-from itertools import chain
-
-
-def longest_path_length(source: int, sink: int, graph_filename: str | Path) -> int:
- """
- Calculate the length of the longest path in the given graph between source and sink.
-
- >>> longest_path_length(0, 4, 'data/04-data01.txt')
- 9
- """
-
- graph = parse_graph(graph_filename)
-
- path_lengths = [-1 for _ in range(max(len(graph), source, sink) + 1)]
- path_lengths[source] = 0
-
- for node in topological_ordering(graph):
- if node is not source:
- path_lengths[node] = max(path_lengths[predecessor] + weight for predecessor, weight in incoming_edges(graph, node))
-
- return path_lengths[sink]
-
-
-def longest_path(source: int, sink: int, graph_filename: str | Path) -> tuple[int, ...]:
- """
- Calculate the longest path in the given graph between source and sink.
-
- The path is constructed by using a backtracking algorithm.
-
- >>> longest_path(0, 4, 'data/04-data01.txt')
- (0, 2, 3, 4)
- """
-
- graph = parse_graph(graph_filename)
-
- size = max(len(graph), source, sink) + 1
-
- previous = [-1 for _ in range(size)]
-
- path_lengths = [-1 for _ in range(size)]
- path_lengths[source] = 0
-
- # Calculate the path by weights
- for node in topological_ordering(graph):
- if node is not source:
- # Calculate the longest path based on the incoming edges in the DAG
- for predecessor, weight in incoming_edges(graph, node):
- if path_lengths[node] < path_lengths[predecessor] + weight:
- previous[node] = predecessor
- path_lengths[node] = path_lengths[predecessor] + weight
-
- # Reconstruct the path by backtracking
- path = []
- current = sink
-
- while previous[current] >= 0:
- path.append(current)
- current = previous[current]
-
- path.reverse()
-
- return tuple(path)
-
-
-def parse_graph(graph_filename: str | Path) -> dict[int, list[tuple[int, int]]]:
- """
- Returns the list of edges in the given input file.
-
- For every node, the list of outgoing edges and their weights are given.
-
- >>> parse_graph('data/04-data01.txt')
- {0: [(1, 7), (2, 4)], 1: [(4, 1)], 2: [(3, 2)], 3: [(4, 3)]}
- """
-
- # Graph of all nodes, with a list of outgoing edges
- graph = {}
-
- with open(graph_filename, 'r', encoding='utf-8') as graph_file:
- for line in graph_file:
- source, value = line.split('->')
- source = int(source)
- target, weight = map(int, value.split(':'))
-
- if source not in graph:
- graph[source] = []
-
- graph[source].append((target, weight))
-
- return graph
-
-def topological_ordering(graph: dict[int, list[tuple[int, int]]]) -> list[int]:
- """
- Returns a valid topological ordering of the given graph.
-
- >>> topological_ordering({0: [(1, 7), (2, 4)], 1: [(4, 1)], 2: [(3, 2)], 3: [(4, 3)]})
- [0, 1, 2, 3]
- """
-
- # List of nodes in topological order
- node_order = []
-
- # Set of nodes that still need to be visited.
- remaining_nodes = set(graph)
-
- while len(remaining_nodes) > 0:
- # Select the next node.
- source, value = min(graph.items(), key=lambda item: len(item[1]))
-
- node_order.append(source)
-
- # Remove all outgoing edges from remaining nodes.
- for target, weight in graph[source]:
- remaining_nodes.remove(target)
-
- return node_order
-```