rust_igraph/algorithms/operators/
contract_vertices.rs1use crate::core::error::IgraphError;
6use crate::core::{Graph, IgraphResult, VertexId};
7
8pub fn contract_vertices(graph: &Graph, mapping: &[VertexId]) -> IgraphResult<Graph> {
47 let n = graph.vcount();
48
49 if mapping.len() != n as usize {
50 return Err(IgraphError::InvalidArgument(format!(
51 "mapping length {} does not match vertex count {n}",
52 mapping.len()
53 )));
54 }
55
56 let new_vcount = if n == 0 {
58 0u32
59 } else {
60 mapping.iter().copied().max().unwrap_or(0) + 1
61 };
62
63 let ecount = graph.ecount();
65 let directed = graph.is_directed();
66 let mut edges: Vec<(VertexId, VertexId)> = Vec::with_capacity(ecount);
67
68 for eid in 0..ecount {
69 #[allow(clippy::cast_possible_truncation)]
70 let eid_u32 = eid as u32;
71 let (src, tgt) = graph.edge(eid_u32)?;
72 let new_src = mapping[src as usize];
73 let new_tgt = mapping[tgt as usize];
74 edges.push((new_src, new_tgt));
75 }
76
77 let mut result = Graph::new(new_vcount, directed)?;
78 result.add_edges(edges)?;
79 Ok(result)
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85
86 #[test]
87 fn test_basic_contraction() {
88 let mut g = Graph::with_vertices(4);
89 g.add_edge(0, 1).unwrap();
90 g.add_edge(1, 2).unwrap();
91 g.add_edge(2, 3).unwrap();
92
93 let cg = contract_vertices(&g, &[0, 0, 1, 1]).unwrap();
95 assert_eq!(cg.vcount(), 2);
96 assert_eq!(cg.ecount(), 3);
97 }
98
99 #[test]
100 fn test_identity_mapping() {
101 let mut g = Graph::with_vertices(3);
102 g.add_edge(0, 1).unwrap();
103 g.add_edge(1, 2).unwrap();
104
105 let cg = contract_vertices(&g, &[0, 1, 2]).unwrap();
106 assert_eq!(cg.vcount(), 3);
107 assert_eq!(cg.ecount(), 2);
108 for eid in 0..2u32 {
109 assert_eq!(g.edge(eid).unwrap(), cg.edge(eid).unwrap());
110 }
111 }
112
113 #[test]
114 fn test_all_into_one() {
115 let mut g = Graph::with_vertices(4);
116 g.add_edge(0, 1).unwrap();
117 g.add_edge(1, 2).unwrap();
118 g.add_edge(2, 3).unwrap();
119 g.add_edge(3, 0).unwrap();
120
121 let cg = contract_vertices(&g, &[0, 0, 0, 0]).unwrap();
123 assert_eq!(cg.vcount(), 1);
124 assert_eq!(cg.ecount(), 4); }
126
127 #[test]
128 fn test_directed() {
129 let mut g = Graph::new(4, true).unwrap();
130 g.add_edge(0, 1).unwrap();
131 g.add_edge(2, 3).unwrap();
132
133 let cg = contract_vertices(&g, &[0, 1, 0, 1]).unwrap();
135 assert!(cg.is_directed());
136 assert_eq!(cg.vcount(), 2);
137 assert_eq!(cg.ecount(), 2);
138 assert_eq!(cg.edge(0).unwrap(), (0, 1));
139 assert_eq!(cg.edge(1).unwrap(), (0, 1));
140 }
141
142 #[test]
143 fn test_empty_graph() {
144 let g = Graph::with_vertices(0);
145 let cg = contract_vertices(&g, &[]).unwrap();
146 assert_eq!(cg.vcount(), 0);
147 assert_eq!(cg.ecount(), 0);
148 }
149
150 #[test]
151 fn test_no_edges() {
152 let g = Graph::with_vertices(5);
153 let cg = contract_vertices(&g, &[0, 0, 1, 1, 2]).unwrap();
154 assert_eq!(cg.vcount(), 3);
155 assert_eq!(cg.ecount(), 0);
156 }
157
158 #[test]
159 fn test_wrong_length() {
160 let g = Graph::with_vertices(3);
161 let result = contract_vertices(&g, &[0, 1]);
162 assert!(result.is_err());
163 }
164
165 #[test]
166 fn test_self_loop_preserved() {
167 let mut g = Graph::with_vertices(3);
168 g.add_edge(1, 1).unwrap();
169
170 let cg = contract_vertices(&g, &[0, 0, 1]).unwrap();
171 assert_eq!(cg.vcount(), 2);
172 assert_eq!(cg.ecount(), 1);
173 assert_eq!(cg.edge(0).unwrap(), (0, 0));
174 }
175
176 #[test]
177 fn test_non_consecutive_mapping() {
178 let mut g = Graph::with_vertices(3);
180 g.add_edge(0, 1).unwrap();
181 g.add_edge(1, 2).unwrap();
182
183 let cg = contract_vertices(&g, &[0, 5, 5]).unwrap();
185 assert_eq!(cg.vcount(), 6);
186 assert_eq!(cg.ecount(), 2);
187 }
188}