Add relative overdueness to review order (#1757)

* Add relative overdueness to review order

* Add test for relative overdue
This commit is contained in:
Abdo 2022-04-09 06:20:09 +03:00 committed by GitHub
parent a7ad6aebba
commit 964f0a5763
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 91 additions and 10 deletions

View file

@ -170,6 +170,7 @@ deck-config-sort-order-ascending-intervals = Ascending intervals
deck-config-sort-order-descending-intervals = Descending intervals deck-config-sort-order-descending-intervals = Descending intervals
deck-config-sort-order-ascending-ease = Ascending ease deck-config-sort-order-ascending-ease = Ascending ease
deck-config-sort-order-descending-ease = Descending ease deck-config-sort-order-descending-ease = Descending ease
deck-config-sort-order-relative-overdueness = Relative overdueness
deck-config-display-order-will-use-current-deck = deck-config-display-order-will-use-current-deck =
Anki will use the display order from the deck you Anki will use the display order from the deck you
select to study, and not any subdecks it may have. select to study, and not any subdecks it may have.

View file

@ -68,6 +68,7 @@ message DeckConfig {
REVIEW_CARD_ORDER_INTERVALS_DESCENDING = 4; REVIEW_CARD_ORDER_INTERVALS_DESCENDING = 4;
REVIEW_CARD_ORDER_EASE_ASCENDING = 5; REVIEW_CARD_ORDER_EASE_ASCENDING = 5;
REVIEW_CARD_ORDER_EASE_DESCENDING = 6; REVIEW_CARD_ORDER_EASE_DESCENDING = 6;
REVIEW_CARD_ORDER_RELATIVE_OVERDUENESS = 7;
} }
enum ReviewMix { enum ReviewMix {
REVIEW_MIX_MIX_WITH_REVIEWS = 0; REVIEW_MIX_MIX_WITH_REVIEWS = 0;

View file

@ -265,6 +265,7 @@ mod test {
use super::*; use super::*;
use crate::{ use crate::{
backend_proto::deck_config::config::{NewCardGatherPriority, NewCardSortOrder}, backend_proto::deck_config::config::{NewCardGatherPriority, NewCardSortOrder},
card::{CardQueue, CardType},
collection::open_test_collection, collection::open_test_collection,
}; };
@ -296,10 +297,29 @@ mod test {
}) })
.collect() .collect()
} }
fn set_deck_review_order(&mut self, deck: &mut Deck, order: ReviewCardOrder) {
let mut conf = DeckConfig::default();
conf.inner.review_order = order as i32;
self.add_or_update_deck_config(&mut conf).unwrap();
deck.normal_mut().unwrap().config_id = conf.id.0;
self.add_or_update_deck(deck).unwrap();
}
fn queue_as_due_and_ivl(&mut self, deck_id: DeckId) -> Vec<(i32, u32)> {
self.build_queues(deck_id)
.unwrap()
.iter()
.map(|entry| {
let card = self.storage.get_card(entry.card_id()).unwrap().unwrap();
(card.due, card.interval)
})
.collect()
}
} }
#[test] #[test]
fn queue_building() -> Result<()> { fn new_queue_building() -> Result<()> {
let mut col = open_test_collection(); let mut col = open_test_collection();
col.set_config_bool(BoolKey::Sched2021, true, false)?; col.set_config_bool(BoolKey::Sched2021, true, false)?;
@ -366,4 +386,52 @@ mod test {
Ok(()) Ok(())
} }
#[test]
fn review_queue_building() -> Result<()> {
let mut col = open_test_collection();
col.set_config_bool(BoolKey::Sched2021, true, false)?;
let mut deck = col.get_or_create_normal_deck("Default").unwrap();
let nt = col.get_notetype_by_name("Basic")?.unwrap();
let mut cards = vec![];
// relative overdueness
let expected_queue = vec![
(-150, 1),
(-100, 1),
(-50, 1),
(-150, 5),
(-100, 5),
(-50, 5),
(-150, 20),
(-150, 20),
(-100, 20),
(-50, 20),
(-150, 100),
(-100, 100),
(-50, 100),
(0, 1),
(0, 5),
(0, 20),
(0, 100),
];
for t in expected_queue.iter() {
let mut note = nt.new_note();
note.set_field(0, "foo")?;
note.id.0 = 0;
col.add_note(&mut note, deck.id)?;
let mut card = col.storage.get_card_by_ordinal(note.id, 0)?.unwrap();
card.interval = t.1;
card.due = t.0;
card.ctype = CardType::Review;
card.queue = CardQueue::Review;
cards.push(card);
}
col.update_cards_maybe_undoable(cards, false)?;
col.set_deck_review_order(&mut deck, ReviewCardOrder::RelativeOverdueness);
assert_eq!(col.queue_as_due_and_ivl(deck.id), expected_queue);
Ok(())
}
} }

View file

