Skip to main content

use_edge/
lib.rs

1#![forbid(unsafe_code)]
2//! Primitive edge helpers.
3//!
4//! The crate keeps edge handling explicit with small structs for generic,
5//! directed, and undirected edges.
6//!
7//! # Examples
8//!
9//! ```rust
10//! use use_edge::{Edge, UndirectedEdge, dedupe_edges, has_self_loop};
11//!
12//! let deduped = dedupe_edges(&[Edge::new(0, 1), Edge::new(0, 1), Edge::new(1, 1)]);
13//! let undirected = UndirectedEdge::new(2, 1);
14//!
15//! assert_eq!(deduped, vec![Edge::new(0, 1), Edge::new(1, 1)]);
16//! assert_eq!(undirected.source(), 1);
17//! assert!(has_self_loop(Edge::new(1, 1)));
18//! ```
19
20use 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}