finn 0.1.0

An Ikea shark companion for your Rust project
Documentation

#[derive(Clone, Debug, PartialEq, PartialOrd)]
pub struct SuperMemo2Item {
    /// Interval in days between reviews.
    pub interval: f64,
    
    /// A rough grade of the base difficulty of recalling the card.
    pub easiness_factor: f64,
 
    /// Number of correct (grade >= 3) answers in a row.
    pub correct_streak: u32,
}

impl SuperMemo2Item {
    pub fn after_review(&self, user_grade: SuperMemo2UserGrade) -> SuperMemo2Item {
        supermemo2_review(user_grade, &self)
    }
}

impl Default for SuperMemo2Item {
    fn default() -> Self {
        Self { interval: 0., easiness_factor: 2.5, correct_streak: 0 }
    }
}

#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum SuperMemo2UserGrade {
    /// "Total blackout", complete failure to recall the information.
    IncorrectTotal = 0,
    /// Incorrect response, but upon seeing the correct answer it seemed easy to remember.
    IncorrectFamiliar = 1,
    /// Incorrect response, but upon seeing the correct answer it seemed easy to remember.
    IncorrectClose = 2,

    /// Correct response, but required significant effort to recall.
    CorrectHard = 3,
    /// Correct response, after some hesitation.
    CorrectHesistant = 4,
    /// Correct response with perfect recall.
    CorrectPerfect = 5,
}

impl SuperMemo2UserGrade {
    fn correct(&self) -> bool {
        use SuperMemo2UserGrade::*;
        match self {
            IncorrectTotal |
            IncorrectFamiliar |
            IncorrectClose => false,

            CorrectHard |
            CorrectHesistant |
            CorrectPerfect => true,
        }
    }
}


/// Returns the resulting item parameters after a user review with given score.
pub fn supermemo2_review(user_grade: SuperMemo2UserGrade, params: &SuperMemo2Item) -> SuperMemo2Item {

   let interval = match (user_grade.correct(), params.correct_streak) {
        (false, _) => 1.,
        (true, 0) => 1.,
        (true, 1) => 6.,
        (true, _) => {
            params.interval * params.easiness_factor
        }
   };

   let easiness_factor = {
        /* 
            // these parentheses...
            EF ← EF + 0.1 − ((5 − q) *
                (0.08 + (5 − q) × 0.02))
            if EF < 1.3 then
                EF ← 1.3
        */
        let qi = (5 - user_grade as u32) as f64;

        let constant = params.easiness_factor + 0.1; 
        let variable_part = qi * (0.08 + (qi * 0.02));
        let pre_floor = constant - variable_part;

        pre_floor.max(1.3)
   };

   let correct_streak = if user_grade.correct() { params.correct_streak + 1 } else { 0 };

   SuperMemo2Item { interval, easiness_factor, correct_streak }
}



#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test1() {
        use SuperMemo2UserGrade::*;

        let res = SuperMemo2Item::default()
            .after_review(IncorrectTotal);

        assert_eq!(res, SuperMemo2Item {
            interval: 1.,
            easiness_factor: 1.7000000000000002,
            correct_streak: 0,
        });
    }

    #[test]
    fn test2() {
        use SuperMemo2UserGrade::*;

        let res = SuperMemo2Item::default()
            .after_review(IncorrectTotal)
            .after_review(IncorrectTotal);

        assert_eq!(res, SuperMemo2Item {
            interval: 1.,
            easiness_factor: 1.3,
            correct_streak: 0,
        });
    }

    #[test]
    fn correct_streak() {
        use SuperMemo2UserGrade::*;

        let res = SuperMemo2Item::default()
            .after_review(CorrectPerfect)
            .after_review(CorrectPerfect);

        assert_eq!(res, SuperMemo2Item {
            interval: 6.,
            easiness_factor: 2.7,
            correct_streak: 2,
        });
    }



}