oximedia_graph/
serialize.rs1use std::collections::HashMap;
4
5#[allow(dead_code)]
7#[derive(Debug, Clone)]
8pub struct SerializableGraph {
9 pub nodes: HashMap<usize, String>,
11 pub edges: Vec<(usize, usize, Option<String>)>,
13 pub directed: bool,
15 pub name: Option<String>,
17}
18
19impl SerializableGraph {
20 #[allow(dead_code)]
22 pub fn new_directed() -> Self {
23 Self {
24 nodes: HashMap::new(),
25 edges: Vec::new(),
26 directed: true,
27 name: None,
28 }
29 }
30
31 #[allow(dead_code)]
33 pub fn new_undirected() -> Self {
34 Self {
35 nodes: HashMap::new(),
36 edges: Vec::new(),
37 directed: false,
38 name: None,
39 }
40 }
41
42 #[allow(dead_code)]
44 pub fn add_node(&mut self, id: usize, label: impl Into<String>) {
45 self.nodes.insert(id, label.into());
46 }
47
48 #[allow(dead_code)]
50 pub fn add_edge(&mut self, from: usize, to: usize, label: Option<String>) {
51 self.edges.push((from, to, label));
52 }
53
54 #[allow(dead_code)]
56 pub fn to_dot(&self) -> String {
57 let graph_type = if self.directed { "digraph" } else { "graph" };
58 let name = self.name.as_deref().unwrap_or("G");
59 let edge_op = if self.directed { "->" } else { "--" };
60
61 let mut out = String::new();
62 out.push_str(&format!("{graph_type} {name} {{\n"));
63
64 let mut node_ids: Vec<usize> = self.nodes.keys().copied().collect();
66 node_ids.sort_unstable();
67 for id in node_ids {
68 let label = &self.nodes[&id];
69 if label.is_empty() {
70 out.push_str(&format!(" {id};\n"));
71 } else {
72 out.push_str(&format!(" {id} [label=\"{label}\"];\n"));
73 }
74 }
75
76 for (from, to, label) in &self.edges {
77 if let Some(lbl) = label {
78 out.push_str(&format!(" {from} {edge_op} {to} [label=\"{lbl}\"];\n"));
79 } else {
80 out.push_str(&format!(" {from} {edge_op} {to};\n"));
81 }
82 }
83
84 out.push_str("}\n");
85 out
86 }
87
88 #[allow(dead_code)]
91 pub fn to_adjacency_list(&self) -> String {
92 let mut adj: HashMap<usize, Vec<usize>> = HashMap::new();
93 for &id in self.nodes.keys() {
94 adj.entry(id).or_default();
95 }
96 for &(from, to, _) in &self.edges {
97 adj.entry(from).or_default().push(to);
98 if !self.directed {
99 adj.entry(to).or_default().push(from);
100 }
101 }
102
103 let mut node_ids: Vec<usize> = adj.keys().copied().collect();
104 node_ids.sort_unstable();
105
106 let mut out = String::new();
107 for id in node_ids {
108 let mut neighbors = adj[&id].clone();
109 neighbors.sort_unstable();
110 let neighbor_str = neighbors
111 .iter()
112 .map(|n| n.to_string())
113 .collect::<Vec<_>>()
114 .join(" ");
115 out.push_str(&format!("{id}: {neighbor_str}\n"));
116 }
117 out
118 }
119
120 #[allow(dead_code)]
123 pub fn to_edge_list(&self) -> String {
124 let mut out = String::new();
125 for &(from, to, ref label) in &self.edges {
126 if let Some(lbl) = label {
127 out.push_str(&format!("{from} {to} {lbl}\n"));
128 } else {
129 out.push_str(&format!("{from} {to}\n"));
130 }
131 }
132 out
133 }
134
135 #[allow(dead_code)]
137 pub fn node_count(&self) -> usize {
138 self.nodes.len()
139 }
140
141 #[allow(dead_code)]
143 pub fn edge_count(&self) -> usize {
144 self.edges.len()
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 fn make_simple_graph() -> SerializableGraph {
153 let mut g = SerializableGraph::new_directed();
154 g.add_node(0, "A");
155 g.add_node(1, "B");
156 g.add_node(2, "C");
157 g.add_edge(0, 1, None);
158 g.add_edge(1, 2, Some("edge".to_string()));
159 g
160 }
161
162 #[test]
163 fn test_node_count() {
164 let g = make_simple_graph();
165 assert_eq!(g.node_count(), 3);
166 }
167
168 #[test]
169 fn test_edge_count() {
170 let g = make_simple_graph();
171 assert_eq!(g.edge_count(), 2);
172 }
173
174 #[test]
175 fn test_dot_contains_digraph() {
176 let g = make_simple_graph();
177 let dot = g.to_dot();
178 assert!(dot.contains("digraph"));
179 }
180
181 #[test]
182 fn test_dot_contains_nodes() {
183 let g = make_simple_graph();
184 let dot = g.to_dot();
185 assert!(dot.contains("label=\"A\""));
186 assert!(dot.contains("label=\"B\""));
187 }
188
189 #[test]
190 fn test_dot_contains_edge_op() {
191 let g = make_simple_graph();
192 let dot = g.to_dot();
193 assert!(dot.contains("->"));
194 }
195
196 #[test]
197 fn test_dot_undirected_uses_double_dash() {
198 let mut g = SerializableGraph::new_undirected();
199 g.add_node(0, "X");
200 g.add_node(1, "Y");
201 g.add_edge(0, 1, None);
202 let dot = g.to_dot();
203 assert!(dot.contains("--"));
204 assert!(!dot.contains("->"));
205 }
206
207 #[test]
208 fn test_dot_labeled_edge() {
209 let g = make_simple_graph();
210 let dot = g.to_dot();
211 assert!(dot.contains("label=\"edge\""));
212 }
213
214 #[test]
215 fn test_adjacency_list_contains_nodes() {
216 let g = make_simple_graph();
217 let al = g.to_adjacency_list();
218 assert!(al.contains("0:"));
219 assert!(al.contains("1:"));
220 assert!(al.contains("2:"));
221 }
222
223 #[test]
224 fn test_adjacency_list_has_edges() {
225 let g = make_simple_graph();
226 let al = g.to_adjacency_list();
227 assert!(al.contains("0: 1"));
229 }
230
231 #[test]
232 fn test_edge_list_format() {
233 let g = make_simple_graph();
234 let el = g.to_edge_list();
235 assert!(el.contains("0 1"));
236 assert!(el.contains("1 2 edge"));
237 }
238
239 #[test]
240 fn test_empty_graph_dot() {
241 let g = SerializableGraph::new_directed();
242 let dot = g.to_dot();
243 assert!(dot.contains("digraph G {"));
244 assert!(dot.contains("}"));
245 }
246
247 #[test]
248 fn test_graph_with_name() {
249 let mut g = SerializableGraph::new_directed();
250 g.name = Some("MyGraph".to_string());
251 let dot = g.to_dot();
252 assert!(dot.contains("digraph MyGraph {"));
253 }
254
255 #[test]
256 fn test_adjacency_list_undirected_bidirectional() {
257 let mut g = SerializableGraph::new_undirected();
258 g.add_node(0, "A");
259 g.add_node(1, "B");
260 g.add_edge(0, 1, None);
261 let al = g.to_adjacency_list();
262 assert!(al.contains("0: 1"));
264 assert!(al.contains("1: 0"));
265 }
266}