use crate::{integer, string};
pub trait Chooser: std::fmt::Debug + Sync {
fn name(&self) -> &'static str;
fn narrow_strings(
&self,
values: &[&[u8]],
offered: &[string::Kind],
depth: u8,
) -> Vec<string::Kind>;
fn narrow_integers(
&self,
values: &[i64],
offered: &[integer::Kind],
depth: u8,
) -> Vec<integer::Kind>;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Exhaustive;
pub const EXHAUSTIVE: Exhaustive = Exhaustive;
impl Chooser for Exhaustive {
fn name(&self) -> &'static str {
"exhaustive"
}
fn narrow_strings(
&self,
_values: &[&[u8]],
offered: &[string::Kind],
_depth: u8,
) -> Vec<string::Kind> {
offered.to_vec()
}
fn narrow_integers(
&self,
_values: &[i64],
offered: &[integer::Kind],
_depth: u8,
) -> Vec<integer::Kind> {
offered.to_vec()
}
}
#[derive(Debug, Clone, Copy)]
pub struct Sampled {
window: usize,
regions: usize,
}
const WINDOW: usize = 1024;
const REGIONS: usize = 8;
const FLOOR: usize = 256 * 1024;
impl Default for Sampled {
fn default() -> Self {
Self { window: WINDOW, regions: REGIONS }
}
}
impl Sampled {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn over(window: usize, regions: usize) -> Self {
Self { window: window.max(1), regions: regions.max(1) }
}
#[must_use]
pub fn size(self) -> usize {
self.window * self.regions
}
fn worth_it(self, count: usize, bytes: usize) -> bool {
count > self.size() && bytes >= FLOOR
}
}
impl Chooser for Sampled {
fn name(&self) -> &'static str {
"sampled"
}
fn narrow_strings(
&self,
values: &[&[u8]],
offered: &[string::Kind],
depth: u8,
) -> Vec<string::Kind> {
let bytes = values.iter().map(|value| value.len()).sum();
if offered.len() < 2 || !self.worth_it(values.len(), bytes) {
return offered.to_vec();
}
let sample = sample(values, self.window, self.regions);
let mut best: Option<(string::Kind, usize)> = None;
for &kind in offered {
let Ok(Some(size)) = string::size_as(kind, &sample, depth) else {
continue;
};
if best.is_none_or(|(_, smallest)| size < smallest) {
best = Some((kind, size));
}
}
best.map_or_else(|| offered.to_vec(), |(kind, _)| vec![kind])
}
fn narrow_integers(
&self,
values: &[i64],
offered: &[integer::Kind],
depth: u8,
) -> Vec<integer::Kind> {
if offered.len() < 2 || !self.worth_it(values.len(), values.len() * 8) {
return offered.to_vec();
}
let sample = sample(values, self.window, self.regions);
let mut best: Option<(integer::Kind, usize)> = None;
for &kind in offered {
let Ok(Some(size)) = integer::size_as(kind, &sample, depth) else {
continue;
};
if best.is_none_or(|(_, smallest)| size < smallest) {
best = Some((kind, size));
}
}
best.map_or_else(|| offered.to_vec(), |(kind, _)| vec![kind])
}
}
pub(crate) fn sample<T: Copy>(values: &[T], window: usize, regions: usize) -> Vec<T> {
let wanted = window * regions;
if values.len() <= wanted {
return values.to_vec();
}
let last = values.len() - window;
let mut out = Vec::with_capacity(wanted);
for region in 0..regions {
let from = if regions == 1 { 0 } else { region * last / (regions - 1) };
out.extend_from_slice(&values[from..from + window]);
}
out
}
#[cfg(test)]
mod tests {
use super::{Chooser, EXHAUSTIVE, Sampled, sample};
use crate::{integer, string};
#[test]
fn a_sample_covers_the_whole_input_and_not_one_end_of_it() {
let values: Vec<i64> = (0..8000).collect();
let taken = sample(&values, 10, 4);
assert_eq!(taken.len(), 40);
assert_eq!(taken[0], 0);
assert_eq!(taken[10], 2663);
assert_eq!(taken[20], 5326);
assert_eq!(taken[30], 7990);
assert_eq!(taken[39], 7999);
}
#[test]
fn an_input_no_bigger_than_the_sample_is_the_sample() {
let values: Vec<i64> = (0..30).collect();
assert_eq!(sample(&values, 10, 4), values);
}
#[test]
fn the_last_window_does_not_run_off_the_end() {
let values: Vec<i64> = (0..100).collect();
let taken = sample(&values, 40, 2);
assert_eq!(taken.len(), 80);
assert_eq!(*taken.last().expect("the sample is not empty"), 99);
}
#[test]
fn the_exhaustive_chooser_hands_back_exactly_what_it_was_offered() {
let offered = [string::Kind::Plain, string::Kind::Fsst, string::Kind::Dict];
assert_eq!(EXHAUSTIVE.narrow_strings(&[b"a".as_slice()], &offered, 0), offered);
let offered = [integer::Kind::Packed, integer::Kind::Delta];
assert_eq!(EXHAUSTIVE.narrow_integers(&[1, 2], &offered, 0), offered);
}
#[test]
fn a_chunk_no_bigger_than_the_sample_is_not_narrowed_at_all() {
let sampled = Sampled::over(4, 2);
let values: Vec<i64> = (0..8).collect();
let offered = [integer::Kind::Packed, integer::Kind::Delta];
assert_eq!(sampled.narrow_integers(&values, &offered, 0), offered);
}
#[test]
fn a_sampled_chooser_returns_one_of_what_it_was_offered() {
let sampled = Sampled::over(16, 2);
let values: Vec<i64> = (0..40_000).map(|index| index / 200).collect();
let offered = [integer::Kind::Packed, integer::Kind::Rle, integer::Kind::Dict];
let narrowed = sampled.narrow_integers(&values, &offered, 0);
assert_eq!(narrowed.len(), 1);
assert!(offered.contains(&narrowed[0]), "{narrowed:?}");
}
#[test]
fn a_chunk_with_plenty_of_values_and_hardly_any_bytes_is_not_sampled() {
let sampled = Sampled::over(16, 2);
let empty = Vec::new();
let values: Vec<&[u8]> = vec![empty.as_slice(); 40_000];
let offered = [string::Kind::Plain, string::Kind::Fsst, string::Kind::Dict];
assert_eq!(sampled.narrow_strings(&values, &offered, 0), offered);
}
}