Skip to main content

yui_link/link/
path.rs

1//! [`Path`]: a connected component of a diagram, either an arc or a circle,
2//! held as an oriented sequence of edges.
3
4use std::fmt::Display;
5
6use smallvec::SmallVec;
7
8use crate::Edge;
9
10/// Inline capacity for the variable-length `Path::Arc`/`Path::Circ` variants.
11/// With `Edge = u8`, the 16-byte inline buffer (= heap repr's ptr+cap) fits
12/// 16 elements — same struct size as `Vec<Edge>`, but skips allocation for
13/// short paths.
14pub type PathEdges = SmallVec<[Edge; 16]>;
15
16/// An *oriented* connected component of a tangle: either an arc (`Arc`) or a
17/// closed loop (`Circ`). `Path` compares equal as oriented edge sequences.
18#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
19pub enum Path {
20    Arc(PathEdges),
21    Circ(PathEdges),
22}
23
24impl Path {
25    pub fn arc<I>(edges: I) -> Self
26    where I: IntoIterator<Item = Edge> {
27        let edges: PathEdges = edges.into_iter().collect();
28        assert!(!edges.is_empty());
29        Self::Arc(edges)
30    }
31
32    pub fn circ<I>(edges: I) -> Self
33    where I: IntoIterator<Item = Edge> {
34        let edges: PathEdges = edges.into_iter().collect();
35        assert!(!edges.is_empty());
36        Self::Circ(edges)
37    }
38
39    pub fn is_arc(&self) -> bool { matches!(self, Self::Arc(_)) }
40    pub fn is_circle(&self) -> bool { matches!(self, Self::Circ(_)) }
41
42    pub fn contains(&self, e: Edge) -> bool {
43        match self {
44            Self::Arc(es) | Self::Circ(es) => es.contains(&e),
45        }
46    }
47
48    pub fn len(&self) -> usize {
49        match self {
50            Self::Arc(es) | Self::Circ(es) => es.len(),
51        }
52    }
53
54    pub fn edges(&self) -> &[Edge] {
55        match self {
56            Self::Arc(es) | Self::Circ(es) => &es[..],
57        }
58    }
59
60    pub fn min_edge(&self) -> Edge {
61        match self {
62            Self::Arc(es) | Self::Circ(es) => *es.iter().min().unwrap(),
63        }
64    }
65
66    pub fn end_pts(&self) -> Option<(Edge, Edge)> {
67        match self {
68            Self::Arc(es) => Some((es[0], *es.last().unwrap())),
69            Self::Circ(_) => None,
70        }
71    }
72
73    pub fn into_seq(self) -> PathEdges {
74        match self {
75            Self::Arc(es) | Self::Circ(es) => es,
76        }
77    }
78}
79
80impl Display for Path {
81    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82        let c = self.edges().iter().map(|e| e.to_string()).collect::<Vec<_>>().join("-");
83        if self.is_circle() {
84            write!(f, "⚪︎({c})")
85        } else {
86            write!(f, "[{c}]")
87        }
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    #[test]
96    fn arc_and_circ() {
97        let a = Path::arc([1, 2, 3]);
98        assert!(a.is_arc());
99        assert!(!a.is_circle());
100        assert_eq!(a.len(), 3);
101        assert_eq!(a.edges(), &[1, 2, 3]);
102
103        let c = Path::circ([1, 2, 3]);
104        assert!(c.is_circle());
105        assert!(!c.is_arc());
106        assert_eq!(c.edges(), a.edges());
107
108        // same edges, different kind — not equal.
109        assert_ne!(a, c);
110    }
111
112    #[test]
113    #[should_panic]
114    fn arc_rejects_empty() {
115        let _ = Path::arc([]);
116    }
117
118    #[test]
119    #[should_panic]
120    fn circ_rejects_empty() {
121        let _ = Path::circ([]);
122    }
123
124    #[test]
125    fn contains_and_min_edge() {
126        let p = Path::arc([4, 2, 7]);
127        assert!(p.contains(2));
128        assert!(!p.contains(3));
129        assert_eq!(p.min_edge(), 2);
130    }
131
132    #[test]
133    fn end_pts_only_for_arcs() {
134        assert_eq!(Path::arc([4, 2, 7]).end_pts(), Some((4, 7)));
135        assert_eq!(Path::arc([5]).end_pts(), Some((5, 5)));
136        assert_eq!(Path::circ([4, 2, 7]).end_pts(), None);
137    }
138
139    #[test]
140    fn equality_is_oriented() {
141        // `Path` compares as an oriented sequence: neither reversal nor rotation is equal.
142        let p = Path::circ([1, 2, 3]);
143        assert_ne!(p, Path::circ([3, 2, 1]));
144        assert_ne!(p, Path::circ([2, 3, 1]));
145    }
146
147    #[test]
148    fn into_seq_keeps_the_order() {
149        assert_eq!(Path::circ([4, 2, 7]).into_seq().to_vec(), vec![4, 2, 7]);
150    }
151
152    #[test]
153    fn display() {
154        assert_eq!(Path::arc([1, 2, 3]).to_string(),  "[1-2-3]");
155        assert_eq!(Path::circ([1, 2, 3]).to_string(), "⚪︎(1-2-3)");
156    }
157}