Skip to main content

ff_structure/
pair_table.rs

1//! PairTable construction and helper traits.
2
3use std::ops::{Deref, DerefMut, Index, IndexMut};
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
45// Implementing indexing for NAIDX and usize allows users to use BOTH types for indexing the
46// PairTable, circumvents casting "index as usize" everywhere in the code, and makes the API more
47// ergonomic. The internal implementation still uses usize, so we just cast under the hood. This
48// way, users can use NAIDX indexing without worrying about the internal representation.
49impl Index<NAIDX> for PairTable {
50    type Output = Option<NAIDX>;
51
52    fn index(&self, index: NAIDX) -> &Self::Output {
53        // We cast to usize here under the hood, so we never 
54        // have to think about it again when using the struct.
55        &self.0[index as usize]
56    }
57}
58
59impl IndexMut<NAIDX> for PairTable {
60    fn index_mut(&mut self, index: NAIDX) -> &mut Self::Output {
61        &mut self.0[index as usize]
62    }
63}
64
65impl Index<usize> for PairTable {
66    type Output = Option<NAIDX>;
67
68    fn index(&self, index: usize) -> &Self::Output {
69        &self.0[index]
70    }
71}
72
73impl IndexMut<usize> for PairTable {
74    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
75        &mut self.0[index]
76    }
77}
78
79impl TryFrom<&str> for PairTable {
80    type Error = StructureError;
81
82    fn try_from(s: &str) -> Result<Self, Self::Error> {
83        let mut stack = Vec::new();
84        let mut table = vec![None; s.len()];
85
86        for (i, c) in s.chars().enumerate() {
87            match c {
88                '(' => stack.push(i),
89                ')' => {
90                    let j = stack.pop().ok_or(StructureError::UnmatchedClose(i))?;
91                    table[i] = Some(j as NAIDX);
92                    table[j] = Some(i as NAIDX);
93                }
94                '.' => (),
95                _ => return Err(StructureError::InvalidToken(format!("character '{}'", c), "structure".to_string(), i)),
96            }
97        }
98
99        if let Some(i) = stack.pop() {
100            return Err(StructureError::UnmatchedOpen(i));
101        }
102        Ok(PairTable(table))
103    }
104}
105
106impl TryFrom<&DotBracketVec> for PairTable {
107    type Error = StructureError;
108
109    fn try_from(db: &DotBracketVec) -> Result<Self, Self::Error> {
110        let mut stack = Vec::new();
111        let mut table = vec![None; db.len()];
112
113        for (i, dot) in db.iter().enumerate() {
114            match dot {
115                DotBracket::Open => stack.push(i),
116                DotBracket::Close => {
117                    let j = stack.pop().ok_or(StructureError::UnmatchedClose(i))?;
118                    table[i] = Some(j as NAIDX);
119                    table[j] = Some(i as NAIDX);
120                }
121                DotBracket::Unpaired => {}
122                DotBracket::Break => unreachable!("unexpected Break in single-stranded case"),
123            }
124        }
125
126        if let Some(i) = stack.pop() {
127            return Err(StructureError::UnmatchedOpen(i));
128        }
129
130        Ok(PairTable(table))
131    }
132}
133
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138
139    #[test]
140    fn test_valid_pair_table() {
141        let pt = PairTable::try_from("((..))").unwrap();
142        assert_eq!(pt.len(), 6);
143        assert_eq!(pt[0 as NAIDX], Some(5));
144        assert_eq!(pt[1 as NAIDX], Some(4));
145        assert_eq!(pt[2 as NAIDX], None);
146        assert_eq!(pt[3 as NAIDX], None);
147        assert_eq!(pt[4 as NAIDX], Some(1));
148        assert_eq!(pt[5 as NAIDX], Some(0));
149    }
150
151    #[test]
152    fn test_unmatched_open() {
153        let err = PairTable::try_from("(()").unwrap_err();
154        assert_eq!(format!("{}", err), "Unmatched '(' at position 0");
155    }
156
157    #[test]
158    fn test_unmatched_close() {
159        let err = PairTable::try_from("())").unwrap_err();
160        assert_eq!(format!("{}", err), "Unmatched ')' at position 2");
161    }
162
163    #[test]
164    fn test_invalid_token() {
165        let err = PairTable::try_from("(x)").unwrap_err();
166        assert_eq!(format!("{}", err), "Invalid character 'x' in structure at position 1");
167    }
168
169    #[test]
170    fn test_well_formed_empty_interval() {
171        let pt= PairTable::try_from("...").unwrap();
172        assert!(pt.is_well_formed(0, 0)); 
173        assert!(pt.is_well_formed(0, 1)); 
174        assert!(pt.is_well_formed(0, 2)); 
175        assert!(pt.is_well_formed(0, 3)); 
176        assert!(pt.is_well_formed(1, 3)); 
177        assert!(pt.is_well_formed(2, 3)); 
178        assert!(pt.is_well_formed(3, 3)); 
179    }
180
181    #[test]
182    fn test_well_formed_pairings_within_interval() {
183        let pt = PairTable::try_from(".(.).").unwrap();
184        assert!(pt.is_well_formed(0, 5)); // Full interval -- 0-based
185        assert!(pt.is_well_formed(0, 4)); 
186        assert!(pt.is_well_formed(1, 5));
187        assert!(pt.is_well_formed(1, 4));
188        assert!(pt.is_well_formed(1, 4));
189        assert!(pt.is_well_formed(2, 3));
190        assert!(!pt.is_well_formed(0, 3)); 
191        assert!(!pt.is_well_formed(1, 3)); 
192        assert!(!pt.is_well_formed(2, 4)); 
193    }
194
195    #[test]
196    #[should_panic(expected = "Invalid interval: j must be <= length")]
197    fn test_well_formed_out_of_bounds_assert() {
198        let pt = PairTable::try_from("..").unwrap();
199        pt.is_well_formed(0, 3); // j = pt.len(), should panic
200    }
201}
202
203
204