use std::sync::Arc;
use parking_lot::RwLock;
use rand::seq::IteratorRandom;
use ustr::{Ustr, UstrSet};
use crate::{
data::{MasteryScore, SchedulerOptions},
scheduler::{Candidate, data::SchedulerData},
};
pub(crate) struct RelearnPile {
options: SchedulerOptions,
pile: Arc<RwLock<UstrSet>>,
}
impl RelearnPile {
pub fn new(options: SchedulerOptions) -> Self {
RelearnPile {
options,
pile: Arc::new(RwLock::new(UstrSet::default())),
}
}
pub fn update(&self, exercise_id: Ustr, score: &MasteryScore) {
let mut relearning_pile = self.pile.write();
match score {
MasteryScore::One | MasteryScore::Two => relearning_pile.insert(exercise_id),
MasteryScore::Three | MasteryScore::Four | MasteryScore::Five => {
relearning_pile.remove(&exercise_id)
}
};
}
fn select_exercises_helper(&self) -> Vec<Candidate> {
let num_to_add = (self.options.batch_size as f32 * self.options.relearn_fraction) as usize;
let pile = self.pile.read();
let relearn_exercises: Vec<_> = pile.iter().sample(&mut rand::rng(), num_to_add);
relearn_exercises
.into_iter()
.map(|exercise_id| Candidate {
exercise_id: *exercise_id,
..Default::default()
})
.collect()
}
pub fn select_exercises(&self, data: &SchedulerData) -> Vec<Candidate> {
self.pile
.write()
.retain(|id| !data.inside_blacklisted(*id).unwrap_or(false));
self.select_exercises_helper()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_update() {
let relearn_pile = RelearnPile::new(SchedulerOptions::default());
let exercise_id = Ustr::from("exercise_1");
let exercise_id_2 = Ustr::from("exercise_2");
relearn_pile.update(exercise_id, &MasteryScore::One);
relearn_pile.update(exercise_id_2, &MasteryScore::Two);
relearn_pile.update(exercise_id_2, &MasteryScore::One);
assert!(relearn_pile.pile.read().contains(&exercise_id));
assert!(relearn_pile.pile.read().contains(&exercise_id_2));
relearn_pile.update(exercise_id, &MasteryScore::Four);
relearn_pile.update(exercise_id_2, &MasteryScore::Five);
assert!(!relearn_pile.pile.read().contains(&exercise_id));
assert!(!relearn_pile.pile.read().contains(&exercise_id_2));
}
#[test]
fn test_add_to_batch() {
let relearn_pile = RelearnPile::new(SchedulerOptions {
batch_size: 10,
relearn_fraction: 0.5,
..SchedulerOptions::default()
});
for i in 0..20 {
let exercise_id = Ustr::from(&format!("exercise_{}", i));
relearn_pile.update(exercise_id, &MasteryScore::One);
}
let pile = relearn_pile.select_exercises_helper();
assert_eq!(pile.len(), 5);
}
}