1use crate::Dataset;
2use crate::DatasetError;
3use crate::transform::{RngSource, SizeConfig};
4use rand::prelude::SliceRandom;
5use rand::{RngExt, distr::Uniform, rngs::StdRng, seq::IteratorRandom};
6use std::{marker::PhantomData, ops::DerefMut, sync::Mutex};
7
8#[derive(Debug, PartialEq)]
10pub struct SamplerDatasetOptions {
11 pub replace_samples: bool,
13
14 pub size_config: SizeConfig,
16
17 pub rng_source: RngSource,
19}
20
21impl Default for SamplerDatasetOptions {
22 fn default() -> Self {
23 Self {
24 replace_samples: true,
25 size_config: SizeConfig::Default,
26 rng_source: RngSource::Default,
27 }
28 }
29}
30
31impl<T> From<Option<T>> for SamplerDatasetOptions
32where
33 T: Into<SamplerDatasetOptions>,
34{
35 fn from(option: Option<T>) -> Self {
36 match option {
37 Some(option) => option.into(),
38 None => Self::default(),
39 }
40 }
41}
42
43impl From<usize> for SamplerDatasetOptions {
44 fn from(size: usize) -> Self {
45 Self::default().with_replacement().with_fixed_size(size)
46 }
47}
48
49impl SamplerDatasetOptions {
50 pub fn with_replace_samples(self, replace_samples: bool) -> Self {
52 Self {
53 replace_samples,
54 ..self
55 }
56 }
57
58 pub fn with_replacement(self) -> Self {
60 self.with_replace_samples(true)
61 }
62
63 pub fn without_replacement(self) -> Self {
65 self.with_replace_samples(false)
66 }
67
68 pub fn with_size<S>(self, source: S) -> Self
70 where
71 S: Into<SizeConfig>,
72 {
73 Self {
74 size_config: source.into(),
75 ..self
76 }
77 }
78
79 pub fn with_source_size(self) -> Self {
81 self.with_size(SizeConfig::Default)
82 }
83
84 pub fn with_fixed_size(self, size: usize) -> Self {
86 self.with_size(size)
87 }
88
89 pub fn with_size_ratio(self, size_ratio: f64) -> Self {
91 self.with_size(size_ratio)
92 }
93
94 pub fn with_rng<R>(self, rng: R) -> Self
96 where
97 R: Into<RngSource>,
98 {
99 Self {
100 rng_source: rng.into(),
101 ..self
102 }
103 }
104
105 pub fn with_system_rng(self) -> Self {
107 self.with_rng(RngSource::Default)
108 }
109
110 pub fn with_seed(self, seed: u64) -> Self {
112 self.with_rng(seed)
113 }
114}
115
116pub struct SamplerDataset<D, I> {
129 dataset: D,
130 size: usize,
131 state: Mutex<SamplerState>,
132 input: PhantomData<I>,
133}
134enum SamplerState {
135 WithReplacement(StdRng),
136 WithoutReplacement(StdRng, Vec<usize>),
137}
138
139impl<D, I> SamplerDataset<D, I>
140where
141 D: Dataset<I>,
142 I: Send + Sync,
143{
144 pub fn new<O>(dataset: D, options: O) -> Self
191 where
192 O: Into<SamplerDatasetOptions>,
193 {
194 let options = options.into();
195 let size = options.size_config.resolve(dataset.len());
196 let rng = options.rng_source.into();
197 Self {
198 dataset,
199 size,
200 state: Mutex::new(match options.replace_samples {
201 true => SamplerState::WithReplacement(rng),
202 false => SamplerState::WithoutReplacement(rng, Vec::with_capacity(size)),
203 }),
204 input: PhantomData,
205 }
206 }
207
208 pub fn with_replacement(dataset: D, size: usize) -> Self {
215 Self::new(
216 dataset,
217 SamplerDatasetOptions::default()
218 .with_replacement()
219 .with_fixed_size(size),
220 )
221 }
222
223 pub fn without_replacement(dataset: D, size: usize) -> Self {
238 Self::new(
239 dataset,
240 SamplerDatasetOptions::default()
241 .without_replacement()
242 .with_fixed_size(size),
243 )
244 }
245
246 pub fn is_with_replacement(&self) -> bool {
252 match self.state.lock().unwrap().deref_mut() {
253 SamplerState::WithReplacement(_) => true,
254 SamplerState::WithoutReplacement(_, _) => false,
255 }
256 }
257
258 fn index(&self) -> usize {
259 match self.state.lock().unwrap().deref_mut() {
260 SamplerState::WithReplacement(rng) => {
261 rng.sample(Uniform::new(0, self.dataset.len()).unwrap())
262 }
263 SamplerState::WithoutReplacement(rng, indices) => {
264 if indices.is_empty() {
265 let idx_range = 0..self.dataset.len();
267 for _ in 0..(self.size / self.dataset.len()) {
268 indices.extend(idx_range.clone())
272 }
273
274 indices.extend(idx_range.sample(rng, self.size - indices.len()));
279
280 indices.shuffle(rng);
282 }
283
284 indices.pop().expect("Indices are refilled when empty.")
285 }
286 }
287 }
288}
289
290impl<D, I> Dataset<I> for SamplerDataset<D, I>
291where
292 D: Dataset<I>,
293 I: Send + Sync,
294{
295 fn get(&self, index: usize) -> Result<I, DatasetError> {
296 assert!(
297 index < self.size,
298 "Index out of bounds for SamplerDataset: {} >= {}",
299 index,
300 self.size
301 );
302 self.dataset.get(self.index())
303 }
304
305 fn len(&self) -> usize {
306 self.size
307 }
308}
309
310#[cfg(test)]
311mod tests {
312 #![allow(clippy::bool_assert_comparison)]
313
314 use super::*;
315 use crate::FakeDataset;
316 use rand::SeedableRng;
317 use std::collections::HashMap;
318
319 #[test]
320 fn test_samplerdataset_options() {
321 let options = SamplerDatasetOptions::default();
322 assert_eq!(options.replace_samples, true);
323 assert_eq!(options.size_config, SizeConfig::Default);
324 assert_eq!(options.rng_source, RngSource::Default);
325
326 let options = options.with_replace_samples(false);
328 assert_eq!(options.replace_samples, false);
329 let options = options.with_replacement();
330 assert_eq!(options.replace_samples, true);
331 let options = options.without_replacement();
332 assert_eq!(options.replace_samples, false);
333
334 let options = options.with_size(SizeConfig::Default);
336 assert_eq!(options.size_config, SizeConfig::Default);
337 let options = options.with_source_size();
338 assert_eq!(options.size_config, SizeConfig::Default);
339 let options = options.with_fixed_size(10);
340 assert_eq!(options.size_config, SizeConfig::Fixed(10));
341 let options = options.with_size_ratio(1.5);
342 assert_eq!(options.size_config, SizeConfig::Ratio(1.5));
343
344 let options = options.with_system_rng();
346 assert_eq!(options.rng_source, RngSource::Default);
347 let options = options.with_seed(42);
348 assert_eq!(options.rng_source, RngSource::Seed(42));
349 let rng = StdRng::seed_from_u64(9);
350 let options = options.with_rng(rng);
351 assert!(matches!(options.rng_source, RngSource::Rng(_)));
352 }
353
354 #[test]
355 fn sampler_dataset_constructors_test() {
356 let ds = SamplerDataset::new(FakeDataset::<u32>::new(10), 15);
357 assert_eq!(ds.len(), 15);
358 assert_eq!(ds.dataset.len(), 10);
359 assert!(ds.is_with_replacement());
360
361 let ds = SamplerDataset::with_replacement(FakeDataset::<u32>::new(10), 15);
362 assert_eq!(ds.len(), 15);
363 assert_eq!(ds.dataset.len(), 10);
364 assert!(ds.is_with_replacement());
365
366 let ds = SamplerDataset::without_replacement(FakeDataset::<u32>::new(10), 15);
367 assert_eq!(ds.len(), 15);
368 assert_eq!(ds.dataset.len(), 10);
369 assert!(!ds.is_with_replacement());
370 }
371
372 #[test]
373 fn sampler_dataset_with_replacement_iter() {
374 let factor = 3;
375 let len_original = 10;
376 let dataset_sampler = SamplerDataset::with_replacement(
377 FakeDataset::<String>::new(len_original),
378 len_original * factor,
379 );
380 let mut total = 0;
381
382 for _item in dataset_sampler.iter() {
383 total += 1;
384 }
385
386 assert_eq!(total, factor * len_original);
387 }
388
389 #[test]
390 fn sampler_dataset_without_replacement_bucket_test() {
391 let factor = 3;
392 let len_original = 10;
393
394 let dataset_sampler = SamplerDataset::new(
395 FakeDataset::<String>::new(len_original),
396 SamplerDatasetOptions::default()
397 .without_replacement()
398 .with_size_ratio(factor as f64),
399 );
400
401 let mut buckets = HashMap::new();
402
403 for item in dataset_sampler.iter().map(Result::unwrap) {
404 let count = match buckets.get(&item) {
405 Some(count) => count + 1,
406 None => 1,
407 };
408
409 buckets.insert(item, count);
410 }
411
412 let mut total = 0;
413 for count in buckets.into_values() {
414 assert_eq!(count, factor);
415 total += count;
416 }
417 assert_eq!(total, factor * len_original);
418 }
419
420 #[test]
421 fn sampler_dataset_without_replacement_uniform_order_test() {
422 let size = 1000;
424 let dataset_sampler =
425 SamplerDataset::without_replacement(FakeDataset::<i32>::new(size), size);
426
427 let indices: Vec<_> = (0..size).map(|_| dataset_sampler.index()).collect();
428 let mean_delta = indices
429 .windows(2)
430 .map(|pair| pair[1].abs_diff(pair[0]))
431 .sum::<usize>() as f64
432 / (size - 1) as f64;
433
434 let expected = (size + 2) as f64 / 3.0;
435
436 assert!(
437 (mean_delta - expected).abs() <= 0.25 * expected,
438 "Sampled indices are not uniformly distributed: mean_delta: {mean_delta}, expected: {expected}"
439 );
440 }
441}