#![warn(missing_docs)]
#![warn(rustdoc::missing_crate_level_docs)]
use std::f32::consts::E;
use rand::Rng;
#[derive(Clone, Debug)]
pub enum UserReview {
TooHard,
JustEnough,
TooEasy,
}
#[inline]
pub fn compute_interval(forgetting_rate: f32, probability: f32) -> i32 {
assert!(forgetting_rate.is_sign_positive());
assert!(probability < 1.0);
let n_days_f = probability.log(E) / (-forgetting_rate);
n_days_f as i32
}
#[derive(Clone, Debug)]
pub struct SchedulingData {
pub interval: i32,
pub difficulty: f32,
pub memory_strength: f32,
pub adjusting_factor: f32,
pub times_reviewed: i32,
pub times_recalled: i32,
}
#[derive(Clone, Debug)]
pub struct UpdateParameters {
pub difficulty_change_factor: f32,
pub memory_strength_change_factor: f32,
}
impl Default for SchedulingData {
fn default() -> Self {
SchedulingData {
interval: 1,
difficulty: 10.0,
memory_strength: 100.0,
adjusting_factor: 1.0,
times_reviewed: 0,
times_recalled: 0,
}
}
}
impl Default for UpdateParameters {
fn default() -> Self {
Self {
difficulty_change_factor: 1.1,
memory_strength_change_factor: 1.60,
}
}
}
pub fn schedule(
item_data: SchedulingData,
user_review: UserReview,
update_parameters: UpdateParameters,
probability: f32,
) -> SchedulingData {
let SchedulingData {
interval: _,
difficulty,
memory_strength,
adjusting_factor,
times_reviewed,
times_recalled,
} = item_data;
let new_difficulty = match user_review {
UserReview::TooHard => difficulty * update_parameters.difficulty_change_factor,
UserReview::JustEnough => difficulty,
UserReview::TooEasy => difficulty * (2.0 - update_parameters.difficulty_change_factor),
};
let new_memory_strength = memory_strength * update_parameters.memory_strength_change_factor;
let new_forgetting_rate = (1.0 / adjusting_factor) * (difficulty / memory_strength);
let next_interval_no_random = compute_interval(new_forgetting_rate, probability);
let mut rng = rand::thread_rng();
let random_range = next_interval_no_random / 10;
let random_change = rng.gen_range(0..random_range * 2) - random_range;
let next_interval = next_interval_no_random + random_change;
SchedulingData {
interval: next_interval,
difficulty: new_difficulty,
memory_strength: new_memory_strength,
adjusting_factor,
times_reviewed: times_reviewed + 1,
times_recalled: times_recalled + 1,
}
}
pub fn update_adjusting_factor(
item_data: SchedulingData,
target_probability: f32,
) -> SchedulingData {
let SchedulingData {
interval,
difficulty,
memory_strength,
adjusting_factor: _,
times_reviewed,
times_recalled,
} = item_data;
let actual_probability = times_recalled as f32 / times_reviewed as f32;
let new_adjusting_factor = target_probability.log(E) / actual_probability.log(E);
SchedulingData {
interval,
difficulty,
memory_strength,
adjusting_factor: new_adjusting_factor,
times_reviewed,
times_recalled,
}
}