utils/graph.rs
1//! Graph helpers.
2
3use crate::bit::BitIterator;
4use crate::number::UnsignedInteger;
5use std::marker::PhantomData;
6
7/// Explore all hamiltonian paths/cycles in a graph.
8///
9/// # Panics
10/// This function panics if the number of vertices exceeds the maximum supported, currently 32.
11///
12/// # Examples
13///
14/// Shortest hamiltonian path and cycle starting at vertex 0:
15/// ```
16/// # use utils::graph::explore_hamiltonian_paths;
17/// # use std::ops::Add;
18/// let distance_matrix = vec![
19/// 0, 7, 2, 8, 6,
20/// 7, 0, 1, 3, 5,
21/// 2, 1, 0, 9, 4,
22/// 8, 3, 9, 0, 9,
23/// 6, 5, 4, 9, 0,
24/// ];
25///
26/// let mut min_hamiltonian_path = u32::MAX;
27/// let mut min_hamiltonian_cycle = u32::MAX;
28/// explore_hamiltonian_paths(
29/// 5, // Number of vertices
30/// 0, // Start vertex
31/// 0, // Initial path cost
32/// |a, b| distance_matrix[a as usize * 5 + b as usize], // Distance function
33/// u32::add, // Accumulate function
34/// |path, loop_edge| { // Callback function
35/// min_hamiltonian_path = min_hamiltonian_path.min(path);
36/// min_hamiltonian_cycle = min_hamiltonian_cycle.min(path + loop_edge);
37/// }
38/// );
39///
40/// assert_eq!(min_hamiltonian_path, 14); // 0 =[6]=> 4 =[4]=> 2 =[1]=> 1 =[3]=> 3
41/// assert_eq!(min_hamiltonian_cycle, 21); // 0 =[2]=> 2 =[1]=> 1 =[3]=> 3 =[9]=> 4 =[6]=> 0
42/// ```
43///
44/// Shortest and longest hamilton paths starting at any vertex:
45/// ```
46/// # use utils::graph::explore_hamiltonian_paths;
47/// let distance_matrix = vec![
48/// 0, 7, 2, 8, 6,
49/// 7, 0, 1, 3, 5,
50/// 2, 1, 0, 9, 4,
51/// 8, 3, 9, 0, 9,
52/// 6, 5, 4, 9, 0,
53/// ];
54///
55/// let mut min_path = u32::MAX;
56/// let mut max_path = 0;
57/// explore_hamiltonian_paths(
58/// 5, // Number of vertices
59/// 0, // Start vertex
60/// (0, u32::MAX, 0), // Initial path cost
61/// |a, b| distance_matrix[a as usize * 5 + b as usize], // Distance function
62/// |(total, min_edge, max_edge), edge| { // Accumulate function
63/// (total + edge, min_edge.min(edge), max_edge.max(edge))
64/// },
65/// |(total, min_edge, max_edge), loop_edge| { // Callback function
66/// let loop_total = total + loop_edge;
67/// min_path = min_path.min(loop_total - max_edge.max(loop_edge));
68/// max_path = max_path.max(loop_total - min_edge.min(loop_edge));
69/// }
70/// );
71///
72/// assert_eq!(min_path, 12); // 3 =[3]=> 1 =[1]=> 2 =[2]=> 0 =[6]=> 4
73/// assert_eq!(max_path, 31); // 1 =[7]=> 0 =[6]=> 4 =[9]=> 3 =[9]=> 2
74/// ```
75#[inline]
76pub fn explore_hamiltonian_paths<E, P: Copy>(
77 vertices: u32,
78 start_vertex: u32,
79 initial_path: P,
80 distance_fn: impl Fn(u32, u32) -> E,
81 accumulate_fn: impl Fn(P, E) -> P,
82 callback_fn: impl FnMut(P, E),
83) {
84 // Rust doesn't allow recursive FnMut closures, so move state into struct
85 struct Visitor<P, F, G, H> {
86 start_vertex: u32,
87 phantom: PhantomData<P>,
88 distance_fn: F,
89 accumulate_fn: G,
90 callback_fn: H,
91 }
92
93 impl<E, P: Copy, F: Fn(u32, u32) -> E, G: Fn(P, E) -> P, H: FnMut(P, E)> Visitor<P, F, G, H> {
94 #[inline]
95 fn visit<V: UnsignedInteger>(&mut self, current_vertex: u32, visited: V, path: P) {
96 if visited == V::MAX {
97 let loop_edge = (self.distance_fn)(current_vertex, self.start_vertex);
98 (self.callback_fn)(path, loop_edge);
99 return;
100 }
101
102 for (next_vertex, next_bit) in BitIterator::zeroes(visited) {
103 let new_edge = (self.distance_fn)(current_vertex, next_vertex);
104 let new_path = (self.accumulate_fn)(path, new_edge);
105 self.visit(next_vertex, visited | next_bit, new_path);
106 }
107 }
108 }
109
110 // visit can take any unsigned integer type as a bitmask, so the code could switch the
111 // implementation based on the number of vertices. However, it seems unlikely that an AoC
112 // problem would require this as generating that many permutations would be very slow, so for
113 // now assume u32 is fine.
114 assert!(vertices <= 32, "too many vertices");
115
116 let mut visitor = Visitor {
117 start_vertex,
118 distance_fn,
119 accumulate_fn,
120 callback_fn,
121 phantom: PhantomData,
122 };
123 visitor.visit(
124 start_vertex,
125 !(u32::MAX >> (u32::BITS - vertices)) | (1 << start_vertex),
126 initial_path,
127 );
128}