use crate::{Result, StrobeError};
use nthash_rs::kmer::NtHash;
pub trait KmerHasher: Send + Sync + 'static {
fn hash_all(&self, seq: &[u8], k: usize) -> Result<Vec<u64>>;
}
pub struct NtHash64;
impl Default for NtHash64 {
fn default() -> Self {
Self
}
}
impl KmerHasher for NtHash64 {
fn hash_all(&self, seq: &[u8], k: usize) -> Result<Vec<u64>> {
if !(1..=64).contains(&k) {
return Err(StrobeError::StrobeLengthTooSmall);
}
if seq.len() < k {
return Err(StrobeError::SequenceTooShort);
}
let mut roller = NtHash::new(seq, k as u16, 1, 0).map_err(StrobeError::from)?;
let mut out = Vec::with_capacity(seq.len() - k + 1);
while roller.roll() {
out.push(roller.hashes()[0]);
}
Ok(out)
}
}
pub(crate) fn check_hash_count(hashes: &[u64], seq_len: usize, k: usize) -> Result<()> {
if hashes.len() != seq_len - k + 1 {
return Err(StrobeError::IncompleteHashValues);
}
Ok(())
}
#[derive(Debug, Clone)]
pub(crate) struct MinWindow {
idx_q: Box<[usize]>,
val_q: Box<[u64]>,
head: usize,
len: usize,
w: usize,
next_pos: usize,
hist: Box<[(usize, u64)]>,
hist_mask: usize,
}
impl MinWindow {
pub(crate) fn new(w: usize, lag: usize) -> Self {
assert!(w >= 1, "window size must be ≥ 1");
let hist_len = (lag + 1).next_power_of_two();
Self {
idx_q: vec![0usize; w].into_boxed_slice(),
val_q: vec![0u64; w].into_boxed_slice(),
head: 0,
len: 0,
w,
next_pos: 0,
hist: vec![(0usize, u64::MAX); hist_len].into_boxed_slice(),
hist_mask: hist_len - 1,
}
}
#[inline(always)]
fn wrap(&self, x: usize) -> usize {
if x >= self.w { x - self.w } else { x }
}
#[inline]
fn push(&mut self, i: usize, h: u64) {
let window_start = i.saturating_sub(self.w - 1);
while self.len > 0 && self.idx_q[self.head] < window_start {
self.head = self.wrap(self.head + 1);
self.len -= 1;
}
while self.len > 0 && self.val_q[self.wrap(self.head + self.len - 1)] > h {
self.len -= 1;
}
let tail = self.wrap(self.head + self.len);
self.idx_q[tail] = i;
self.val_q[tail] = h;
self.len += 1;
}
#[inline]
fn min(&self) -> (usize, u64) {
(self.idx_q[self.head], self.val_q[self.head])
}
#[inline]
pub(crate) fn at(&self, p: usize) -> (usize, u64) {
self.hist[p & self.hist_mask]
}
#[inline]
pub(crate) fn advance_to(&mut self, hashes: &[u64], p: usize) -> (usize, u64) {
if self.hist_mask == 0 {
while self.next_pos <= p {
self.push(self.next_pos, hashes[self.next_pos]);
self.next_pos += 1;
}
return self.min();
}
while self.next_pos <= p {
let i = self.next_pos;
self.push(i, hashes[i]);
self.hist[i & self.hist_mask] = self.min();
self.next_pos += 1;
}
self.at(p)
}
}
pub fn compute_min_hashes(hashes: &[u64], w: usize) -> (Vec<usize>, Vec<u64>) {
assert!(w >= 1, "window size must be ≥ 1");
let n = hashes.len();
if w == 1 {
return ((0..n).collect(), hashes.to_vec());
}
let mut locs = vec![0usize; n];
let mut mins = vec![u64::MAX; n];
let mut idx_q = vec![0usize; w];
let mut val_q = vec![0u64; w];
let mut head = 0usize;
let mut len = 0usize;
#[inline(always)]
fn wrap(x: usize, w: usize) -> usize {
if x >= w { x - w } else { x }
}
for (i, &h) in hashes.iter().enumerate() {
let window_start = i.saturating_sub(w - 1);
while len > 0 && idx_q[head] < window_start {
head = wrap(head + 1, w);
len -= 1;
}
while len > 0 && val_q[wrap(head + len - 1, w)] > h {
len -= 1;
}
let tail = wrap(head + len, w);
idx_q[tail] = i;
val_q[tail] = h;
len += 1;
if i >= w - 1 {
locs[i] = idx_q[head];
mins[i] = val_q[head];
}
}
(locs, mins)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn slide_min_window_ties_pick_earliest() {
let v = [5u64, 3, 3, 1, 1, 1, 4, 2, 2, 9, 9, 0];
for (w, want) in [
(2usize, &[1usize, 1, 3, 3, 4, 5, 7, 7, 8, 9, 11][..]),
(3, &[1, 3, 3, 3, 4, 5, 7, 7, 8, 11][..]),
(4, &[3, 3, 3, 3, 4, 5, 7, 7, 11][..]),
] {
let (locs, mins) = compute_min_hashes(&v, w);
assert_eq!(&locs[w - 1..], want, "locations for w={w}");
for (i, &loc) in locs.iter().enumerate().skip(w - 1) {
assert_eq!(v[loc], mins[i], "value at reported loc for w={w}, i={i}");
}
}
}
#[test]
fn min_window_matches_bulk_on_ties() {
let v = [5u64, 3, 3, 1, 1, 1, 4, 2, 2, 9, 9, 0];
for (w, want) in [
(2usize, &[1usize, 1, 3, 3, 4, 5, 7, 7, 8, 9, 11][..]),
(3, &[1, 3, 3, 3, 4, 5, 7, 7, 8, 11][..]),
(4, &[3, 3, 3, 3, 4, 5, 7, 7, 11][..]),
] {
let (bulk_locs, bulk_mins) = compute_min_hashes(&v, w);
let mut win = MinWindow::new(w, 2);
for (offset, &want_loc) in want.iter().enumerate() {
let p = w - 1 + offset;
assert_eq!(
win.advance_to(&v, p),
(want_loc, v[want_loc]),
"w={w} p={p}"
);
assert_eq!(win.at(p), (bulk_locs[p], bulk_mins[p]), "w={w} p={p}");
if p > w {
assert_eq!(win.at(p - 2), (bulk_locs[p - 2], bulk_mins[p - 2]));
}
}
}
}
#[test]
fn slide_min_window_three() {
let v = [5, 3, 6, 1, 4];
let (locs, mins) = compute_min_hashes(&v, 3);
assert_eq!(&mins[2..], &[3, 1, 1]);
assert_eq!(&locs[2..], &[1, 3, 3]);
}
}