use std::{cmp::Ordering, slice::Iter};
use crate::Scoring;
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
pub struct Match {
score: isize,
consecutive: usize,
matched: Vec<usize>,
}
impl Match {
pub(crate) fn with_matched(score: isize, consecutive: usize, matched: Vec<usize>) -> Self {
Match {
score,
consecutive,
matched,
}
}
pub fn score(&self) -> isize {
self.score
}
pub fn matched_indices(&self) -> Iter<usize> {
self.matched.iter()
}
pub fn continuous_matches(&self) -> ContinuousMatches {
ContinuousMatches {
matched: &self.matched,
current: 0,
}
}
pub fn extend_with(&mut self, other: &Match, scoring: &Scoring) {
self.score += other.score;
self.consecutive += other.consecutive;
if let (Some(last), Some(first)) = (self.matched.last(), other.matched.first()) {
let distance = first - last;
match distance {
0 => {}
1 => {
self.consecutive += 1;
self.score += self.consecutive as isize * scoring.bonus_consecutive;
}
_ => {
self.consecutive = 0;
let penalty = (distance as isize - 1) * scoring.penalty_distance;
self.score -= penalty;
}
}
}
self.matched.extend(&other.matched);
}
}
impl Ord for Match {
fn cmp(&self, other: &Match) -> Ordering {
self.score.cmp(&other.score)
}
}
impl PartialOrd for Match {
fn partial_cmp(&self, other: &Match) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Eq for Match {}
impl PartialEq for Match {
fn eq(&self, other: &Match) -> bool {
self.score == other.score
}
}
#[derive(Debug)]
pub struct ContinuousMatch {
start: usize,
len: usize,
}
impl ContinuousMatch {
pub(crate) fn new(start: usize, len: usize) -> Self {
ContinuousMatch { start, len }
}
pub fn start(&self) -> usize {
self.start
}
pub fn len(&self) -> usize {
self.len
}
}
impl Eq for ContinuousMatch {}
impl PartialEq for ContinuousMatch {
fn eq(&self, other: &ContinuousMatch) -> bool {
self.start == other.start && self.len == other.len
}
}
pub struct ContinuousMatches<'a> {
matched: &'a Vec<usize>,
current: usize,
}
impl<'a> Iterator for ContinuousMatches<'_> {
type Item = ContinuousMatch;
fn next(&mut self) -> Option<ContinuousMatch> {
let mut start = None;
let mut len = 0;
let mut last_idx = None;
for idx in self.matched.iter().cloned().skip(self.current) {
start = start.or(Some(idx));
if last_idx.is_some() && (idx - last_idx.unwrap() != 1) {
return Some(ContinuousMatch::new(start.unwrap(), len));
}
self.current += 1;
len += 1;
last_idx = Some(idx);
}
if last_idx.is_some() {
return Some(ContinuousMatch::new(start.unwrap(), len));
}
None
}
}
#[cfg(test)]
mod tests {
use crate::Scoring;
use super::{ContinuousMatch, Match};
#[test]
fn continuous() {
let m = Match::with_matched(0, 0, vec![0, 1, 2, 5, 6, 10]);
assert_eq!(
m.continuous_matches().collect::<Vec<ContinuousMatch>>(),
vec![
ContinuousMatch { start: 0, len: 3 },
ContinuousMatch { start: 5, len: 2 },
ContinuousMatch { start: 10, len: 1 },
]
)
}
#[test]
fn extend_match() {
let mut a = Match::with_matched(16, 3, vec![1, 2, 3]);
let b = Match::with_matched(8, 3, vec![5, 6, 7]);
let s = Scoring::default();
a.extend_with(&b, &s);
assert_eq!(a.score(), 24 - s.penalty_distance);
assert_eq!(a.consecutive, 0);
assert_eq!(a.matched_indices().len(), 6);
}
#[test]
fn extend_match_cont() {
let mut a = Match::with_matched(16, 3, vec![1, 2, 3]);
let b = Match::with_matched(8, 3, vec![4, 5, 6]);
let s = Scoring::default();
a.extend_with(&b, &s);
assert_eq!(a.score(), 16 + 8 + (3 + 3 + 1) * s.bonus_consecutive);
assert_eq!(a.consecutive, 3 + 3 + 1);
assert_eq!(a.matched_indices().len(), 6);
}
}