rust_igraph/algorithms/io/
edgelist.rs1use std::io::{BufRead, BufReader, Read, Write};
10
11use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
12
13pub fn read_edgelist<R: Read>(input: R) -> IgraphResult<Graph> {
26 let reader = BufReader::new(input);
27 let mut edges: Vec<(VertexId, VertexId)> = Vec::new();
28 let mut max_id: VertexId = 0;
29 let mut had_any = false;
30
31 for (line_no, line) in reader.lines().enumerate() {
32 let line = line?;
33 let trimmed = line.trim();
34 if trimmed.is_empty() || trimmed.starts_with('#') {
35 continue;
36 }
37 let mut parts = trimmed.split_whitespace();
38 let u = parse_id(parts.next(), line_no + 1)?;
39 let v = parse_id(parts.next(), line_no + 1)?;
40 if parts.next().is_some() {
41 return Err(IgraphError::Parse {
42 line: line_no + 1,
43 message: "expected exactly two vertex ids".into(),
44 });
45 }
46 max_id = max_id.max(u).max(v);
47 edges.push((u, v));
48 had_any = true;
49 }
50
51 let n = if had_any {
52 max_id.checked_add(1).ok_or(IgraphError::Internal(
53 "vertex id u32::MAX cannot be sized into n",
54 ))?
55 } else {
56 0
57 };
58 let mut g = Graph::with_vertices(n);
59 g.add_edges(edges)?;
60 Ok(g)
61}
62
63fn parse_id(token: Option<&str>, line: usize) -> IgraphResult<VertexId> {
64 let s = token.ok_or_else(|| IgraphError::Parse {
65 line,
66 message: "missing vertex id".into(),
67 })?;
68 s.parse::<VertexId>().map_err(|e| IgraphError::Parse {
69 line,
70 message: format!("invalid vertex id `{s}`: {e}"),
71 })
72}
73
74pub fn write_edgelist<W: Write>(graph: &Graph, writer: &mut W) -> IgraphResult<()> {
94 for eid in 0..graph.ecount() {
95 #[allow(clippy::cast_possible_truncation)]
96 let (from, to) = graph.edge(eid as u32)?;
97 writeln!(writer, "{from} {to}")?;
98 }
99 Ok(())
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105
106 #[test]
107 fn empty_input_gives_empty_graph() {
108 let g = read_edgelist(&b""[..]).unwrap();
109 assert_eq!(g.vcount(), 0);
110 assert_eq!(g.ecount(), 0);
111 }
112
113 #[test]
114 fn comments_and_blanks_ignored() {
115 let input = b"# hi\n\n0 1\n 2 3 \n";
116 let g = read_edgelist(&input[..]).unwrap();
117 assert_eq!(g.vcount(), 4);
118 assert_eq!(g.ecount(), 2);
119 }
120
121 #[test]
122 fn malformed_line_errors_with_line_number() {
123 let input = b"0 1\nbroken\n";
124 let err = read_edgelist(&input[..]).unwrap_err();
125 match err {
126 IgraphError::Parse { line, .. } => assert_eq!(line, 2),
127 other => panic!("unexpected error: {other:?}"),
128 }
129 }
130
131 #[test]
132 fn write_basic() {
133 let mut g = Graph::with_vertices(3);
134 g.add_edge(0, 1).unwrap();
135 g.add_edge(1, 2).unwrap();
136
137 let mut buf = Vec::new();
138 write_edgelist(&g, &mut buf).unwrap();
139 let s = String::from_utf8(buf).unwrap();
140 assert_eq!(s, "0 1\n1 2\n");
141 }
142
143 #[test]
144 fn write_empty_graph() {
145 let g = Graph::with_vertices(5);
146 let mut buf = Vec::new();
147 write_edgelist(&g, &mut buf).unwrap();
148 let s = String::from_utf8(buf).unwrap();
149 assert_eq!(s, "");
150 }
151
152 #[test]
153 fn write_directed() {
154 let mut g = Graph::new(3, true).unwrap();
155 g.add_edge(0, 1).unwrap();
156 g.add_edge(2, 0).unwrap();
157
158 let mut buf = Vec::new();
159 write_edgelist(&g, &mut buf).unwrap();
160 let s = String::from_utf8(buf).unwrap();
161 assert_eq!(s, "0 1\n2 0\n");
162 }
163
164 #[test]
165 fn round_trip() {
166 let mut g = Graph::with_vertices(4);
167 g.add_edge(0, 1).unwrap();
168 g.add_edge(2, 3).unwrap();
169 g.add_edge(1, 3).unwrap();
170
171 let mut buf = Vec::new();
172 write_edgelist(&g, &mut buf).unwrap();
173
174 let g2 = read_edgelist(&buf[..]).unwrap();
175 assert_eq!(g2.vcount(), g.vcount());
176 assert_eq!(g2.ecount(), g.ecount());
177 }
178}