pub struct QualityCount {
actual_counts: [u64; 150],
total_counts: u64,
}
impl QualityCount {
pub fn new() -> Self {
QualityCount {
actual_counts: [0u64; 150],
total_counts: 0,
}
}
pub fn add_value(&mut self, quality_char: u8) {
let idx = quality_char as usize;
if idx >= self.actual_counts.len() {
eprintln!(
"Warning: quality character '{}' (ASCII {}) exceeds maximum {}; clamping",
quality_char as char,
idx,
self.actual_counts.len() - 1
);
self.actual_counts[self.actual_counts.len() - 1] += 1;
self.total_counts += 1;
return;
}
self.actual_counts[idx] += 1;
self.total_counts += 1;
}
pub fn get_total_count(&self) -> u64 {
self.total_counts
}
pub fn get_min_char(&self) -> Option<u8> {
for i in 0..self.actual_counts.len() {
if self.actual_counts[i] > 0 {
return Some(i as u8);
}
}
None
}
pub fn get_max_char(&self) -> Option<u8> {
for i in (0..self.actual_counts.len()).rev() {
if self.actual_counts[i] > 0 {
return Some(i as u8);
}
}
None
}
pub fn get_mean(&self, offset: u8) -> f64 {
let mut total: u64 = 0;
let mut count: u64 = 0;
let off = offset as usize;
for i in off..self.actual_counts.len() {
total += self.actual_counts[i] * (i - off) as u64;
count += self.actual_counts[i];
}
total as f64 / count as f64
}
pub fn get_percentile(&self, offset: u8, percentile: u8) -> f64 {
let mut threshold: u64 = self.total_counts;
threshold *= percentile as u64;
threshold /= 100;
let off = offset as usize;
let mut count: u64 = 0;
for i in off..self.actual_counts.len() {
count += self.actual_counts[i];
if count >= threshold {
return (i - off) as f64;
}
}
-1.0
}
}
pub fn calculate_offsets<I, Q>(counts: I) -> (u8, u8)
where
I: IntoIterator<Item = Q>,
Q: std::borrow::Borrow<QualityCount>,
{
let mut min_char: u8 = 0;
let mut max_char: u8 = 0;
let mut first = true;
for item in counts {
let qc = item.borrow();
if first {
if let (Some(lo), Some(hi)) = (qc.get_min_char(), qc.get_max_char()) {
min_char = lo;
max_char = hi;
first = false;
}
} else {
if let Some(mc) = qc.get_min_char() {
if mc < min_char {
min_char = mc;
}
}
if let Some(mc) = qc.get_max_char() {
if mc > max_char {
max_char = mc;
}
}
}
}
(min_char, max_char)
}
impl Default for QualityCount {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty_counts() {
let qc = QualityCount::new();
assert_eq!(qc.get_total_count(), 0);
assert!(qc.get_min_char().is_none());
assert!(qc.get_max_char().is_none());
assert!(qc.get_mean(33).is_nan());
}
#[test]
fn test_single_value() {
let mut qc = QualityCount::new();
qc.add_value(b'I');
assert_eq!(qc.get_total_count(), 1);
assert_eq!(qc.get_min_char(), Some(b'I'));
assert_eq!(qc.get_max_char(), Some(b'I'));
assert!((qc.get_mean(33) - 40.0).abs() < f64::EPSILON);
}
#[test]
fn test_multiple_values() {
let mut qc = QualityCount::new();
qc.add_value(53);
qc.add_value(63);
assert_eq!(qc.get_total_count(), 2);
assert_eq!(qc.get_min_char(), Some(53));
assert_eq!(qc.get_max_char(), Some(63));
assert!((qc.get_mean(33) - 25.0).abs() < f64::EPSILON);
}
#[test]
fn test_percentile_integer_division() {
let mut qc = QualityCount::new();
qc.add_value(b'I'); let p25 = qc.get_percentile(33, 25);
assert!((p25 - 0.0).abs() < f64::EPSILON);
let p100 = qc.get_percentile(33, 100);
assert!((p100 - 40.0).abs() < f64::EPSILON);
}
#[test]
fn test_percentile_multiple() {
let mut qc = QualityCount::new();
for _ in 0..3 {
qc.add_value(53);
}
for _ in 0..7 {
qc.add_value(63);
}
assert!((qc.get_percentile(33, 50) - 30.0).abs() < f64::EPSILON);
assert!((qc.get_percentile(33, 25) - 20.0).abs() < f64::EPSILON);
assert!((qc.get_percentile(33, 90) - 30.0).abs() < f64::EPSILON);
}
#[test]
fn test_add_value_overflow_clamps() {
let mut qc = QualityCount::new();
qc.add_value(200);
assert_eq!(qc.get_total_count(), 1);
assert_eq!(qc.get_max_char(), Some(149));
}
}