use crate::{
data::views::{IndexAdjustable, SliceRange},
kmer::{FindKmers, FindKmersInSeq},
prelude::{NucleotidesView, Translate},
private::Sealed,
};
use std::{
marker::PhantomData,
ops::{Bound, Range},
};
pub struct RangeSearch<'a, Q: ?Sized> {
pub(crate) slice: &'a [u8],
pub(crate) starting_at: usize,
phantom: PhantomData<Q>,
}
impl<'a, Q: ?Sized> RangeSearch<'a, Q> {
fn new<R>(sequence: &'a [u8], range: R) -> Self
where
R: SliceRange, {
let slice = &sequence[range.clone()];
let starting_at = match range.start_bound() {
Bound::Included(&start) => start,
Bound::Unbounded => 0,
Bound::Excluded(&start) => start.saturating_add(1),
};
Self {
slice,
starting_at,
phantom: PhantomData,
}
}
}
impl<Q> RangeSearch<'_, Q>
where
Q: Translate,
{
#[inline]
#[must_use]
pub fn find_next_aa(self, aa: u8) -> Option<usize> {
NucleotidesView::from(self.slice)
.find_next_aa(aa)
.map(|index| self.adjust_to_context(&index))
}
#[inline]
#[must_use]
pub fn find_next_aa_in_frame(mut self, aa: u8) -> Option<usize> {
let starting_at_in_frame = self.starting_at.next_multiple_of(3);
let offset = starting_at_in_frame - self.starting_at;
self.starting_at += offset;
self.slice = self.slice.get(offset..)?;
NucleotidesView::from(self.slice)
.find_next_aa_in_frame(aa)
.map(|index| self.adjust_to_context(&index))
}
}
impl<'a, Q: ?Sized> RangeSearch<'a, Q> {
#[inline]
pub(crate) fn adjust_to_context<I: IndexAdjustable>(&self, index: &I) -> I {
index.add(self.starting_at)
}
#[inline]
pub fn position<P>(&self, mut predicate: P) -> Option<usize>
where
P: FnMut(u8) -> bool, {
self.slice
.iter()
.position(|&item| predicate(item))
.map(|index| self.adjust_to_context(&index))
}
#[inline]
pub fn rposition<P>(&self, mut predicate: P) -> Option<usize>
where
P: FnMut(u8) -> bool, {
self.slice
.iter()
.rposition(|&item| predicate(item))
.map(|index| self.adjust_to_context(&index))
}
#[inline]
pub fn find_kmers<const MAX_LEN: usize, T>(self, kmers: &T) -> Option<Range<usize>>
where
T: FindKmersInSeq<MAX_LEN>, {
self.slice.find_kmers(kmers).map(|r| self.adjust_to_context(&r))
}
#[inline]
pub fn find_kmers_rev<const MAX_LEN: usize, T>(self, kmers: &T) -> Option<Range<usize>>
where
T: FindKmersInSeq<MAX_LEN>, {
self.slice.find_kmers_rev(kmers).map(|r| self.adjust_to_context(&r))
}
#[inline]
pub fn find_all_kmers<const MAX_LEN: usize, T>(self, kmers: &'a T) -> impl Iterator<Item = Range<usize>> + 'a
where
Q: 'a,
T: FindKmersInSeq<MAX_LEN>, {
kmers
.find_all_in_seq(self.slice.as_ref())
.map(move |r| self.adjust_to_context(&r))
}
#[inline]
pub fn find_all_kmers_rev<const MAX_LEN: usize, T>(self, kmers: &'a T) -> impl Iterator<Item = Range<usize>> + 'a
where
Q: 'a,
T: FindKmersInSeq<MAX_LEN>, {
kmers.find_all_in_seq_rev(self.slice).map(move |r| self.adjust_to_context(&r))
}
}
pub trait ToRangeSearch: AsRef<[u8]> + Sealed {
fn search_in<R: SliceRange>(&self, range: R) -> RangeSearch<'_, Self> {
RangeSearch::new(self.as_ref(), range)
}
#[inline]
fn search_in_first(&self, n: usize) -> RangeSearch<'_, Self> {
Self::search_in(self, ..n.min(self.as_ref().len()))
}
#[inline]
fn search_in_last(&self, n: usize) -> RangeSearch<'_, Self> {
Self::search_in(self, self.as_ref().len().saturating_sub(n)..)
}
}
impl<T: AsRef<[u8]> + Sealed> ToRangeSearch for T {}
#[cfg(test)]
mod test {
use super::*;
use crate::search::ByteSubstring;
#[test]
fn search_nucleotides() {
let seq = NucleotidesView::from(b"CCCATGCCCCATGCCATG");
let idx = seq.search_in(8..).find_next_aa_in_frame(b'M');
assert_eq!(idx, Some(15));
}
#[test]
fn search_regular() {
let seq = b"GGGAAGCATCACGTATCGA";
let contains = seq.search_in(2..).contains_substring(b"GGG");
assert!(!contains);
}
}