Merge pull request #1295 from cyphar/set_due_date-use-deck-ease

scheduler: use deck config's initial ease in set_due_date
This commit is contained in:
Damien Elmes 2021-07-16 18:12:21 +10:00 committed by GitHub
commit c75fd5086c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -1,6 +1,8 @@
// Copyright: Ankitects Pty Ltd and contributors // Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use std::collections::HashMap;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use rand::distributions::{Distribution, Uniform}; use rand::distributions::{Distribution, Uniform};
use regex::Regex; use regex::Regex;
@ -9,7 +11,6 @@ use crate::{
card::{Card, CardId, CardQueue, CardType}, card::{Card, CardId, CardQueue, CardType},
collection::Collection, collection::Collection,
config::StringKey, config::StringKey,
deckconfig::INITIAL_EASE_FACTOR_THOUSANDS,
error::Result, error::Result,
prelude::*, prelude::*,
}; };
@ -19,7 +20,14 @@ impl Card {
/// If card is not a review card, convert it into one. /// If card is not a review card, convert it into one.
/// Review/relearning cards have their interval preserved unless /// Review/relearning cards have their interval preserved unless
/// `force_reset` is true. /// `force_reset` is true.
fn set_due_date(&mut self, today: u32, days_from_today: u32, force_reset: bool) { /// If the card has no ease factor (it's new), `ease_factor` is used.
fn set_due_date(
&mut self,
today: u32,
days_from_today: u32,
ease_factor: f32,
force_reset: bool,
) {
let new_due = (today + days_from_today) as i32; let new_due = (today + days_from_today) as i32;
let new_interval = let new_interval =
if force_reset || !matches!(self.ctype, CardType::Review | CardType::Relearn) { if force_reset || !matches!(self.ctype, CardType::Review | CardType::Relearn) {
@ -27,11 +35,12 @@ impl Card {
} else { } else {
self.interval self.interval
}; };
let ease_factor = (ease_factor * 1000.0).round() as u16;
self.schedule_as_review(new_interval, new_due); self.schedule_as_review(new_interval, new_due, ease_factor);
} }
fn schedule_as_review(&mut self, interval: u32, due: i32) { fn schedule_as_review(&mut self, interval: u32, due: i32, ease_factor: u16) {
self.remove_from_filtered_deck_before_reschedule(); self.remove_from_filtered_deck_before_reschedule();
self.interval = interval.max(1); self.interval = interval.max(1);
self.due = due; self.due = due;
@ -40,7 +49,7 @@ impl Card {
if self.ease_factor == 0 { if self.ease_factor == 0 {
// unlike the old Python code, we leave the ease factor alone // unlike the old Python code, we leave the ease factor alone
// if it's already set // if it's already set
self.ease_factor = INITIAL_EASE_FACTOR_THOUSANDS; self.ease_factor = ease_factor;
} }
} }
} }
@ -100,12 +109,31 @@ impl Collection {
let today = self.timing_today()?.days_elapsed; let today = self.timing_today()?.days_elapsed;
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
let distribution = Uniform::from(spec.min..=spec.max); let distribution = Uniform::from(spec.min..=spec.max);
let mut decks_initial_ease: HashMap<DeckId, f32> = HashMap::new();
self.transact(Op::SetDueDate, |col| { self.transact(Op::SetDueDate, |col| {
col.storage.set_search_table_to_card_ids(cids, false)?; col.storage.set_search_table_to_card_ids(cids, false)?;
for mut card in col.storage.all_searched_cards()? { for mut card in col.storage.all_searched_cards()? {
let deck_id = card.original_deck_id.or(card.deck_id);
let ease_factor = match decks_initial_ease.get(&deck_id) {
Some(ease) => *ease,
None => {
let config_id = col
.get_deck(deck_id)?
.ok_or(AnkiError::NotFound)?
.config_id()
.ok_or(AnkiError::NotFound)?;
let ease = col
.get_deck_config(config_id, true)?
.unwrap_or_else(Default::default) // cannot happen
.inner
.initial_ease;
decks_initial_ease.insert(deck_id, ease);
ease
}
};
let original = card.clone(); let original = card.clone();
let days_from_today = distribution.sample(&mut rng); let days_from_today = distribution.sample(&mut rng);
card.set_due_date(today, days_from_today, spec.force_reset); card.set_due_date(today, days_from_today, ease_factor, spec.force_reset);
col.log_manually_scheduled_review(&card, &original, usn)?; col.log_manually_scheduled_review(&card, &original, usn)?;
col.update_card_inner(&mut card, original, usn)?; col.update_card_inner(&mut card, original, usn)?;
} }
@ -169,35 +197,41 @@ mod test {
let mut c = Card::new(NoteId(0), 0, DeckId(0), 0); let mut c = Card::new(NoteId(0), 0, DeckId(0), 0);
// setting the due date of a new card will convert it // setting the due date of a new card will convert it
c.set_due_date(5, 2, false); c.set_due_date(5, 2, 1.8, false);
assert_eq!(c.ctype, CardType::Review); assert_eq!(c.ctype, CardType::Review);
assert_eq!(c.due, 7); assert_eq!(c.due, 7);
assert_eq!(c.interval, 2); assert_eq!(c.interval, 2);
assert_eq!(c.ease_factor, 1800);
// reschedule it again the next day, shifting it from day 7 to day 9 // reschedule it again the next day, shifting it from day 7 to day 9
c.set_due_date(6, 3, false); c.set_due_date(6, 3, 2.5, false);
assert_eq!(c.due, 9); assert_eq!(c.due, 9);
assert_eq!(c.interval, 2); assert_eq!(c.interval, 2);
assert_eq!(c.ease_factor, 1800); // interval doesn't change
// we can bring cards forward too - return it to its original due date // we can bring cards forward too - return it to its original due date
c.set_due_date(6, 1, false); c.set_due_date(6, 1, 2.4, false);
assert_eq!(c.due, 7); assert_eq!(c.due, 7);
assert_eq!(c.interval, 2); assert_eq!(c.interval, 2);
assert_eq!(c.ease_factor, 1800); // interval doesn't change
// we can force the interval to be reset instead of shifted // we can force the interval to be reset instead of shifted
c.set_due_date(6, 3, true); c.set_due_date(6, 3, 2.3, true);
assert_eq!(c.due, 9); assert_eq!(c.due, 9);
assert_eq!(c.interval, 3); assert_eq!(c.interval, 3);
assert_eq!(c.ease_factor, 1800); // interval doesn't change
// should work in a filtered deck // should work in a filtered deck
c.interval = 2; c.interval = 2;
c.ease_factor = 0;
c.original_due = 7; c.original_due = 7;
c.original_deck_id = DeckId(1); c.original_deck_id = DeckId(1);
c.due = -10000; c.due = -10000;
c.queue = CardQueue::New; c.queue = CardQueue::New;
c.set_due_date(6, 1, false); c.set_due_date(6, 1, 2.2, false);
assert_eq!(c.due, 7); assert_eq!(c.due, 7);
assert_eq!(c.interval, 2); assert_eq!(c.interval, 2);
assert_eq!(c.ease_factor, 2200);
assert_eq!(c.queue, CardQueue::Review); assert_eq!(c.queue, CardQueue::Review);
assert_eq!(c.original_due, 0); assert_eq!(c.original_due, 0);
assert_eq!(c.original_deck_id, DeckId(0)); assert_eq!(c.original_deck_id, DeckId(0));
@ -206,8 +240,9 @@ mod test {
c.ctype = CardType::Relearn; c.ctype = CardType::Relearn;
c.original_due = c.due; c.original_due = c.due;
c.due = 12345678; c.due = 12345678;
c.set_due_date(6, 10, false); c.set_due_date(6, 10, 2.1, false);
assert_eq!(c.due, 16); assert_eq!(c.due, 16);
assert_eq!(c.interval, 2); assert_eq!(c.interval, 2);
assert_eq!(c.ease_factor, 2200); // interval doesn't change
} }
} }