@ -4,7 +4,7 @@
pub(crate) mod data; pub(crate) mod data;
pub(crate) mod filtered; pub(crate) mod filtered;
use std::{collections::HashSet, convert::TryFrom, result}; use std::{collections::HashSet, convert::TryFrom, fmt, result};
use rusqlite::{ use rusqlite::{
named_params, params, named_params, params,
@ -216,7 +216,7 @@ impl super::SqliteStorage {
where where
F: FnMut(DueCard) -> bool, F: FnMut(DueCard) -> bool,
{ {
let order_clause = review_order_sql(order); let order_clause = review_order_sql(order, day_cutoff);
let mut stmt = self.db.prepare_cached(&format!( let mut stmt = self.db.prepare_cached(&format!(
"{} order by {}", "{} order by {}",
include_str!("due_cards.sql"), include_str!("due_cards.sql"),
@ -612,11 +612,13 @@ enum ReviewOrderSubclause {
IntervalsDescending, IntervalsDescending,
EaseAscending, EaseAscending,
EaseDescending, EaseDescending,
RelativeOverdueness { today: u32 },
} }
impl ReviewOrderSubclause { impl fmt::Display for ReviewOrderSubclause {
fn to_str(self) -> &'static str { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { let temp_string;
let clause = match self {
ReviewOrderSubclause::Day => "due", ReviewOrderSubclause::Day => "due",
ReviewOrderSubclause::Deck => "(select rowid from active_decks ad where ad.id = did)", ReviewOrderSubclause::Deck => "(select rowid from active_decks ad where ad.id = did)",
ReviewOrderSubclause::Random => "fnvhash(id, mod)", ReviewOrderSubclause::Random => "fnvhash(id, mod)",
@ -624,11 +626,16 @@ impl ReviewOrderSubclause {
ReviewOrderSubclause::IntervalsDescending => "ivl desc", ReviewOrderSubclause::IntervalsDescending => "ivl desc",
ReviewOrderSubclause::EaseAscending => "factor asc", ReviewOrderSubclause::EaseAscending => "factor asc",
ReviewOrderSubclause::EaseDescending => "factor desc", ReviewOrderSubclause::EaseDescending => "factor desc",
} ReviewOrderSubclause::RelativeOverdueness { today } => {
temp_string = format!("ivl / cast({today}-due+0.001 as real)", today = today);
&temp_string
}
};
write!(f, "{}", clause)
} }
} }
fn review_order_sql(order: ReviewCardOrder) -> String { fn review_order_sql(order: ReviewCardOrder, today: u32) -> String {
let mut subclauses = match order { let mut subclauses = match order {
ReviewCardOrder::Day => vec![ReviewOrderSubclause::Day], ReviewCardOrder::Day => vec![ReviewOrderSubclause::Day],
ReviewCardOrder::DayThenDeck => vec![ReviewOrderSubclause::Day, ReviewOrderSubclause::Deck], ReviewCardOrder::DayThenDeck => vec![ReviewOrderSubclause::Day, ReviewOrderSubclause::Deck],
@ -637,12 +644,15 @@ fn review_order_sql(order: ReviewCardOrder) -> String {
ReviewCardOrder::IntervalsDescending => vec![ReviewOrderSubclause::IntervalsDescending], ReviewCardOrder::IntervalsDescending => vec![ReviewOrderSubclause::IntervalsDescending],
ReviewCardOrder::EaseAscending => vec![ReviewOrderSubclause::EaseAscending], ReviewCardOrder::EaseAscending => vec![ReviewOrderSubclause::EaseAscending],
ReviewCardOrder::EaseDescending => vec![ReviewOrderSubclause::EaseDescending], ReviewCardOrder::EaseDescending => vec![ReviewOrderSubclause::EaseDescending],
ReviewCardOrder::RelativeOverdueness => {
vec![ReviewOrderSubclause::RelativeOverdueness { today }]
}
}; };
subclauses.push(ReviewOrderSubclause::Random); subclauses.push(ReviewOrderSubclause::Random);
let v: Vec<_> = subclauses let v: Vec<_> = subclauses
.into_iter() .iter()
.map(ReviewOrderSubclause::to_str) .map(ReviewOrderSubclause::to_string)
.collect(); .collect();
v.join(", ") v.join(", ")
} }

View file

@ -42,6 +42,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
tr.deckConfigSortOrderDescendingIntervals(), tr.deckConfigSortOrderDescendingIntervals(),
tr.deckConfigSortOrderAscendingEase(), tr.deckConfigSortOrderAscendingEase(),
tr.deckConfigSortOrderDescendingEase(), tr.deckConfigSortOrderDescendingEase(),
tr.deckConfigSortOrderRelativeOverdueness(),
]; ];
const GatherOrder = DeckConfig.DeckConfig.Config.NewCardGatherPriority; const GatherOrder = DeckConfig.DeckConfig.Config.NewCardGatherPriority;