use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PositionError {
InvalidIPosition {
offset: i32,
errors: u8,
max_distance: u8,
},
InvalidMPosition {
offset: i32,
errors: u8,
max_distance: u8,
},
}
impl fmt::Display for PositionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PositionError::InvalidIPosition {
offset,
errors,
max_distance,
} => {
write!(
f,
"Invalid I-position: I + {}#{} with n={}. \
Invariant: |t| ≤ k ∧ -n ≤ t ≤ n ∧ 0 ≤ k ≤ n",
offset, errors, max_distance
)
}
PositionError::InvalidMPosition {
offset,
errors,
max_distance,
} => {
write!(
f,
"Invalid M-position: M + {}#{} with n={}. \
Invariant: k ≥ -t - n ∧ -2n ≤ t ≤ 0 ∧ 0 ≤ k ≤ n",
offset, errors, max_distance
)
}
}
}
}
impl std::error::Error for PositionError {}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum GeneralizedPosition {
INonFinal {
offset: i32,
errors: u8,
},
MFinal {
offset: i32,
errors: u8,
},
ITransposing {
offset: i32,
errors: u8,
},
MTransposing {
offset: i32,
errors: u8,
},
ISplitting {
offset: i32,
errors: u8,
entry_char: char,
},
MSplitting {
offset: i32,
errors: u8,
entry_char: char,
},
}
impl PartialOrd for GeneralizedPosition {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for GeneralizedPosition {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
use std::cmp::Ordering;
use GeneralizedPosition::*;
let variant_priority = |pos: &GeneralizedPosition| match pos {
INonFinal { .. } => 0,
ITransposing { .. } => 1,
ISplitting { .. } => 2,
MFinal { .. } => 3,
MTransposing { .. } => 4,
MSplitting { .. } => 5,
};
let p1 = variant_priority(self);
let p2 = variant_priority(other);
match p1.cmp(&p2) {
Ordering::Equal => {
let e1 = self.errors();
let e2 = other.errors();
match e1.cmp(&e2) {
Ordering::Equal => self.offset().cmp(&other.offset()),
other => other,
}
}
other => other,
}
}
}
impl GeneralizedPosition {
pub fn new_i(offset: i32, errors: u8, max_distance: u8) -> Result<Self, PositionError> {
let n = max_distance as i32;
let invariant_satisfied = if errors == 0 && offset > 0 {
true
} else {
offset.abs() <= errors as i32 && offset >= -n && offset <= n && errors <= max_distance
};
if invariant_satisfied {
Ok(GeneralizedPosition::INonFinal { offset, errors })
} else {
Err(PositionError::InvalidIPosition {
offset,
errors,
max_distance,
})
}
}
pub fn new_m(offset: i32, errors: u8, max_distance: u8) -> Result<Self, PositionError> {
let n = max_distance as i32;
if errors as i32 >= -offset - n && offset >= -2 * n && offset <= 0 && errors <= max_distance
{
Ok(GeneralizedPosition::MFinal { offset, errors })
} else {
Err(PositionError::InvalidMPosition {
offset,
errors,
max_distance,
})
}
}
pub fn new_i_transposing(
offset: i32,
errors: u8,
max_distance: u8,
) -> Result<Self, PositionError> {
let n = max_distance as i32;
let invariant_satisfied = if errors == 0 && offset < 0 {
offset >= -n && errors <= max_distance
} else {
offset.abs() <= errors as i32 && offset >= -n && offset <= n && errors <= max_distance
};
if invariant_satisfied {
Ok(GeneralizedPosition::ITransposing { offset, errors })
} else {
Err(PositionError::InvalidIPosition {
offset,
errors,
max_distance,
})
}
}
pub fn new_m_transposing(
offset: i32,
errors: u8,
max_distance: u8,
) -> Result<Self, PositionError> {
let n = max_distance as i32;
if errors as i32 >= -offset - n && offset >= -2 * n && offset <= 0 && errors <= max_distance
{
Ok(GeneralizedPosition::MTransposing { offset, errors })
} else {
Err(PositionError::InvalidMPosition {
offset,
errors,
max_distance,
})
}
}
pub fn new_i_splitting(
offset: i32,
errors: u8,
max_distance: u8,
entry_char: char,
) -> Result<Self, PositionError> {
let n = max_distance as i32;
let invariant_satisfied = offset.abs() <= (errors as i32 + 1)
&& offset >= -n
&& offset <= n
&& errors <= max_distance;
if invariant_satisfied {
Ok(GeneralizedPosition::ISplitting {
offset,
errors,
entry_char,
})
} else {
Err(PositionError::InvalidIPosition {
offset,
errors,
max_distance,
})
}
}
pub fn new_m_splitting(
offset: i32,
errors: u8,
max_distance: u8,
entry_char: char,
) -> Result<Self, PositionError> {
let n = max_distance as i32;
if (errors as i32 + 1) >= -offset - n
&& offset >= -2 * n
&& offset <= 0
&& errors <= max_distance
{
Ok(GeneralizedPosition::MSplitting {
offset,
errors,
entry_char,
})
} else {
Err(PositionError::InvalidMPosition {
offset,
errors,
max_distance,
})
}
}
pub fn offset(&self) -> i32 {
match self {
GeneralizedPosition::INonFinal { offset, .. }
| GeneralizedPosition::MFinal { offset, .. }
| GeneralizedPosition::ITransposing { offset, .. }
| GeneralizedPosition::MTransposing { offset, .. }
| GeneralizedPosition::ISplitting { offset, .. }
| GeneralizedPosition::MSplitting { offset, .. } => *offset,
}
}
pub fn errors(&self) -> u8 {
match self {
GeneralizedPosition::INonFinal { errors, .. }
| GeneralizedPosition::MFinal { errors, .. }
| GeneralizedPosition::ITransposing { errors, .. }
| GeneralizedPosition::MTransposing { errors, .. }
| GeneralizedPosition::ISplitting { errors, .. }
| GeneralizedPosition::MSplitting { errors, .. } => *errors,
}
}
pub fn is_non_final(&self) -> bool {
matches!(
self,
GeneralizedPosition::INonFinal { .. }
| GeneralizedPosition::ITransposing { .. }
| GeneralizedPosition::ISplitting { .. }
)
}
pub fn is_final(&self) -> bool {
matches!(
self,
GeneralizedPosition::MFinal { .. }
| GeneralizedPosition::MTransposing { .. }
| GeneralizedPosition::MSplitting { .. }
)
}
}
impl fmt::Display for GeneralizedPosition {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
GeneralizedPosition::INonFinal { offset, errors } => {
write!(f, "I + {}#{}", offset, errors)
}
GeneralizedPosition::MFinal { offset, errors } => {
write!(f, "M + {}#{}", offset, errors)
}
GeneralizedPosition::ITransposing { offset, errors } => {
write!(f, "I + {}#{}_t", offset, errors)
}
GeneralizedPosition::MTransposing { offset, errors } => {
write!(f, "M + {}#{}_t", offset, errors)
}
GeneralizedPosition::ISplitting { offset, errors, .. } => {
write!(f, "I + {}#{}_s", offset, errors)
}
GeneralizedPosition::MSplitting { offset, errors, .. } => {
write!(f, "M + {}#{}_s", offset, errors)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_i_valid() {
let pos = GeneralizedPosition::new_i(0, 0, 2)
.expect("test fixture: GeneralizedPosition::new_i with valid args");
assert_eq!(pos.offset(), 0);
assert_eq!(pos.errors(), 0);
assert!(pos.is_non_final());
let pos = GeneralizedPosition::new_i(1, 1, 2)
.expect("test fixture: GeneralizedPosition::new_i with valid args");
assert_eq!(pos.offset(), 1);
assert_eq!(pos.errors(), 1);
let pos = GeneralizedPosition::new_i(-2, 2, 2)
.expect("test fixture: GeneralizedPosition::new_i with valid args");
assert_eq!(pos.offset(), -2);
assert_eq!(pos.errors(), 2);
}
#[test]
fn test_new_i_invalid() {
assert!(GeneralizedPosition::new_i(2, 1, 2).is_err());
assert!(GeneralizedPosition::new_i(3, 3, 2).is_err());
assert!(GeneralizedPosition::new_i(-3, 3, 2).is_err());
assert!(GeneralizedPosition::new_i(0, 3, 2).is_err());
}
#[test]
fn test_new_m_valid() {
let pos = GeneralizedPosition::new_m(0, 0, 2)
.expect("test fixture: GeneralizedPosition::new_m with valid args");
assert_eq!(pos.offset(), 0);
assert_eq!(pos.errors(), 0);
assert!(pos.is_final());
let pos = GeneralizedPosition::new_m(-1, 1, 2)
.expect("test fixture: GeneralizedPosition::new_m with valid args");
assert_eq!(pos.offset(), -1);
assert_eq!(pos.errors(), 1);
let pos = GeneralizedPosition::new_m(-4, 2, 2)
.expect("test fixture: GeneralizedPosition::new_m with valid args");
assert_eq!(pos.offset(), -4);
assert_eq!(pos.errors(), 2);
}
#[test]
fn test_new_m_invalid() {
assert!(GeneralizedPosition::new_m(-4, 1, 2).is_err());
assert!(GeneralizedPosition::new_m(1, 1, 2).is_err());
assert!(GeneralizedPosition::new_m(-5, 2, 2).is_err());
assert!(GeneralizedPosition::new_m(-1, 3, 2).is_err());
}
#[test]
fn test_ordering() {
let pos1 = GeneralizedPosition::new_i(0, 0, 2)
.expect("test fixture: GeneralizedPosition::new_i with valid args");
let pos2 = GeneralizedPosition::new_i(1, 1, 2)
.expect("test fixture: GeneralizedPosition::new_i with valid args");
let pos3 = GeneralizedPosition::new_i(-1, 1, 2)
.expect("test fixture: GeneralizedPosition::new_i with valid args");
let pos4 = GeneralizedPosition::new_m(0, 0, 2)
.expect("test fixture: GeneralizedPosition::new_m with valid args");
assert!(pos1 < pos2);
assert!(pos1 < pos3);
assert!(pos3 < pos2);
assert!(pos1 < pos4);
assert!(pos2 < pos4);
}
#[test]
fn test_display() {
let pos1 = GeneralizedPosition::new_i(1, 2, 3)
.expect("test fixture: GeneralizedPosition::new_i with valid args");
assert_eq!(format!("{}", pos1), "I + 1#2");
let pos2 = GeneralizedPosition::new_m(-1, 1, 2)
.expect("test fixture: GeneralizedPosition::new_m with valid args");
assert_eq!(format!("{}", pos2), "M + -1#1");
}
#[test]
fn test_new_i_transposing_valid() {
let pos = GeneralizedPosition::new_i_transposing(0, 1, 2)
.expect("test fixture: GeneralizedPosition::new_i_transposing with valid args");
assert_eq!(pos.offset(), 0);
assert_eq!(pos.errors(), 1);
assert!(pos.is_non_final());
assert!(!pos.is_final());
let pos = GeneralizedPosition::new_i_transposing(-1, 1, 2)
.expect("test fixture: GeneralizedPosition::new_i_transposing with valid args");
assert_eq!(pos.offset(), -1);
assert_eq!(pos.errors(), 1);
}
#[test]
fn test_new_i_transposing_invalid() {
assert!(GeneralizedPosition::new_i_transposing(3, 1, 2).is_err()); assert!(GeneralizedPosition::new_i_transposing(2, 1, 2).is_err()); }
#[test]
fn test_new_m_transposing_valid() {
let pos = GeneralizedPosition::new_m_transposing(0, 0, 2)
.expect("test fixture: GeneralizedPosition::new_m_transposing with valid args");
assert_eq!(pos.offset(), 0);
assert_eq!(pos.errors(), 0);
assert!(!pos.is_non_final());
assert!(pos.is_final());
let pos = GeneralizedPosition::new_m_transposing(-1, 1, 2)
.expect("test fixture: GeneralizedPosition::new_m_transposing with valid args");
assert_eq!(pos.offset(), -1);
assert_eq!(pos.errors(), 1);
}
#[test]
fn test_new_m_transposing_invalid() {
assert!(GeneralizedPosition::new_m_transposing(-4, 1, 2).is_err()); assert!(GeneralizedPosition::new_m_transposing(1, 1, 2).is_err()); }
#[test]
fn test_new_i_splitting_valid() {
let pos = GeneralizedPosition::new_i_splitting(0, 1, 2, 'a')
.expect("test fixture: GeneralizedPosition::new_i_splitting with valid args");
assert_eq!(pos.offset(), 0);
assert_eq!(pos.errors(), 1);
assert!(pos.is_non_final());
assert!(!pos.is_final());
let pos = GeneralizedPosition::new_i_splitting(-2, 2, 2, 'b')
.expect("test fixture: GeneralizedPosition::new_i_splitting with valid args");
assert_eq!(pos.offset(), -2);
assert_eq!(pos.errors(), 2);
}
#[test]
fn test_new_i_splitting_invalid() {
assert!(GeneralizedPosition::new_i_splitting(3, 1, 2, 'a').is_err()); assert!(GeneralizedPosition::new_i_splitting(-3, 2, 2, 'a').is_err()); }
#[test]
fn test_new_m_splitting_valid() {
let pos = GeneralizedPosition::new_m_splitting(0, 0, 2, 'a')
.expect("test fixture: GeneralizedPosition::new_m_splitting with valid args");
assert_eq!(pos.offset(), 0);
assert_eq!(pos.errors(), 0);
assert!(!pos.is_non_final());
assert!(pos.is_final());
let pos = GeneralizedPosition::new_m_splitting(-2, 2, 2, 'b')
.expect("test fixture: GeneralizedPosition::new_m_splitting with valid args");
assert_eq!(pos.offset(), -2);
assert_eq!(pos.errors(), 2);
}
#[test]
fn test_new_m_splitting_invalid() {
assert!(GeneralizedPosition::new_m_splitting(-5, 2, 2, 'a').is_err()); assert!(GeneralizedPosition::new_m_splitting(1, 1, 2, 'a').is_err()); }
#[test]
fn test_display_transposing() {
let pos = GeneralizedPosition::new_i_transposing(1, 2, 3)
.expect("test fixture: GeneralizedPosition::new_i_transposing with valid args");
assert_eq!(format!("{}", pos), "I + 1#2_t");
let pos = GeneralizedPosition::new_m_transposing(-1, 1, 2)
.expect("test fixture: GeneralizedPosition::new_m_transposing with valid args");
assert_eq!(format!("{}", pos), "M + -1#1_t");
}
#[test]
fn test_display_splitting() {
let pos = GeneralizedPosition::new_i_splitting(0, 1, 2, 'a')
.expect("test fixture: GeneralizedPosition::new_i_splitting with valid args");
assert_eq!(format!("{}", pos), "I + 0#1_s");
let pos = GeneralizedPosition::new_m_splitting(-2, 2, 2, 'a')
.expect("test fixture: GeneralizedPosition::new_m_splitting with valid args");
assert_eq!(format!("{}", pos), "M + -2#2_s");
}
#[test]
fn test_ordering_with_new_variants() {
let i_normal = GeneralizedPosition::new_i(0, 1, 2)
.expect("test fixture: GeneralizedPosition::new_i with valid args");
let i_transposing = GeneralizedPosition::new_i_transposing(0, 1, 2)
.expect("test fixture: GeneralizedPosition::new_i_transposing with valid args");
let i_splitting = GeneralizedPosition::new_i_splitting(0, 1, 2, 'a')
.expect("test fixture: GeneralizedPosition::new_i_splitting with valid args");
let m_normal = GeneralizedPosition::new_m(0, 1, 2)
.expect("test fixture: GeneralizedPosition::new_m with valid args");
let m_transposing = GeneralizedPosition::new_m_transposing(0, 1, 2)
.expect("test fixture: GeneralizedPosition::new_m_transposing with valid args");
let m_splitting = GeneralizedPosition::new_m_splitting(0, 1, 2, 'a')
.expect("test fixture: GeneralizedPosition::new_m_splitting with valid args");
assert!(i_normal < m_normal);
assert!(i_transposing < m_transposing);
assert!(i_splitting < m_splitting);
assert!(i_normal < i_transposing);
assert!(i_transposing < i_splitting);
assert!(m_normal < m_transposing);
assert!(m_transposing < m_splitting);
}
}