use crate::mer::{MerDna, encode_base};
pub struct StringMers<'a> {
seq: &'a [u8],
k: usize,
pos: usize,
valid_count: usize,
current: MerDna,
canonical: bool,
}
impl<'a> StringMers<'a> {
pub fn new(seq: &'a str, k: usize) -> Self {
Self {
seq: seq.as_bytes(),
k,
pos: 0,
valid_count: 0,
current: MerDna::new(k),
canonical: false,
}
}
pub fn canonicals(seq: &'a str, k: usize) -> Self {
Self {
seq: seq.as_bytes(),
k,
pos: 0,
valid_count: 0,
current: MerDna::new(k),
canonical: true,
}
}
}
impl<'a> Iterator for StringMers<'a> {
type Item = MerDna;
fn next(&mut self) -> Option<MerDna> {
while self.pos < self.seq.len() {
let ch = self.seq[self.pos];
self.pos += 1;
match encode_base(ch) {
Some(_code) => {
self.current.shift_left(ch);
self.valid_count += 1;
if self.valid_count >= self.k {
return if self.canonical {
Some(self.current.get_canonical())
} else {
Some(self.current.clone())
};
}
}
None => {
self.valid_count = 0;
self.current = MerDna::new(self.k);
}
}
}
None
}
}
pub fn string_mers(seq: &str, k: usize) -> StringMers<'_> {
StringMers::new(seq, k)
}
pub fn string_canonicals(seq: &str, k: usize) -> StringMers<'_> {
StringMers::canonicals(seq, k)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_extraction() {
let mers: Vec<String> = StringMers::new("ACGTACGT", 4)
.map(|m| m.to_string())
.collect();
assert_eq!(mers, vec!["ACGT", "CGTA", "GTAC", "TACG", "ACGT"]);
}
#[test]
fn test_exact_length() {
let mers: Vec<String> = StringMers::new("ACGT", 4).map(|m| m.to_string()).collect();
assert_eq!(mers, vec!["ACGT"]);
}
#[test]
fn test_shorter_than_k() {
let mers: Vec<String> = StringMers::new("ACG", 4).map(|m| m.to_string()).collect();
assert!(mers.is_empty());
}
#[test]
fn test_empty_string() {
let mers: Vec<String> = StringMers::new("", 4).map(|m| m.to_string()).collect();
assert!(mers.is_empty());
}
#[test]
fn test_skip_invalid_characters() {
let mers: Vec<String> = StringMers::new("ACGTNACGT", 4)
.map(|m| m.to_string())
.collect();
assert_eq!(mers, vec!["ACGT", "ACGT"]);
}
#[test]
fn test_invalid_at_start() {
let mers: Vec<String> = StringMers::new("NACGT", 4).map(|m| m.to_string()).collect();
assert_eq!(mers, vec!["ACGT"]);
}
#[test]
fn test_all_invalid() {
let mers: Vec<String> = StringMers::new("NNNN", 4).map(|m| m.to_string()).collect();
assert!(mers.is_empty());
}
#[test]
fn test_k_equals_1() {
let mers: Vec<String> = StringMers::new("ACGT", 1).map(|m| m.to_string()).collect();
assert_eq!(mers, vec!["A", "C", "G", "T"]);
}
#[test]
fn test_lowercase() {
let mers: Vec<String> = StringMers::new("acgt", 4).map(|m| m.to_string()).collect();
assert_eq!(mers, vec!["ACGT"]);
}
#[test]
fn test_canonical_mode() {
let mers: Vec<String> = StringMers::canonicals("TTTT", 4)
.map(|m| m.to_string())
.collect();
assert_eq!(mers, vec!["AAAA"]);
}
#[test]
fn test_canonical_palindrome() {
let mers: Vec<String> = StringMers::canonicals("ACGT", 4)
.map(|m| m.to_string())
.collect();
assert_eq!(mers, vec!["ACGT"]);
}
#[test]
fn test_canonical_various() {
let mers: Vec<String> = StringMers::canonicals("ACGTACGT", 4)
.map(|m| m.to_string())
.collect();
for mer_str in &mers {
let mer: MerDna = mer_str.parse().unwrap();
assert_eq!(mer.get_canonical().to_string(), *mer_str);
}
}
#[test]
fn test_convenience_functions() {
let mers1: Vec<String> = string_mers("ACGT", 4).map(|m| m.to_string()).collect();
let mers2: Vec<String> = StringMers::new("ACGT", 4).map(|m| m.to_string()).collect();
assert_eq!(mers1, mers2);
let can1: Vec<String> = string_canonicals("ACGT", 4)
.map(|m| m.to_string())
.collect();
let can2: Vec<String> = StringMers::canonicals("ACGT", 4)
.map(|m| m.to_string())
.collect();
assert_eq!(can1, can2);
}
#[test]
fn test_multiple_invalid_regions() {
let mers: Vec<String> = StringMers::new("ACGTNNTACG", 3)
.map(|m| m.to_string())
.collect();
assert_eq!(mers, vec!["ACG", "CGT", "TAC", "ACG"]);
}
#[test]
fn test_long_sequence() {
let seq = "ACGT".repeat(100);
let mers: Vec<String> = StringMers::new(&seq, 25).map(|m| m.to_string()).collect();
assert_eq!(mers.len(), seq.len() - 24);
}
#[test]
fn test_homopolymer_run() {
let mers: Vec<String> = StringMers::new("AAAAA", 3).map(|m| m.to_string()).collect();
assert_eq!(mers, vec!["AAA", "AAA", "AAA"]);
}
}