use crate::Result;
use std::cmp::{max, min};
use std::collections::HashMap;
pub trait StringMetric {
fn distance(&self, s1: &str, s2: &str) -> Result<f64>;
fn normalized_distance(&self, s1: &str, s2: &str) -> Result<f64> {
let dist = self.distance(s1, s2)?;
let max_len = max(s1.len(), s2.len()) as f64;
Ok(if max_len > 0.0 { dist / max_len } else { 0.0 })
}
fn similarity(&self, s1: &str, s2: &str) -> Result<f64> {
Ok(1.0 - self.normalized_distance(s1, s2)?)
}
}
#[derive(Debug, Clone)]
pub struct DamerauLevenshteinMetric {
restricted: bool,
}
impl DamerauLevenshteinMetric {
pub fn new() -> Self {
Self { restricted: false }
}
pub fn restricted() -> Self {
Self { restricted: true }
}
}
impl Default for DamerauLevenshteinMetric {
fn default() -> Self {
Self::new()
}
}
impl StringMetric for DamerauLevenshteinMetric {
fn distance(&self, s1: &str, s2: &str) -> Result<f64> {
if self.restricted {
Ok(osa_distance(s1, s2) as f64)
} else {
Ok(damerau_levenshtein_distance(s1, s2) as f64)
}
}
}
#[allow(dead_code)]
fn osa_distance(s1: &str, s2: &str) -> usize {
let a: Vec<char> = s1.chars().collect();
let b: Vec<char> = s2.chars().collect();
let len_a = a.len();
let len_b = b.len();
if len_a == 0 {
return len_b;
}
if len_b == 0 {
return len_a;
}
let mut matrix = vec![vec![0; len_b + 1]; len_a + 1];
for (i, row) in matrix.iter_mut().enumerate().take(len_a + 1) {
row[0] = i;
}
for j in 0..=len_b {
matrix[0][j] = j;
}
for i in 1..=len_a {
for j in 1..=len_b {
let cost = if a[i - 1] == b[j - 1] { 0 } else { 1 };
matrix[i][j] = min(
min(
matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, ),
matrix[i - 1][j - 1] + cost, );
if i > 1 && j > 1 && a[i - 1] == b[j - 2] && a[i - 2] == b[j - 1] {
matrix[i][j] = min(matrix[i][j], matrix[i - 2][j - 2] + cost);
}
}
}
matrix[len_a][len_b]
}
#[allow(dead_code)]
fn damerau_levenshtein_distance(s1: &str, s2: &str) -> usize {
let a: Vec<char> = s1.chars().collect();
let b: Vec<char> = s2.chars().collect();
let len_a = a.len();
let len_b = b.len();
if len_a == 0 {
return len_b;
}
if len_b == 0 {
return len_a;
}
let max_dist = len_a + len_b;
let mut h = vec![vec![max_dist; len_b + 2]; len_a + 2];
let mut da: HashMap<char, usize> = HashMap::new();
for i in 0..=len_a {
h[i + 1][0] = max_dist;
h[i + 1][1] = i;
}
for j in 0..=len_b {
h[0][j + 1] = max_dist;
h[1][j + 1] = j;
}
for i in 1..=len_a {
let mut db = 0;
for j in 1..=len_b {
let k = da.get(&b[j - 1]).copied().unwrap_or(0);
let l = db;
let cost = if a[i - 1] == b[j - 1] {
db = j;
0
} else {
1
};
h[i + 1][j + 1] = min(
min(
h[i][j] + cost, h[i + 1][j] + 1, ),
min(
h[i][j + 1] + 1, h[k][l] + (i - k - 1) + 1 + (j - l - 1), ),
);
}
da.insert(a[i - 1], i);
}
h[len_a + 1][len_b + 1]
}
pub trait PhoneticAlgorithm {
fn encode(&self, text: &str) -> Result<String>;
fn sounds_like(&self, s1: &str, s2: &str) -> Result<bool> {
Ok(self.encode(s1)? == self.encode(s2)?)
}
}
#[derive(Debug, Clone)]
pub struct Soundex {
length: usize,
}
impl Soundex {
pub fn new() -> Self {
Self { length: 4 }
}
pub fn with_length(length: usize) -> Self {
Self { length }
}
}
impl Default for Soundex {
fn default() -> Self {
Self::new()
}
}
impl PhoneticAlgorithm for Soundex {
fn encode(&self, text: &str) -> Result<String> {
if text.is_empty() {
return Ok(String::new());
}
let text = text.to_uppercase();
let chars: Vec<char> = text.chars().filter(|c| c.is_alphabetic()).collect();
if chars.is_empty() {
return Ok(String::new());
}
let mut code = String::new();
code.push(chars[0]);
let mut last_code = encode_char(chars[0]);
for &ch in &chars[1..] {
let ch_code = encode_char(ch);
if ch_code != '0' && ch_code != last_code {
code.push(ch_code);
last_code = ch_code;
}
if code.len() >= self.length {
break;
}
}
while code.len() < self.length {
code.push('0');
}
Ok(code)
}
}
#[allow(dead_code)]
fn encode_char(ch: char) -> char {
match ch.to_ascii_uppercase() {
'B' | 'F' | 'P' | 'V' => '1',
'C' | 'G' | 'J' | 'K' | 'Q' | 'S' | 'X' | 'Z' => '2',
'D' | 'T' => '3',
'L' => '4',
'M' | 'N' => '5',
'R' => '6',
_ => '0',
}
}
#[derive(Debug, Clone)]
pub struct Metaphone {
max_length: usize,
}
#[derive(Debug, Clone)]
pub struct Nysiis {
max_length: usize,
}
#[derive(Debug, Clone)]
pub struct NeedlemanWunsch {
match_score: i32,
mismatch_penalty: i32,
gap_penalty: i32,
}
#[derive(Debug, Clone, PartialEq)]
pub struct AlignmentResult {
pub aligned_seq1: String,
pub aligned_seq2: String,
pub score: i32,
}
#[derive(Debug, Clone)]
pub struct SmithWaterman {
match_score: i32,
mismatch_penalty: i32,
gap_penalty: i32,
}
impl Metaphone {
pub fn new() -> Self {
Self { max_length: 6 }
}
pub fn with_max_length(_maxlength: usize) -> Self {
Self {
max_length: _maxlength,
}
}
}
impl Default for Metaphone {
fn default() -> Self {
Self::new()
}
}
impl PhoneticAlgorithm for Metaphone {
fn encode(&self, text: &str) -> Result<String> {
if text.is_empty() {
return Ok(String::new());
}
let text = text.to_uppercase();
let chars: Vec<char> = text.chars().filter(|c| c.is_alphabetic()).collect();
if chars.is_empty() {
return Ok(String::new());
}
let mut result = String::new();
let mut i = 0;
if chars.len() >= 2 {
match (chars[0], chars[1]) {
('A', 'E') | ('G', 'N') | ('K', 'N') | ('P', 'N') | ('W', 'R') => i = 1,
('W', 'H') => {
result.push('W');
i = 2;
}
_ => {}
}
}
while i < chars.len() && result.len() < self.max_length {
let ch = chars[i];
let next = chars.get(i + 1).copied();
let prev = if i > 0 {
chars.get(i - 1).copied()
} else {
None
};
match ch {
'A' | 'E' | 'I' | 'O' | 'U' if i == 0 => {
result.push(ch);
}
'B' if !result.ends_with('B') => {
result.push('B');
}
'C' => {
if next == Some('H') {
result.push('X');
i += 1;
} else if next == Some('I') || next == Some('E') || next == Some('Y') {
result.push('S');
} else {
result.push('K');
}
}
'D' => {
if next == Some('G') && chars.get(i + 2) == Some(&'E') {
result.push('J');
i += 2;
} else {
result.push('T');
}
}
'F' | 'J' | 'L' | 'M' | 'N' | 'R' if !result.ends_with(ch) => {
result.push(ch);
}
'G' => {
if next == Some('H') {
i += 1;
} else if next == Some('N') {
result.push('N');
i += 1;
} else {
result.push('K');
}
}
'H' if prev != Some('C')
&& prev != Some('S')
&& prev != Some('P')
&& prev != Some('T')
&& prev != Some('G') =>
{
result.push('H');
}
'K' if prev != Some('C') => {
result.push('K');
}
'P' => {
if next == Some('H') {
result.push('F');
i += 1;
} else {
result.push('P');
}
}
'Q' => result.push('K'),
'S' => {
if next == Some('H') {
result.push('X');
i += 1;
} else if next == Some('I')
&& (chars.get(i + 2) == Some(&'A') || chars.get(i + 2) == Some(&'O'))
{
result.push('X');
} else {
result.push('S');
}
}
'T' => {
if next == Some('H') {
result.push('0');
i += 1;
} else if next != Some('C') || chars.get(i + 2) != Some(&'H') {
result.push('T');
}
}
'V' => result.push('F'),
'W' | 'Y' if next.map(|c| "AEIOU".contains(c)).unwrap_or(false) => {
result.push(ch);
}
'X' => {
result.push('K');
result.push('S');
}
'Z' => result.push('S'),
_ => {}
}
i += 1;
}
Ok(result)
}
}
impl Nysiis {
pub fn new() -> Self {
Self { max_length: 0 }
}
pub fn with_max_length(_maxlength: usize) -> Self {
Self {
max_length: _maxlength,
}
}
}
impl Default for Nysiis {
fn default() -> Self {
Self::new()
}
}
impl PhoneticAlgorithm for Nysiis {
fn encode(&self, text: &str) -> Result<String> {
if text.is_empty() {
return Ok(String::new());
}
let mut word: Vec<char> = text
.to_uppercase()
.chars()
.filter(|c| c.is_alphabetic())
.collect();
if word.is_empty() {
return Ok(String::new());
}
if word.len() >= 3 {
let start3 = &word[0..3].iter().collect::<String>();
let start2 = &word[0..2].iter().collect::<String>();
if start3 == "MAC" {
word[1] = 'C';
} else if start2 == "KN" {
word.remove(0);
}
} else if word.len() >= 2 {
let start = &word[0..2].iter().collect::<String>();
if start == "KN" {
word.remove(0);
}
}
if word.len() >= 2 && (word[0] == 'P' && (word[1] == 'H' || word[1] == 'F')) {
word[0] = 'F';
word[1] = 'F';
}
if word.len() >= 3 && word[0] == 'S' && word[1] == 'C' && word[2] == 'H' {
word[0] = 'S';
word[1] = 'S';
word.remove(2); }
let len = word.len();
if len >= 2 {
let end = &word[len - 2..].iter().collect::<String>();
match end.as_str() {
"EE" | "IE" => {
word.truncate(len - 2);
word.push('Y');
}
"DT" | "RT" | "RD" | "NT" | "ND" => {
word.truncate(len - 1);
word.push('D');
}
_ => {}
}
}
let mut result = vec![word[0]];
for i in 1..word.len() {
let ch = word[i];
let prev = word[i - 1];
let next = word.get(i + 1).copied();
match ch {
'A' => {
if prev != 'A' && result.last() != Some(&'A') {
result.push('A');
}
}
'E' | 'I' | 'O' | 'U' => {
if prev == ch {
continue; }
if result.last() != Some(&'A') {
result.push('A');
}
}
'Q' => result.push('G'),
'Z' => result.push('S'),
'M' => result.push('N'),
'K' => {
if next == Some('N') {
result.push('N');
} else {
result.push('C');
}
}
'S' => {
if next == Some('C') && word.get(i + 2) == Some(&'H') {
result.push('S');
result.push('S');
result.push('S');
} else {
result.push('S');
}
}
'P' => {
if next == Some('H') {
result.push('F');
result.push('F');
} else {
result.push('P');
}
}
'H' => {
if prev == 'G' {
} else if !matches!(prev, 'A' | 'E' | 'I' | 'O' | 'U')
&& !matches!(
next,
Some('A') | Some('E') | Some('I') | Some('O') | Some('U')
)
&& prev != ch
{
result.push('H');
}
}
'W' => {
if matches!(prev, 'A' | 'E' | 'I' | 'O' | 'U') && prev != ch {
result.push('W');
}
}
_ => {
if prev != ch {
result.push(ch);
} else if i == 1 && ch == 'F' && result.len() == 1 && result[0] == 'F' {
result.push(ch);
} else if i == 1 && ch == 'S' && result.len() == 1 && result[0] == 'S' {
result.push(ch);
}
}
}
}
while result.len() > 1
&& (result.last() == Some(&'S')
|| result.last() == Some(&'A')
|| result.last() == Some(&'H'))
{
result.pop();
}
if result.len() >= 2 && result[result.len() - 2] == 'A' && result[result.len() - 1] == 'Y' {
result.pop();
result.pop();
result.push('Y');
}
let mut encoded: String = result.into_iter().collect();
if self.max_length > 0 && encoded.len() > self.max_length {
encoded.truncate(self.max_length);
}
Ok(encoded)
}
}
impl NeedlemanWunsch {
pub fn new() -> Self {
Self {
match_score: 1,
mismatch_penalty: -1,
gap_penalty: -1,
}
}
pub fn with_scores(_match_score: i32, mismatch_penalty: i32, gappenalty: i32) -> Self {
Self {
match_score: _match_score,
mismatch_penalty,
gap_penalty: gappenalty,
}
}
pub fn align(&self, seq1: &str, seq2: &str) -> AlignmentResult {
let seq1_chars: Vec<char> = seq1.chars().collect();
let seq2_chars: Vec<char> = seq2.chars().collect();
let m = seq1_chars.len();
let n = seq2_chars.len();
let mut matrix = vec![vec![0; n + 1]; m + 1];
for (i, item) in matrix.iter_mut().enumerate().take(m + 1) {
item[0] = i as i32 * self.gap_penalty;
}
for j in 0..=n {
matrix[0][j] = j as i32 * self.gap_penalty;
}
for i in 1..=m {
for j in 1..=n {
let match_mismatch = if seq1_chars[i - 1] == seq2_chars[j - 1] {
matrix[i - 1][j - 1] + self.match_score
} else {
matrix[i - 1][j - 1] + self.mismatch_penalty
};
let delete = matrix[i - 1][j] + self.gap_penalty;
let insert = matrix[i][j - 1] + self.gap_penalty;
matrix[i][j] = *[match_mismatch, delete, insert]
.iter()
.max()
.expect("Operation failed");
}
}
let mut aligned_seq1 = String::new();
let mut aligned_seq2 = String::new();
let mut i = m;
let mut j = n;
while i > 0 || j > 0 {
if i > 0 && j > 0 {
let current_score = matrix[i][j];
let diagonal_score = if seq1_chars[i - 1] == seq2_chars[j - 1] {
matrix[i - 1][j - 1] + self.match_score
} else {
matrix[i - 1][j - 1] + self.mismatch_penalty
};
let up_score = matrix[i - 1][j] + self.gap_penalty;
let left_score = matrix[i][j - 1] + self.gap_penalty;
if current_score == diagonal_score {
aligned_seq1.insert(0, seq1_chars[i - 1]);
aligned_seq2.insert(0, seq2_chars[j - 1]);
i -= 1;
j -= 1;
} else if current_score == left_score {
aligned_seq1.insert(0, '-');
aligned_seq2.insert(0, seq2_chars[j - 1]);
j -= 1;
} else if current_score == up_score {
aligned_seq1.insert(0, seq1_chars[i - 1]);
aligned_seq2.insert(0, '-');
i -= 1;
} else {
aligned_seq1.insert(0, seq1_chars[i - 1]);
aligned_seq2.insert(0, seq2_chars[j - 1]);
i -= 1;
j -= 1;
}
} else if i > 0 {
aligned_seq1.insert(0, seq1_chars[i - 1]);
aligned_seq2.insert(0, '-');
i -= 1;
} else {
aligned_seq1.insert(0, '-');
aligned_seq2.insert(0, seq2_chars[j - 1]);
j -= 1;
}
}
AlignmentResult {
aligned_seq1,
aligned_seq2,
score: matrix[m][n],
}
}
}
impl Default for NeedlemanWunsch {
fn default() -> Self {
Self::new()
}
}
impl SmithWaterman {
pub fn new() -> Self {
Self {
match_score: 2,
mismatch_penalty: -1,
gap_penalty: -1,
}
}
pub fn with_scores(_match_score: i32, mismatch_penalty: i32, gappenalty: i32) -> Self {
Self {
match_score: _match_score,
mismatch_penalty,
gap_penalty: gappenalty,
}
}
pub fn align(&self, seq1: &str, seq2: &str) -> AlignmentResult {
let seq1_chars: Vec<char> = seq1.chars().collect();
let seq2_chars: Vec<char> = seq2.chars().collect();
let m = seq1_chars.len();
let n = seq2_chars.len();
let mut matrix = vec![vec![0; n + 1]; m + 1];
let mut max_score = 0;
let mut max_i = 0;
let mut max_j = 0;
for i in 1..=m {
for j in 1..=n {
let match_mismatch = if seq1_chars[i - 1] == seq2_chars[j - 1] {
matrix[i - 1][j - 1] + self.match_score
} else {
matrix[i - 1][j - 1] + self.mismatch_penalty
};
let delete = matrix[i - 1][j] + self.gap_penalty;
let insert = matrix[i][j - 1] + self.gap_penalty;
matrix[i][j] = *[0, match_mismatch, delete, insert]
.iter()
.max()
.expect("Operation failed");
if matrix[i][j] > max_score {
max_score = matrix[i][j];
max_i = i;
max_j = j;
}
}
}
let mut aligned_seq1 = String::new();
let mut aligned_seq2 = String::new();
let mut i = max_i;
let mut j = max_j;
while i > 0 && j > 0 && matrix[i][j] > 0 {
let current_score = matrix[i][j];
let diagonal_score = if seq1_chars[i - 1] == seq2_chars[j - 1] {
matrix[i - 1][j - 1] + self.match_score
} else {
matrix[i - 1][j - 1] + self.mismatch_penalty
};
if current_score == diagonal_score {
aligned_seq1.insert(0, seq1_chars[i - 1]);
aligned_seq2.insert(0, seq2_chars[j - 1]);
i -= 1;
j -= 1;
} else if current_score == matrix[i - 1][j] + self.gap_penalty {
aligned_seq1.insert(0, seq1_chars[i - 1]);
aligned_seq2.insert(0, '-');
i -= 1;
} else if current_score == matrix[i][j - 1] + self.gap_penalty {
aligned_seq1.insert(0, '-');
aligned_seq2.insert(0, seq2_chars[j - 1]);
j -= 1;
} else {
break;
}
}
AlignmentResult {
aligned_seq1,
aligned_seq2,
score: max_score,
}
}
}
impl Default for SmithWaterman {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_damerau_levenshtein() {
let metric = DamerauLevenshteinMetric::new();
assert_eq!(metric.distance("", "").expect("Operation failed"), 0.0);
assert_eq!(metric.distance("abc", "").expect("Operation failed"), 3.0);
assert_eq!(metric.distance("", "abc").expect("Operation failed"), 3.0);
assert_eq!(
metric.distance("abc", "abc").expect("Operation failed"),
0.0
);
assert_eq!(
metric.distance("abc", "aXc").expect("Operation failed"),
1.0
); assert_eq!(metric.distance("abc", "ac").expect("Operation failed"), 1.0); assert_eq!(metric.distance("ac", "abc").expect("Operation failed"), 1.0); assert_eq!(
metric.distance("abc", "acb").expect("Operation failed"),
1.0
);
assert_eq!(
metric
.distance("kitten", "sitting")
.expect("Operation failed"),
3.0
);
assert!(
(metric
.normalized_distance("abc", "aXc")
.expect("Operation failed")
- 0.333)
.abs()
< 0.01
);
assert_eq!(
metric
.normalized_distance("", "")
.expect("Operation failed"),
0.0
);
assert!((metric.similarity("abc", "aXc").expect("Operation failed") - 0.667).abs() < 0.01);
}
#[test]
fn test_restricted_damerau_levenshtein() {
let metric = DamerauLevenshteinMetric::restricted();
assert_eq!(metric.distance("ca", "abc").expect("Operation failed"), 3.0);
}
#[test]
fn test_soundex() {
let soundex = Soundex::new();
assert_eq!(soundex.encode("Robert").expect("Operation failed"), "R163");
assert_eq!(soundex.encode("Rupert").expect("Operation failed"), "R163");
assert_eq!(soundex.encode("SOUNDEX").expect("Operation failed"), "S532");
assert_eq!(soundex.encode("Smith").expect("Operation failed"), "S530");
assert_eq!(soundex.encode("Smythe").expect("Operation failed"), "S530");
assert_eq!(soundex.encode("").expect("Operation failed"), "");
assert_eq!(soundex.encode("123").expect("Operation failed"), "");
assert!(soundex
.sounds_like("Robert", "Rupert")
.expect("Operation failed"));
assert!(soundex
.sounds_like("Smith", "Smythe")
.expect("Operation failed"));
assert!(!soundex
.sounds_like("Smith", "Jones")
.expect("Operation failed"));
let soundex_5 = Soundex::with_length(5);
assert_eq!(
soundex_5.encode("SOUNDEX").expect("Operation failed"),
"S5320"
);
}
#[test]
fn test_metaphone() {
let metaphone = Metaphone::new();
assert_eq!(
metaphone.encode("programming").expect("Operation failed"),
"PRKRMN"
);
assert_eq!(
metaphone.encode("programmer").expect("Operation failed"),
"PRKRMR"
);
assert_eq!(metaphone.encode("Wright").expect("Operation failed"), "RT");
assert_eq!(metaphone.encode("White").expect("Operation failed"), "WT");
assert_eq!(metaphone.encode("Knight").expect("Operation failed"), "NT");
assert_eq!(metaphone.encode("").expect("Operation failed"), "");
assert_eq!(metaphone.encode("123").expect("Operation failed"), "");
assert!(metaphone
.sounds_like("Wright", "Write")
.expect("Operation failed"));
assert!(!metaphone
.sounds_like("White", "Wright")
.expect("Operation failed"));
let metaphone_3 = Metaphone::with_max_length(3);
assert_eq!(
metaphone_3.encode("programming").expect("Operation failed"),
"PRK"
);
}
#[test]
fn test_phonetic_edge_cases() {
let soundex = Soundex::new();
let metaphone = Metaphone::new();
assert_eq!(soundex.encode("O'Brien").expect("Operation failed"), "O165");
assert_eq!(
metaphone.encode("O'Brien").expect("Operation failed"),
"OBRN"
);
assert_eq!(
soundex.encode("McDonald").expect("Operation failed"),
"M235"
);
assert_eq!(
metaphone.encode("McDonald").expect("Operation failed"),
"MKTNLT"
);
}
#[test]
fn test_nysiis() {
let nysiis = Nysiis::new();
assert_eq!(
nysiis.encode("Johnson").expect("Operation failed"),
"JANSAN"
);
assert_eq!(
nysiis.encode("Williams").expect("Operation failed"),
"WALAN"
); assert_eq!(nysiis.encode("Jones").expect("Operation failed"), "JAN");
assert_eq!(nysiis.encode("Smith").expect("Operation failed"), "SNAT");
assert_eq!(
nysiis.encode("MacDonald").expect("Operation failed"),
"MCDANALD"
);
assert_eq!(nysiis.encode("Knight").expect("Operation failed"), "NAGT");
assert_eq!(nysiis.encode("").expect("Operation failed"), "");
assert_eq!(nysiis.encode("123").expect("Operation failed"), "");
assert!(nysiis
.sounds_like("Johnson", "Jonson")
.expect("Operation failed"));
assert!(!nysiis
.sounds_like("Smith", "Jones")
.expect("Operation failed"));
assert_eq!(
nysiis.encode("Philips").expect("Operation failed"),
"FFALAP"
);
assert_eq!(nysiis.encode("Schmidt").expect("Operation failed"), "SSNAD");
assert_eq!(
nysiis.encode("Schneider").expect("Operation failed"),
"SSNADAR"
);
let nysiis_6 = Nysiis::with_max_length(6);
assert_eq!(
nysiis_6.encode("Williams").expect("Operation failed"),
"WALAN"
); assert_eq!(
nysiis_6.encode("MacDonald").expect("Operation failed"),
"MCDANA"
); }
#[test]
fn test_needleman_wunsch() {
let aligner = NeedlemanWunsch::new();
let result = aligner.align("GATTACA", "GCATGCU");
assert_eq!(result.aligned_seq1, "G-ATTACA");
assert_eq!(result.score, 0);
let seq2_chars = result
.aligned_seq2
.chars()
.filter(|&c| c != '-')
.collect::<String>();
assert_eq!(seq2_chars, "GCATGCU");
assert_eq!(result.aligned_seq1.len(), result.aligned_seq2.len());
let result = aligner.align("HELLO", "HELLO");
assert_eq!(result.aligned_seq1, "HELLO");
assert_eq!(result.aligned_seq2, "HELLO");
assert_eq!(result.score, 5);
let result = aligner.align("", "ABC");
assert_eq!(result.aligned_seq1, "---");
assert_eq!(result.aligned_seq2, "ABC");
assert_eq!(result.score, -3);
let result = aligner.align("ABC", "");
assert_eq!(result.aligned_seq1, "ABC");
assert_eq!(result.aligned_seq2, "---");
assert_eq!(result.score, -3);
let custom_aligner = NeedlemanWunsch::with_scores(2, -2, -1);
let result = custom_aligner.align("CAT", "CART");
assert!(result.score > 0);
}
#[test]
fn test_smith_waterman() {
let aligner = SmithWaterman::new();
let result = aligner.align("GGTTGACTA", "TGTTACGG");
assert!(result.score > 0);
assert!(result.aligned_seq1.contains("GTT"));
assert!(result.aligned_seq2.contains("GTT"));
let result = aligner.align("ABCDEFG", "XYZCDEFPQ");
assert_eq!(result.aligned_seq1, "CDEF");
assert_eq!(result.aligned_seq2, "CDEF");
assert_eq!(result.score, 8);
let result = aligner.align("", "ABC");
assert_eq!(result.aligned_seq1, "");
assert_eq!(result.aligned_seq2, "");
assert_eq!(result.score, 0);
let result = aligner.align("AAA", "BBB");
assert_eq!(result.score, 0);
let custom_aligner = SmithWaterman::with_scores(3, -3, -2);
let result = custom_aligner.align("ACACACTA", "AGCACACA");
assert!(result.score > 0);
}
}