rust_igraph/algorithms/io/
leda.rs1use std::io::Write;
29
30use crate::core::{Graph, IgraphError, IgraphResult};
31
32pub fn write_leda<W: Write>(
55 graph: &Graph,
56 vertex_labels: Option<&[String]>,
57 edge_weights: Option<&[f64]>,
58 writer: &mut W,
59) -> IgraphResult<()> {
60 if let Some(l) = vertex_labels {
61 if l.len() != graph.vcount() as usize {
62 return Err(IgraphError::InvalidArgument(format!(
63 "vertex_labels length {} does not match vcount {}",
64 l.len(),
65 graph.vcount()
66 )));
67 }
68 for (i, lbl) in l.iter().enumerate() {
69 if lbl.contains('\n') {
70 return Err(IgraphError::InvalidArgument(format!(
71 "vertex label at index {i} contains a newline character"
72 )));
73 }
74 }
75 }
76 if let Some(w) = edge_weights {
77 if w.len() != graph.ecount() {
78 return Err(IgraphError::InvalidArgument(format!(
79 "edge_weights length {} does not match ecount {}",
80 w.len(),
81 graph.ecount()
82 )));
83 }
84 }
85
86 writeln!(writer, "LEDA.GRAPH")?;
88
89 if vertex_labels.is_some() {
91 writeln!(writer, "string")?;
92 } else {
93 writeln!(writer, "void")?;
94 }
95
96 if edge_weights.is_some() {
98 writeln!(writer, "double")?;
99 } else {
100 writeln!(writer, "void")?;
101 }
102
103 if graph.is_directed() {
105 writeln!(writer, "-1")?;
106 } else {
107 writeln!(writer, "-2")?;
108 }
109
110 writeln!(writer, "# Vertices")?;
112 writeln!(writer, "{}", graph.vcount())?;
113
114 for v in 0..graph.vcount() {
115 match vertex_labels {
116 Some(labels) => writeln!(writer, "|{{{}}}|", labels[v as usize])?,
117 None => writeln!(writer, "|{{}}|")?,
118 }
119 }
120
121 writeln!(writer, "# Edges")?;
123 writeln!(writer, "{}", graph.ecount())?;
124
125 for eid in 0..graph.ecount() {
126 #[allow(clippy::cast_possible_truncation)]
127 let (from, to) = graph.edge(eid as u32)?;
128
129 match edge_weights {
130 Some(w) => writeln!(writer, "{} {} 0 |{{{}}}|", from + 1, to + 1, w[eid])?,
131 None => writeln!(writer, "{} {} 0 |{{}}|", from + 1, to + 1)?,
132 }
133 }
134
135 Ok(())
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141
142 #[test]
143 fn test_basic_undirected() {
144 let mut g = Graph::with_vertices(3);
145 g.add_edge(0, 1).unwrap();
146 g.add_edge(1, 2).unwrap();
147
148 let mut buf = Vec::new();
149 write_leda(&g, None, None, &mut buf).unwrap();
150 let s = String::from_utf8(buf).unwrap();
151
152 assert!(s.starts_with("LEDA.GRAPH\n"));
153 assert!(s.contains("void\nvoid\n-2\n"));
154 assert!(s.contains("# Vertices\n3\n"));
155 assert!(s.contains("|{}|\n|{}|\n|{}|\n"));
156 assert!(s.contains("# Edges\n2\n"));
157 assert!(s.contains("1 2 0 |{}|\n"));
158 assert!(s.contains("2 3 0 |{}|\n"));
159 }
160
161 #[test]
162 fn test_directed() {
163 let mut g = Graph::new(2, true).unwrap();
164 g.add_edge(0, 1).unwrap();
165
166 let mut buf = Vec::new();
167 write_leda(&g, None, None, &mut buf).unwrap();
168 let s = String::from_utf8(buf).unwrap();
169
170 assert!(s.contains("-1\n"));
171 }
172
173 #[test]
174 fn test_with_labels() {
175 let mut g = Graph::with_vertices(3);
176 g.add_edge(0, 1).unwrap();
177
178 let labels = vec!["Alice".to_string(), "Bob".to_string(), "Carol".to_string()];
179 let mut buf = Vec::new();
180 write_leda(&g, Some(&labels), None, &mut buf).unwrap();
181 let s = String::from_utf8(buf).unwrap();
182
183 assert!(s.contains("string\nvoid\n"));
184 assert!(s.contains("|{Alice}|\n"));
185 assert!(s.contains("|{Bob}|\n"));
186 assert!(s.contains("|{Carol}|\n"));
187 }
188
189 #[test]
190 fn test_with_weights() {
191 let mut g = Graph::with_vertices(2);
192 g.add_edge(0, 1).unwrap();
193
194 let weights = vec![3.5];
195 let mut buf = Vec::new();
196 write_leda(&g, None, Some(&weights), &mut buf).unwrap();
197 let s = String::from_utf8(buf).unwrap();
198
199 assert!(s.contains("void\ndouble\n"));
200 assert!(s.contains("1 2 0 |{3.5}|\n"));
201 }
202
203 #[test]
204 fn test_with_labels_and_weights() {
205 let mut g = Graph::with_vertices(2);
206 g.add_edge(0, 1).unwrap();
207
208 let labels = vec!["X".to_string(), "Y".to_string()];
209 let weights = vec![1.25];
210 let mut buf = Vec::new();
211 write_leda(&g, Some(&labels), Some(&weights), &mut buf).unwrap();
212 let s = String::from_utf8(buf).unwrap();
213
214 assert!(s.contains("string\ndouble\n"));
215 assert!(s.contains("|{X}|\n"));
216 assert!(s.contains("|{Y}|\n"));
217 assert!(s.contains("1 2 0 |{1.25}|\n"));
218 }
219
220 #[test]
221 fn test_empty_graph() {
222 let g = Graph::with_vertices(0);
223
224 let mut buf = Vec::new();
225 write_leda(&g, None, None, &mut buf).unwrap();
226 let s = String::from_utf8(buf).unwrap();
227
228 assert!(s.contains("# Vertices\n0\n"));
229 assert!(s.contains("# Edges\n0\n"));
230 }
231
232 #[test]
233 fn test_no_edges() {
234 let g = Graph::with_vertices(3);
235
236 let mut buf = Vec::new();
237 write_leda(&g, None, None, &mut buf).unwrap();
238 let s = String::from_utf8(buf).unwrap();
239
240 assert!(s.contains("# Vertices\n3\n"));
241 assert!(s.contains("# Edges\n0\n"));
242 }
243
244 #[test]
245 fn test_label_mismatch_error() {
246 let g = Graph::with_vertices(3);
247 let labels = vec!["A".to_string()];
248 let mut buf = Vec::new();
249 assert!(write_leda(&g, Some(&labels), None, &mut buf).is_err());
250 }
251
252 #[test]
253 fn test_weight_mismatch_error() {
254 let mut g = Graph::with_vertices(2);
255 g.add_edge(0, 1).unwrap();
256 let weights = vec![1.0, 2.0];
257 let mut buf = Vec::new();
258 assert!(write_leda(&g, None, Some(&weights), &mut buf).is_err());
259 }
260
261 #[test]
262 fn test_newline_in_label_error() {
263 let g = Graph::with_vertices(2);
264 let labels = vec!["hello\nworld".to_string(), "ok".to_string()];
265 let mut buf = Vec::new();
266 assert!(write_leda(&g, Some(&labels), None, &mut buf).is_err());
267 }
268
269 #[test]
270 fn test_self_loop() {
271 let mut g = Graph::with_vertices(2);
272 g.add_edge(0, 0).unwrap();
273
274 let mut buf = Vec::new();
275 write_leda(&g, None, None, &mut buf).unwrap();
276 let s = String::from_utf8(buf).unwrap();
277
278 assert!(s.contains("1 1 0 |{}|\n"));
279 }
280
281 #[test]
282 fn test_one_based_vertex_ids() {
283 let mut g = Graph::with_vertices(4);
284 g.add_edge(2, 3).unwrap();
285
286 let mut buf = Vec::new();
287 write_leda(&g, None, None, &mut buf).unwrap();
288 let s = String::from_utf8(buf).unwrap();
289
290 assert!(s.contains("3 4 0 |{}|\n"));
291 }
292}