1#![allow(dead_code)]
2use std::collections::{HashMap, HashSet};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
12pub struct MergeNodeId(
13 pub usize,
15);
16
17impl std::fmt::Display for MergeNodeId {
18 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
19 write!(f, "MergeNode({})", self.0)
20 }
21}
22
23#[derive(Debug, Clone)]
25pub struct MergeNode {
26 pub id: MergeNodeId,
28 pub label: String,
30 pub metadata: HashMap<String, String>,
32}
33
34impl MergeNode {
35 pub fn new(id: MergeNodeId, label: &str) -> Self {
37 Self {
38 id,
39 label: label.to_string(),
40 metadata: HashMap::new(),
41 }
42 }
43
44 pub fn with_metadata(mut self, key: &str, value: &str) -> Self {
46 self.metadata.insert(key.to_string(), value.to_string());
47 self
48 }
49}
50
51#[derive(Debug, Clone, PartialEq, Eq, Hash)]
53pub struct MergeEdge {
54 pub from: MergeNodeId,
56 pub to: MergeNodeId,
58 pub label: Option<String>,
60 pub weight: u32,
62}
63
64impl MergeEdge {
65 pub fn new(from: MergeNodeId, to: MergeNodeId) -> Self {
67 Self {
68 from,
69 to,
70 label: None,
71 weight: 1,
72 }
73 }
74
75 pub fn with_label(from: MergeNodeId, to: MergeNodeId, label: &str) -> Self {
77 Self {
78 from,
79 to,
80 label: Some(label.to_string()),
81 weight: 1,
82 }
83 }
84
85 pub fn with_weight(mut self, weight: u32) -> Self {
87 self.weight = weight;
88 self
89 }
90}
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
94pub enum ConflictStrategy {
95 KeepFirst,
97 KeepSecond,
99 Remap,
101 Fail,
103}
104
105#[allow(clippy::derivable_impls)]
106impl Default for ConflictStrategy {
107 fn default() -> Self {
108 Self::Remap
109 }
110}
111
112#[derive(Debug, Clone)]
114pub struct MergeConfig {
115 pub conflict_strategy: ConflictStrategy,
117 pub deduplicate_edges: bool,
119 pub cross_links: Vec<(MergeNodeId, MergeNodeId)>,
121}
122
123impl Default for MergeConfig {
124 fn default() -> Self {
125 Self {
126 conflict_strategy: ConflictStrategy::Remap,
127 deduplicate_edges: true,
128 cross_links: Vec::new(),
129 }
130 }
131}
132
133#[derive(Debug, Clone, PartialEq, Eq)]
135pub enum MergeError {
136 Conflict(
138 MergeNodeId,
140 ),
141 NodeNotFound(
143 MergeNodeId,
145 ),
146 EmptyGraphs,
148}
149
150impl std::fmt::Display for MergeError {
151 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152 match self {
153 Self::Conflict(id) => write!(f, "Node conflict: {id}"),
154 Self::NodeNotFound(id) => write!(f, "Node not found: {id}"),
155 Self::EmptyGraphs => write!(f, "Both graphs are empty"),
156 }
157 }
158}
159
160pub struct MergeGraph {
162 nodes: HashMap<MergeNodeId, MergeNode>,
164 edges: Vec<MergeEdge>,
166}
167
168impl MergeGraph {
169 pub fn new() -> Self {
171 Self {
172 nodes: HashMap::new(),
173 edges: Vec::new(),
174 }
175 }
176
177 pub fn add_node(&mut self, node: MergeNode) {
179 self.nodes.insert(node.id, node);
180 }
181
182 pub fn add_edge(&mut self, edge: MergeEdge) {
184 self.edges.push(edge);
185 }
186
187 pub fn node_count(&self) -> usize {
189 self.nodes.len()
190 }
191
192 pub fn edge_count(&self) -> usize {
194 self.edges.len()
195 }
196
197 pub fn has_node(&self, id: MergeNodeId) -> bool {
199 self.nodes.contains_key(&id)
200 }
201
202 pub fn get_node(&self, id: MergeNodeId) -> Option<&MergeNode> {
204 self.nodes.get(&id)
205 }
206
207 pub fn node_ids(&self) -> Vec<MergeNodeId> {
209 let mut ids: Vec<MergeNodeId> = self.nodes.keys().copied().collect();
210 ids.sort();
211 ids
212 }
213
214 pub fn edges(&self) -> &[MergeEdge] {
216 &self.edges
217 }
218
219 pub fn next_id(&self) -> MergeNodeId {
221 let max = self.nodes.keys().map(|k| k.0).max().unwrap_or(0);
222 MergeNodeId(max + 1)
223 }
224
225 pub fn merge(
227 &mut self,
228 other: &MergeGraph,
229 config: &MergeConfig,
230 ) -> Result<MergeMapping, MergeError> {
231 let mut mapping = MergeMapping::new();
232
233 match config.conflict_strategy {
234 ConflictStrategy::Remap => {
235 let mut next_id = self.next_id().0;
236 for (&old_id, node) in &other.nodes {
237 if self.nodes.contains_key(&old_id) {
238 let new_id = MergeNodeId(next_id);
239 next_id += 1;
240 mapping.add(old_id, new_id);
241 let mut new_node = node.clone();
242 new_node.id = new_id;
243 self.nodes.insert(new_id, new_node);
244 } else {
245 mapping.add(old_id, old_id);
246 self.nodes.insert(old_id, node.clone());
247 }
248 }
249 }
250 ConflictStrategy::KeepFirst => {
251 for (&old_id, node) in &other.nodes {
252 self.nodes.entry(old_id).or_insert_with(|| node.clone());
253 mapping.add(old_id, old_id);
254 }
255 }
256 ConflictStrategy::KeepSecond => {
257 for (&old_id, node) in &other.nodes {
258 self.nodes.insert(old_id, node.clone());
259 mapping.add(old_id, old_id);
260 }
261 }
262 ConflictStrategy::Fail => {
263 for &old_id in other.nodes.keys() {
264 if self.nodes.contains_key(&old_id) {
265 return Err(MergeError::Conflict(old_id));
266 }
267 }
268 for (&old_id, node) in &other.nodes {
269 self.nodes.insert(old_id, node.clone());
270 mapping.add(old_id, old_id);
271 }
272 }
273 }
274
275 for edge in &other.edges {
277 let new_from = mapping.resolve(edge.from);
278 let new_to = mapping.resolve(edge.to);
279 let new_edge = MergeEdge {
280 from: new_from,
281 to: new_to,
282 label: edge.label.clone(),
283 weight: edge.weight,
284 };
285 self.edges.push(new_edge);
286 }
287
288 for &(from, to) in &config.cross_links {
290 self.edges.push(MergeEdge::new(from, to));
291 }
292
293 if config.deduplicate_edges {
295 self.deduplicate_edges();
296 }
297
298 Ok(mapping)
299 }
300
301 fn deduplicate_edges(&mut self) {
303 let mut seen: HashSet<(MergeNodeId, MergeNodeId)> = HashSet::new();
304 self.edges.retain(|e| seen.insert((e.from, e.to)));
305 }
306
307 pub fn edge_pairs(&self) -> HashSet<(MergeNodeId, MergeNodeId)> {
309 self.edges.iter().map(|e| (e.from, e.to)).collect()
310 }
311
312 pub fn adjacency_list(&self) -> HashMap<MergeNodeId, Vec<MergeNodeId>> {
314 let mut adj: HashMap<MergeNodeId, Vec<MergeNodeId>> = HashMap::new();
315 for id in self.nodes.keys() {
316 adj.entry(*id).or_default();
317 }
318 for edge in &self.edges {
319 adj.entry(edge.from).or_default().push(edge.to);
320 }
321 for list in adj.values_mut() {
322 list.sort();
323 }
324 adj
325 }
326}
327
328impl Default for MergeGraph {
329 fn default() -> Self {
330 Self::new()
331 }
332}
333
334#[derive(Debug, Clone)]
336pub struct MergeMapping {
337 map: HashMap<MergeNodeId, MergeNodeId>,
339}
340
341impl MergeMapping {
342 pub fn new() -> Self {
344 Self {
345 map: HashMap::new(),
346 }
347 }
348
349 pub fn add(&mut self, old_id: MergeNodeId, new_id: MergeNodeId) {
351 self.map.insert(old_id, new_id);
352 }
353
354 pub fn resolve(&self, old_id: MergeNodeId) -> MergeNodeId {
356 self.map.get(&old_id).copied().unwrap_or(old_id)
357 }
358
359 pub fn remap_count(&self) -> usize {
361 self.map.iter().filter(|(k, v)| k != v).count()
362 }
363
364 pub fn has_remaps(&self) -> bool {
366 self.remap_count() > 0
367 }
368
369 pub fn mappings(&self) -> &HashMap<MergeNodeId, MergeNodeId> {
371 &self.map
372 }
373}
374
375impl Default for MergeMapping {
376 fn default() -> Self {
377 Self::new()
378 }
379}
380
381#[cfg(test)]
382mod tests {
383 use super::*;
384
385 fn mn(id: usize) -> MergeNodeId {
386 MergeNodeId(id)
387 }
388
389 fn make_node(id: usize, label: &str) -> MergeNode {
390 MergeNode::new(mn(id), label)
391 }
392
393 #[test]
394 fn test_empty_graph() {
395 let graph = MergeGraph::new();
396 assert_eq!(graph.node_count(), 0);
397 assert_eq!(graph.edge_count(), 0);
398 }
399
400 #[test]
401 fn test_add_node_and_edge() {
402 let mut graph = MergeGraph::new();
403 graph.add_node(make_node(0, "A"));
404 graph.add_node(make_node(1, "B"));
405 graph.add_edge(MergeEdge::new(mn(0), mn(1)));
406 assert_eq!(graph.node_count(), 2);
407 assert_eq!(graph.edge_count(), 1);
408 }
409
410 #[test]
411 fn test_merge_no_conflict() {
412 let mut g1 = MergeGraph::new();
413 g1.add_node(make_node(0, "A"));
414 g1.add_node(make_node(1, "B"));
415 g1.add_edge(MergeEdge::new(mn(0), mn(1)));
416
417 let mut g2 = MergeGraph::new();
418 g2.add_node(make_node(2, "C"));
419 g2.add_node(make_node(3, "D"));
420 g2.add_edge(MergeEdge::new(mn(2), mn(3)));
421
422 let config = MergeConfig::default();
423 let mapping = g1.merge(&g2, &config).expect("merge should succeed");
424 assert_eq!(g1.node_count(), 4);
425 assert_eq!(g1.edge_count(), 2);
426 assert!(!mapping.has_remaps());
427 }
428
429 #[test]
430 fn test_merge_with_remap() {
431 let mut g1 = MergeGraph::new();
432 g1.add_node(make_node(0, "A"));
433
434 let mut g2 = MergeGraph::new();
435 g2.add_node(make_node(0, "B")); let config = MergeConfig {
438 conflict_strategy: ConflictStrategy::Remap,
439 ..Default::default()
440 };
441 let mapping = g1.merge(&g2, &config).expect("merge should succeed");
442 assert_eq!(g1.node_count(), 2);
443 assert!(mapping.has_remaps());
444 }
445
446 #[test]
447 fn test_merge_keep_first() {
448 let mut g1 = MergeGraph::new();
449 g1.add_node(make_node(0, "A"));
450
451 let mut g2 = MergeGraph::new();
452 g2.add_node(make_node(0, "B"));
453
454 let config = MergeConfig {
455 conflict_strategy: ConflictStrategy::KeepFirst,
456 ..Default::default()
457 };
458 g1.merge(&g2, &config).expect("merge should succeed");
459 assert_eq!(g1.node_count(), 1);
460 assert_eq!(
461 g1.get_node(mn(0)).expect("value should be valid").label,
462 "A"
463 );
464 }
465
466 #[test]
467 fn test_merge_keep_second() {
468 let mut g1 = MergeGraph::new();
469 g1.add_node(make_node(0, "A"));
470
471 let mut g2 = MergeGraph::new();
472 g2.add_node(make_node(0, "B"));
473
474 let config = MergeConfig {
475 conflict_strategy: ConflictStrategy::KeepSecond,
476 ..Default::default()
477 };
478 g1.merge(&g2, &config).expect("merge should succeed");
479 assert_eq!(g1.node_count(), 1);
480 assert_eq!(
481 g1.get_node(mn(0)).expect("value should be valid").label,
482 "B"
483 );
484 }
485
486 #[test]
487 fn test_merge_fail_on_conflict() {
488 let mut g1 = MergeGraph::new();
489 g1.add_node(make_node(0, "A"));
490
491 let mut g2 = MergeGraph::new();
492 g2.add_node(make_node(0, "B"));
493
494 let config = MergeConfig {
495 conflict_strategy: ConflictStrategy::Fail,
496 ..Default::default()
497 };
498 let result = g1.merge(&g2, &config);
499 assert!(matches!(result, Err(MergeError::Conflict(_))));
500 }
501
502 #[test]
503 fn test_merge_with_cross_links() {
504 let mut g1 = MergeGraph::new();
505 g1.add_node(make_node(0, "A"));
506
507 let mut g2 = MergeGraph::new();
508 g2.add_node(make_node(1, "B"));
509
510 let config = MergeConfig {
511 cross_links: vec![(mn(0), mn(1))],
512 ..Default::default()
513 };
514 g1.merge(&g2, &config).expect("merge should succeed");
515 assert_eq!(g1.edge_count(), 1);
516 }
517
518 #[test]
519 fn test_edge_deduplication() {
520 let mut graph = MergeGraph::new();
521 graph.add_node(make_node(0, "A"));
522 graph.add_node(make_node(1, "B"));
523 graph.add_edge(MergeEdge::new(mn(0), mn(1)));
524 graph.add_edge(MergeEdge::new(mn(0), mn(1)));
525 graph.deduplicate_edges();
526 assert_eq!(graph.edge_count(), 1);
527 }
528
529 #[test]
530 fn test_edge_pairs() {
531 let mut graph = MergeGraph::new();
532 graph.add_node(make_node(0, "A"));
533 graph.add_node(make_node(1, "B"));
534 graph.add_edge(MergeEdge::new(mn(0), mn(1)));
535 let pairs = graph.edge_pairs();
536 assert!(pairs.contains(&(mn(0), mn(1))));
537 }
538
539 #[test]
540 fn test_adjacency_list() {
541 let mut graph = MergeGraph::new();
542 graph.add_node(make_node(0, "A"));
543 graph.add_node(make_node(1, "B"));
544 graph.add_node(make_node(2, "C"));
545 graph.add_edge(MergeEdge::new(mn(0), mn(1)));
546 graph.add_edge(MergeEdge::new(mn(0), mn(2)));
547 let adj = graph.adjacency_list();
548 assert_eq!(adj[&mn(0)], vec![mn(1), mn(2)]);
549 }
550
551 #[test]
552 fn test_node_metadata() {
553 let node = make_node(0, "Test").with_metadata("type", "source");
554 assert_eq!(node.metadata.get("type"), Some(&"source".to_string()));
555 }
556
557 #[test]
558 fn test_edge_with_label() {
559 let edge = MergeEdge::with_label(mn(0), mn(1), "data_flow");
560 assert_eq!(edge.label, Some("data_flow".to_string()));
561 }
562
563 #[test]
564 fn test_edge_weight() {
565 let edge = MergeEdge::new(mn(0), mn(1)).with_weight(5);
566 assert_eq!(edge.weight, 5);
567 }
568
569 #[test]
570 fn test_merge_mapping_resolve() {
571 let mut mapping = MergeMapping::new();
572 mapping.add(mn(0), mn(10));
573 assert_eq!(mapping.resolve(mn(0)), mn(10));
574 assert_eq!(mapping.resolve(mn(5)), mn(5)); }
576
577 #[test]
578 fn test_merge_error_display() {
579 let err = MergeError::Conflict(mn(5));
580 assert!(format!("{err}").contains("5"));
581 let err2 = MergeError::EmptyGraphs;
582 assert_eq!(format!("{err2}"), "Both graphs are empty");
583 }
584
585 #[test]
586 fn test_next_id() {
587 let mut graph = MergeGraph::new();
588 graph.add_node(make_node(0, "A"));
589 graph.add_node(make_node(5, "B"));
590 assert_eq!(graph.next_id(), mn(6));
591 }
592
593 #[test]
594 fn test_has_node() {
595 let mut graph = MergeGraph::new();
596 graph.add_node(make_node(0, "A"));
597 assert!(graph.has_node(mn(0)));
598 assert!(!graph.has_node(mn(1)));
599 }
600}