1#![forbid(unsafe_code)]
2use std::collections::HashSet;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
23pub struct Edge {
24 pub source: usize,
25 pub target: usize,
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
29pub struct DirectedEdge {
30 pub source: usize,
31 pub target: usize,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
35pub struct UndirectedEdge {
36 pub a: usize,
37 pub b: usize,
38}
39
40impl Edge {
41 #[must_use]
42 pub const fn new(source: usize, target: usize) -> Self {
43 Self { source, target }
44 }
45
46 #[must_use]
47 pub const fn source(&self) -> usize {
48 self.source
49 }
50
51 #[must_use]
52 pub const fn target(&self) -> usize {
53 self.target
54 }
55
56 #[must_use]
57 pub const fn contains(&self, node: usize) -> bool {
58 self.source == node || self.target == node
59 }
60
61 #[must_use]
62 pub const fn reversed(&self) -> Self {
63 Self::new(self.target, self.source)
64 }
65}
66
67impl DirectedEdge {
68 #[must_use]
69 pub const fn new(source: usize, target: usize) -> Self {
70 Self { source, target }
71 }
72
73 #[must_use]
74 pub const fn source(&self) -> usize {
75 self.source
76 }
77
78 #[must_use]
79 pub const fn target(&self) -> usize {
80 self.target
81 }
82
83 #[must_use]
84 pub const fn contains(&self, node: usize) -> bool {
85 self.source == node || self.target == node
86 }
87
88 #[must_use]
89 pub const fn reversed(&self) -> Self {
90 Self::new(self.target, self.source)
91 }
92}
93
94impl UndirectedEdge {
95 #[must_use]
96 pub fn new(a: usize, b: usize) -> Self {
97 if a <= b {
98 Self { a, b }
99 } else {
100 Self { a: b, b: a }
101 }
102 }
103
104 #[must_use]
105 pub const fn source(&self) -> usize {
106 self.a
107 }
108
109 #[must_use]
110 pub const fn target(&self) -> usize {
111 self.b
112 }
113
114 #[must_use]
115 pub const fn contains(&self, node: usize) -> bool {
116 self.a == node || self.b == node
117 }
118
119 #[must_use]
120 pub fn reversed(&self) -> Self {
121 Self::new(self.b, self.a)
122 }
123}
124
125#[must_use]
126pub fn dedupe_edges(edges: &[Edge]) -> Vec<Edge> {
127 let mut seen = HashSet::with_capacity(edges.len());
128 let mut unique = Vec::with_capacity(edges.len());
129
130 for &edge in edges {
131 if seen.insert(edge) {
132 unique.push(edge);
133 }
134 }
135
136 unique
137}
138
139#[must_use]
140pub const fn has_self_loop(edge: Edge) -> bool {
141 edge.source == edge.target
142}
143
144#[cfg(test)]
145mod tests {
146 use super::{dedupe_edges, has_self_loop, DirectedEdge, Edge, UndirectedEdge};
147
148 #[test]
149 fn constructs_and_reverses_edges() {
150 let edge = Edge::new(0, 1);
151 let directed = DirectedEdge::new(1, 2);
152
153 assert_eq!(edge.source(), 0);
154 assert_eq!(edge.target(), 1);
155 assert_eq!(edge.reversed(), Edge::new(1, 0));
156 assert!(directed.contains(2));
157 assert_eq!(directed.reversed(), DirectedEdge::new(2, 1));
158 }
159
160 #[test]
161 fn normalizes_undirected_edges() {
162 let first = UndirectedEdge::new(1, 2);
163 let second = UndirectedEdge::new(2, 1);
164
165 assert_eq!(first, second);
166 assert_eq!(first.source(), 1);
167 assert_eq!(first.target(), 2);
168 assert_eq!(first.reversed(), first);
169 }
170
171 #[test]
172 fn deduplicates_edges_and_detects_self_loops() {
173 let edges = [Edge::new(0, 1), Edge::new(0, 1), Edge::new(1, 1)];
174
175 assert_eq!(dedupe_edges(&edges), vec![Edge::new(0, 1), Edge::new(1, 1)]);
176 assert!(has_self_loop(Edge::new(1, 1)));
177 assert!(!has_self_loop(Edge::new(0, 1)));
178 }
179}