use crate::prelude::*;
use crate::sized_iter::SizedIter;
use crate::staff::Staff;
use rand::prelude::*;
use rand::seq::index::sample;
use rand_pcg::Pcg32;
use simple_scan::prelude::*;
use std::collections::BTreeMap;
pub struct RandomGrouping<'r> {
stable: bool,
rounding: SizeRounding,
rng: Staff<'r, dyn RngCore>,
}
impl<'r> RandomGrouping<'r> {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn auto_seed() -> Self {
Self {
rng: Staff::new_own(Box::<ThreadRng>::default()),
..Default::default()
}
}
#[must_use]
pub fn from_seed(seed: u64) -> Self {
Self {
rng: Staff::new_own(Box::new(Pcg32::seed_from_u64(seed))),
..Default::default()
}
}
#[must_use]
pub fn from_rng(rng: &'r mut dyn RngCore) -> Self {
Self {
rng: Staff::new_borrow(rng),
..Default::default()
}
}
#[must_use]
pub fn stable(&self) -> bool {
self.stable
}
#[must_use]
pub fn rounding(&self) -> SizeRounding {
self.rounding
}
pub fn with_stable(mut self, value: bool) -> Self {
self.stable = value;
self
}
pub fn with_rounding(mut self, value: SizeRounding) -> Self {
self.rounding = value;
self
}
pub fn divide_by_size<I>(&mut self, samples: I, sizes: &[usize]) -> Vec<Vec<I::Item>>
where
I: IntoIterator,
{
let mut samples_iter = samples.into_iter();
let mut samples_iter = SizedIter::new(&mut samples_iter);
let samples_len = samples_iter.size_hint().1.unwrap();
let select_len = sizes.iter().sum::<usize>();
if samples_len < sizes.iter().sum() {
panic!("Samples length is greater than sizes total.");
}
let mut table = BTreeMap::new();
let idxs = sample(&mut *self.rng, samples_len, select_len).into_vec();
let group_areas = sizes.iter().cloned().trace2(0, |total, size| total + size);
let group_ranges = group_areas.map(|(lower, upper)| lower..upper);
for (group_idx, group_range) in group_ranges.enumerate() {
for &group_item_idx in &idxs[group_range] {
table.insert(group_item_idx, group_idx);
}
}
let mut results = Vec::with_capacity(sizes.len());
let mut prev_idx = -1;
for &size in sizes {
results.push(Vec::with_capacity(size));
}
for (idx, group_idx) in table {
let idx_progress = (idx as isize - prev_idx) as usize;
let sample = samples_iter.nth(idx_progress - 1).unwrap();
results[group_idx].push(sample);
prev_idx = idx as isize;
}
if !self.stable {
for group in results.iter_mut() {
group.shuffle(&mut *self.rng);
}
}
results
}
pub fn divide_by_ratio<I>(&mut self, samples: I, ratios: &[f64]) -> Vec<Vec<I::Item>>
where
I: IntoIterator,
{
if !ratios.iter().all(Self::check_ratio) {
panic!("Ratios contains illegal value.");
}
if ratios.iter().sum::<f64>() > 1.0 {
panic!("Ratios total is greater than 1.");
}
let mut samples_iter = samples.into_iter();
let samples_iter = SizedIter::new(&mut samples_iter);
let samples_len = samples_iter.size_hint().1.unwrap();
let sizes = self.ratios_to_sizes(ratios, samples_len);
self.divide_by_size(samples_iter, &sizes)
}
pub fn divide_slice_by_size<'t, T>(
&mut self,
samples: &'t [T],
sizes: &[usize],
) -> Vec<Vec<&'t T>> {
if samples.len() < sizes.iter().sum() {
panic!("Samples length is greater than sizes total.");
}
let (len, amount) = (samples.len(), sizes.iter().sum::<usize>());
let mut idxs = sample(&mut *self.rng, len, amount).into_vec();
let mut results = Vec::with_capacity(sizes.len());
for (lower, upper) in sizes.iter().cloned().trace2(0, |total, size| total + size) {
let group_range = lower..upper;
let group_item_idxs = sort_if(self.stable, &mut idxs[group_range]);
let group_items = from_idxs(samples, group_item_idxs);
results.push(group_items);
}
return results;
fn sort_if(flag: bool, slice: &mut [usize]) -> &[usize] {
if flag {
slice.sort();
}
slice
}
fn from_idxs<'t, T>(slice: &'t [T], idxs: &[usize]) -> Vec<&'t T> {
let mut result = Vec::with_capacity(idxs.len());
for &idx in idxs {
result.push(&slice[idx]);
}
result
}
}
pub fn divide_slice_by_ratio<'t, T>(
&mut self,
samples: &'t [T],
ratios: &[f64],
) -> Vec<Vec<&'t T>> {
if !ratios.iter().all(Self::check_ratio) {
panic!("Ratios contains illegal value.");
}
if ratios.iter().sum::<f64>() > 1.0 {
panic!("Ratios total is greater than 1.");
}
let sizes = self.ratios_to_sizes(ratios, samples.len());
self.divide_by_size(samples, &sizes)
}
fn check_ratio(x: &f64) -> bool {
!x.is_nan() && *x >= 0.0 && x.is_finite()
}
fn ratios_to_sizes(&self, ratios: &[f64], len: usize) -> Vec<usize> {
return match self.rounding() {
SizeRounding::Floor => floor(ratios, len),
SizeRounding::Tail => tail(ratios, len),
SizeRounding::Each => each(ratios, len),
};
fn floor(ratios: &[f64], len: usize) -> Vec<usize> {
let results = ratios.iter().map(|x| (x * len as f64).floor() as usize);
results.collect()
}
fn tail(ratios: &[f64], len: usize) -> Vec<usize> {
let sizes = ratios.iter().map(|x| (x * len as f64).round() as usize);
let points = sizes.trace(0, |&s, x| (s + x).min(len));
let results = points.diff(0, |c, p| c - p);
results.collect()
}
fn each(ratios: &[f64], len: usize) -> Vec<usize> {
let points = ratios.iter().trace(0.0, |&s, x| s + x);
let points = points.map(move |x| (x * len as f64).round() as usize);
let results = points.diff(0, |c, p| c - p);
results.collect()
}
}
}
impl Default for RandomGrouping<'_> {
fn default() -> Self {
Self {
stable: true,
rounding: SizeRounding::Floor,
rng: Staff::new_own(Box::new(Pcg32::seed_from_u64(0))),
}
}
}