ff_structure/
pair_table.rs

1//! PairTable construction and helper traits.
2
3use std::ops::{Deref, DerefMut};
4use std::convert::TryFrom;
5use crate::NAIDX;
6use crate::StructureError;
7use crate::{DotBracket, DotBracketVec};
8
9/// As of v0.1.3 the PairTable field is private. A pair-table should
10/// be constructed by From or TryFrom traits, but then be save to use.
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct PairTable(Vec<Option<NAIDX>>);
13
14impl PairTable {
15    /// Check if the substructure from `i..j` is well-formed:
16    /// - All pairings are internal to the interval
17    pub fn is_well_formed(&self, i: usize, j: usize) -> bool {
18        assert!(j <= self.len(), "Invalid interval: j must be <= length");
19
20        for k in i..j {
21            if let Some(l) = self[k] {
22                let ul = l as usize;
23                if ul < i || ul >= j {
24                    return false; // points outside
25                }
26            }
27        }
28        true
29    }
30}
31
32impl Deref for PairTable {
33    type Target = [Option<NAIDX>];
34    fn deref(&self) -> &Self::Target {
35        &self.0
36    }
37}
38
39impl DerefMut for PairTable {
40    fn deref_mut(&mut self) -> &mut Self::Target {
41        &mut self.0
42    }
43}
44
45impl TryFrom<&str> for PairTable {
46    type Error = StructureError;
47
48    fn try_from(s: &str) -> Result<Self, Self::Error> {
49        let mut stack = Vec::new();
50        let mut table = vec![None; s.len()];
51
52        for (i, c) in s.chars().enumerate() {
53            match c {
54                '(' => stack.push(i),
55                ')' => {
56                    let j = stack.pop().ok_or(StructureError::UnmatchedClose(i))?;
57                    table[i] = Some(j as NAIDX);
58                    table[j] = Some(i as NAIDX);
59                }
60                '.' => (),
61                _ => return Err(StructureError::InvalidToken(format!("character '{}'", c), "structure".to_string(), i)),
62            }
63        }
64
65        if let Some(i) = stack.pop() {
66            return Err(StructureError::UnmatchedOpen(i));
67        }
68        Ok(PairTable(table))
69    }
70}
71
72impl TryFrom<&DotBracketVec> for PairTable {
73    type Error = StructureError;
74
75    fn try_from(db: &DotBracketVec) -> Result<Self, Self::Error> {
76        let mut stack = Vec::new();
77        let mut table = vec![None; db.len()];
78
79        for (i, dot) in db.iter().enumerate() {
80            match dot {
81                DotBracket::Open => stack.push(i),
82                DotBracket::Close => {
83                    let j = stack.pop().ok_or(StructureError::UnmatchedClose(i))?;
84                    table[i] = Some(j as NAIDX);
85                    table[j] = Some(i as NAIDX);
86                }
87                DotBracket::Unpaired => {}
88                DotBracket::Break => unreachable!("unexpected Break in single-stranded case"),
89            }
90        }
91
92        if let Some(i) = stack.pop() {
93            return Err(StructureError::UnmatchedOpen(i));
94        }
95
96        Ok(PairTable(table))
97    }
98}
99
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104
105    #[test]
106    fn test_valid_pair_table() {
107        let pt = PairTable::try_from("((..))").unwrap();
108        assert_eq!(pt.len(), 6);
109        assert_eq!(pt[0], Some(5));
110        assert_eq!(pt[1], Some(4));
111        assert_eq!(pt[2], None);
112        assert_eq!(pt[3], None);
113        assert_eq!(pt[4], Some(1));
114        assert_eq!(pt[5], Some(0));
115    }
116
117    #[test]
118    fn test_unmatched_open() {
119        let err = PairTable::try_from("(()").unwrap_err();
120        assert_eq!(format!("{}", err), "Unmatched '(' at position 0");
121    }
122
123    #[test]
124    fn test_unmatched_close() {
125        let err = PairTable::try_from("())").unwrap_err();
126        assert_eq!(format!("{}", err), "Unmatched ')' at position 2");
127    }
128
129    #[test]
130    fn test_invalid_token() {
131        let err = PairTable::try_from("(x)").unwrap_err();
132        assert_eq!(format!("{}", err), "Invalid character 'x' in structure at position 1");
133    }
134
135    #[test]
136    fn test_well_formed_empty_interval() {
137        let pt= PairTable::try_from("...").unwrap();
138        assert!(pt.is_well_formed(0, 0)); 
139        assert!(pt.is_well_formed(0, 1)); 
140        assert!(pt.is_well_formed(0, 2)); 
141        assert!(pt.is_well_formed(0, 3)); 
142        assert!(pt.is_well_formed(1, 3)); 
143        assert!(pt.is_well_formed(2, 3)); 
144        assert!(pt.is_well_formed(3, 3)); 
145    }
146
147    #[test]
148    fn test_well_formed_pairings_within_interval() {
149        let pt = PairTable::try_from(".(.).").unwrap();
150        assert!(pt.is_well_formed(0, 5)); // Full interval -- 0-based
151        assert!(pt.is_well_formed(0, 4)); 
152        assert!(pt.is_well_formed(1, 5));
153        assert!(pt.is_well_formed(1, 4));
154        assert!(pt.is_well_formed(1, 4));
155        assert!(pt.is_well_formed(2, 3));
156        assert!(!pt.is_well_formed(0, 3)); 
157        assert!(!pt.is_well_formed(1, 3)); 
158        assert!(!pt.is_well_formed(2, 4)); 
159    }
160
161    #[test]
162    #[should_panic(expected = "Invalid interval: j must be <= length")]
163    fn test_well_formed_out_of_bounds_assert() {
164        let pt = PairTable::try_from("..").unwrap();
165        pt.is_well_formed(0, 3); // j = pt.len(), should panic
166    }
167}
168
169
170