```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
```