use core::ops::Range;
use std::{io, string::String, vec::Vec};
pub(super) const MIN_TRAIN_SAMPLES: usize = 5;
pub(super) struct SampleSet<'a> {
data: &'a [u8],
offsets: Vec<usize>,
}
impl<'a> SampleSet<'a> {
pub(super) fn new(data: &'a [u8], sizes: &[usize]) -> io::Result<Self> {
let mut offsets = Vec::with_capacity(sizes.len() + 1);
offsets.push(0);
let mut end = 0usize;
for &size in sizes {
end = end
.checked_add(size)
.filter(|&end| end <= data.len())
.ok_or_else(|| {
refuse(
TrainingError::Samples,
"the sample sizes add up to more than the samples",
)
})?;
offsets.push(end);
}
if end != data.len() {
return Err(refuse(
TrainingError::Samples,
"the sample sizes do not add up to the samples",
));
}
Ok(Self { data, offsets })
}
pub(super) fn count(&self) -> usize {
self.offsets.len() - 1
}
pub(super) fn offsets(&self) -> &[usize] {
&self.offsets
}
pub(super) fn sample(&self, index: usize) -> &'a [u8] {
&self.data[self.offsets[index]..self.offsets[index + 1]]
}
pub(super) fn leading(&self, count: usize) -> &'a [u8] {
&self.data[..self.offsets[count]]
}
pub(super) fn check_holds_dmer(&self, train: usize, span: usize) -> io::Result<()> {
if self.offsets[..=train]
.windows(2)
.any(|bounds| bounds[1] - bounds[0] >= span)
{
return Ok(());
}
Err(refuse(
TrainingError::Samples,
&std::format!("no training sample is {span} bytes long, so none holds a dmer to count"),
))
}
pub(super) fn split(&self, split_point: f64) -> io::Result<Split> {
split_count(self.count(), split_point)
}
}
pub(super) fn split_count(count: usize, split_point: f64) -> io::Result<Split> {
let split = if split_point < 1.0 {
let train = (count as f64 * split_point) as usize;
Split {
train,
test: train..count,
}
} else {
Split {
train: count,
test: 0..count,
}
};
if split.train < MIN_TRAIN_SAMPLES {
return Err(refuse(
TrainingError::Samples,
&std::format!(
"{} training sample(s) is too few; at least {MIN_TRAIN_SAMPLES} are needed",
split.train
),
));
}
if split.test.is_empty() {
return Err(refuse(
TrainingError::Samples,
"the split leaves no sample to score the dictionary on",
));
}
Ok(split)
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(super) struct Split {
pub(super) train: usize,
pub(super) test: Range<usize>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum TrainingError {
Parameter,
Samples,
DictionaryTooSmall,
}
impl TrainingError {
pub fn of(error: &io::Error) -> Option<Self> {
error
.get_ref()?
.downcast_ref::<Refusal>()
.map(|refusal| refusal.cause)
}
}
#[derive(Debug)]
struct Refusal {
cause: TrainingError,
reason: String,
}
impl core::fmt::Display for Refusal {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(&self.reason)
}
}
impl std::error::Error for Refusal {}
pub(super) fn refuse(cause: TrainingError, reason: &str) -> io::Error {
io::Error::new(
io::ErrorKind::InvalidInput,
Refusal {
cause,
reason: reason.into(),
},
)
}