mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00
Add descending retrievability (#3559)
* "relative overdueness" -> "retrievability ascending" * Add 'retrievability descending'
This commit is contained in:
parent
a150eda287
commit
b646f09c68
10 changed files with 115 additions and 42 deletions
|
@ -209,7 +209,9 @@ 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-ascending-difficulty = Easy cards first
|
deck-config-sort-order-ascending-difficulty = Easy cards first
|
||||||
deck-config-sort-order-descending-difficulty = Difficult cards first
|
deck-config-sort-order-descending-difficulty = Difficult cards first
|
||||||
deck-config-sort-order-relative-overdueness = Relative overdueness
|
deck-config-sort-order-retrievability-ascending = Ascending retrievability
|
||||||
|
deck-config-sort-order-retrievability-descending = Descending retrievability
|
||||||
|
|
||||||
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.
|
||||||
|
|
|
@ -24,7 +24,6 @@ decks-order-added = Order added
|
||||||
decks-order-due = Order due
|
decks-order-due = Order due
|
||||||
decks-please-select-something = Please select something.
|
decks-please-select-something = Please select something.
|
||||||
decks-random = Random
|
decks-random = Random
|
||||||
decks-relative-overdueness = Relative overdueness
|
|
||||||
decks-repeat-failed-cards-after = Delay Repeat failed cards after
|
decks-repeat-failed-cards-after = Delay Repeat failed cards after
|
||||||
# e.g. "Delay for Again", "Delay for Hard", "Delay for Good"
|
# e.g. "Delay for Again", "Delay for Hard", "Delay for Good"
|
||||||
decks-delay-for-button = Delay for { $button }
|
decks-delay-for-button = Delay for { $button }
|
||||||
|
@ -37,3 +36,8 @@ decks-learn-header = Learn
|
||||||
# The count of cards waiting to be reviewed
|
# The count of cards waiting to be reviewed
|
||||||
decks-review-header = Due
|
decks-review-header = Due
|
||||||
decks-zero-minutes-hint = (0 = return card to original deck)
|
decks-zero-minutes-hint = (0 = return card to original deck)
|
||||||
|
|
||||||
|
## These strings are no longer used - you do not need to translate them if they
|
||||||
|
## are not already translated.
|
||||||
|
|
||||||
|
decks-relative-overdueness = Relative overdueness
|
||||||
|
|
|
@ -79,7 +79,8 @@ 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;
|
REVIEW_CARD_ORDER_RETRIEVABILITY_ASCENDING = 7;
|
||||||
|
REVIEW_CARD_ORDER_RETRIEVABILITY_DESCENDING = 11;
|
||||||
REVIEW_CARD_ORDER_RANDOM = 8;
|
REVIEW_CARD_ORDER_RANDOM = 8;
|
||||||
REVIEW_CARD_ORDER_ADDED = 9;
|
REVIEW_CARD_ORDER_ADDED = 9;
|
||||||
REVIEW_CARD_ORDER_REVERSE_ADDED = 10;
|
REVIEW_CARD_ORDER_REVERSE_ADDED = 10;
|
||||||
|
|
|
@ -97,7 +97,8 @@ message Deck {
|
||||||
ADDED = 5;
|
ADDED = 5;
|
||||||
DUE = 6;
|
DUE = 6;
|
||||||
REVERSE_ADDED = 7;
|
REVERSE_ADDED = 7;
|
||||||
DUE_PRIORITY = 8;
|
RETRIEVABILITY_ASCENDING = 8;
|
||||||
|
RETRIEVABILITY_DESCENDING = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
string search = 1;
|
string search = 1;
|
||||||
|
|
|
@ -60,7 +60,12 @@ fn search_order_label(order: FilteredSearchOrder, tr: &I18n) -> String {
|
||||||
FilteredSearchOrder::Added => tr.decks_order_added(),
|
FilteredSearchOrder::Added => tr.decks_order_added(),
|
||||||
FilteredSearchOrder::Due => tr.decks_order_due(),
|
FilteredSearchOrder::Due => tr.decks_order_due(),
|
||||||
FilteredSearchOrder::ReverseAdded => tr.decks_latest_added_first(),
|
FilteredSearchOrder::ReverseAdded => tr.decks_latest_added_first(),
|
||||||
FilteredSearchOrder::DuePriority => tr.decks_relative_overdueness(),
|
FilteredSearchOrder::RetrievabilityAscending => {
|
||||||
|
tr.deck_config_sort_order_retrievability_ascending()
|
||||||
|
}
|
||||||
|
FilteredSearchOrder::RetrievabilityDescending => {
|
||||||
|
tr.deck_config_sort_order_retrievability_descending()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
|
@ -464,7 +464,7 @@ mod test {
|
||||||
cards.push(card);
|
cards.push(card);
|
||||||
}
|
}
|
||||||
col.update_cards_maybe_undoable(cards, false)?;
|
col.update_cards_maybe_undoable(cards, false)?;
|
||||||
col.set_deck_review_order(&mut deck, ReviewCardOrder::RelativeOverdueness);
|
col.set_deck_review_order(&mut deck, ReviewCardOrder::RetrievabilityAscending);
|
||||||
assert_eq!(col.queue_as_due_and_ivl(deck.id), expected_queue);
|
assert_eq!(col.queue_as_due_and_ivl(deck.id), expected_queue);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -5,6 +5,7 @@ use crate::card::CardQueue;
|
||||||
use crate::decks::FilteredSearchOrder;
|
use crate::decks::FilteredSearchOrder;
|
||||||
use crate::decks::FilteredSearchTerm;
|
use crate::decks::FilteredSearchTerm;
|
||||||
use crate::scheduler::timing::SchedTimingToday;
|
use crate::scheduler::timing::SchedTimingToday;
|
||||||
|
use crate::storage::sqlite::SqlSortOrder;
|
||||||
|
|
||||||
pub(crate) fn order_and_limit_for_search(
|
pub(crate) fn order_and_limit_for_search(
|
||||||
term: &FilteredSearchTerm,
|
term: &FilteredSearchTerm,
|
||||||
|
@ -27,24 +28,40 @@ pub(crate) fn order_and_limit_for_search(
|
||||||
"(case when c.due > 1000000000 then due else (due - {today}) * 86400 + {current_timestamp} end), c.ord");
|
"(case when c.due > 1000000000 then due else (due - {today}) * 86400 + {current_timestamp} end), c.ord");
|
||||||
&temp_string
|
&temp_string
|
||||||
}
|
}
|
||||||
FilteredSearchOrder::DuePriority => {
|
FilteredSearchOrder::RetrievabilityAscending => {
|
||||||
let next_day_at = timing.next_day_at.0;
|
let next_day_at = timing.next_day_at.0;
|
||||||
temp_string = if fsrs {
|
temp_string =
|
||||||
format!(
|
build_retrievability_query(fsrs, today, next_day_at, SqlSortOrder::Ascending);
|
||||||
"extract_fsrs_relative_overdueness(c.data, due, {today}, ivl, {next_day_at}) desc"
|
&temp_string
|
||||||
)
|
}
|
||||||
} else {
|
FilteredSearchOrder::RetrievabilityDescending => {
|
||||||
format!(
|
let next_day_at = timing.next_day_at.0;
|
||||||
"
|
temp_string =
|
||||||
(case when queue={rev_queue} and due <= {today}
|
build_retrievability_query(fsrs, today, next_day_at, SqlSortOrder::Descending);
|
||||||
then (ivl / cast({today}-due+0.001 as real)) else 100000+due end)",
|
|
||||||
rev_queue = CardQueue::Review as i8,
|
|
||||||
today = today
|
|
||||||
)
|
|
||||||
};
|
|
||||||
&temp_string
|
&temp_string
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
format!("{}, fnvhash(c.id, c.mod) limit {}", order, term.limit)
|
format!("{}, fnvhash(c.id, c.mod) limit {}", order, term.limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_retrievability_query(
|
||||||
|
fsrs: bool,
|
||||||
|
today: u32,
|
||||||
|
next_day_at: i64,
|
||||||
|
order: SqlSortOrder,
|
||||||
|
) -> String {
|
||||||
|
if fsrs {
|
||||||
|
format!(
|
||||||
|
"extract_fsrs_relative_retrievability(c.data, due, {today}, ivl, {next_day_at}) {order}"
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
"
|
||||||
|
(case when queue={rev_queue} and due <= {today}
|
||||||
|
then (ivl / cast({today}-due+0.001 as real)) else 100000+due end) {order}",
|
||||||
|
rev_queue = CardQueue::Review as i8,
|
||||||
|
today = today
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ use rusqlite::Row;
|
||||||
|
|
||||||
use self::data::CardData;
|
use self::data::CardData;
|
||||||
use super::ids_to_string;
|
use super::ids_to_string;
|
||||||
|
use super::sqlite::SqlSortOrder;
|
||||||
use crate::card::Card;
|
use crate::card::Card;
|
||||||
use crate::card::CardId;
|
use crate::card::CardId;
|
||||||
use crate::card::CardQueue;
|
use crate::card::CardQueue;
|
||||||
|
@ -747,11 +748,13 @@ enum ReviewOrderSubclause {
|
||||||
DifficultyAscending,
|
DifficultyAscending,
|
||||||
/// FSRS
|
/// FSRS
|
||||||
DifficultyDescending,
|
DifficultyDescending,
|
||||||
RelativeOverdueness {
|
RetrievabilitySm2 {
|
||||||
today: u32,
|
today: u32,
|
||||||
|
order: SqlSortOrder,
|
||||||
},
|
},
|
||||||
RelativeOverduenessFsrs {
|
RetrievabilityFsrs {
|
||||||
timing: SchedTimingToday,
|
timing: SchedTimingToday,
|
||||||
|
order: SqlSortOrder,
|
||||||
},
|
},
|
||||||
Added,
|
Added,
|
||||||
ReverseAdded,
|
ReverseAdded,
|
||||||
|
@ -770,15 +773,18 @@ impl fmt::Display for ReviewOrderSubclause {
|
||||||
ReviewOrderSubclause::EaseDescending => "factor desc",
|
ReviewOrderSubclause::EaseDescending => "factor desc",
|
||||||
ReviewOrderSubclause::DifficultyAscending => "extract_fsrs_variable(data, 'd') asc",
|
ReviewOrderSubclause::DifficultyAscending => "extract_fsrs_variable(data, 'd') asc",
|
||||||
ReviewOrderSubclause::DifficultyDescending => "extract_fsrs_variable(data, 'd') desc",
|
ReviewOrderSubclause::DifficultyDescending => "extract_fsrs_variable(data, 'd') desc",
|
||||||
ReviewOrderSubclause::RelativeOverdueness { today } => {
|
ReviewOrderSubclause::RetrievabilitySm2 { today, order } => {
|
||||||
temp_string = format!("ivl / cast({today}-due+0.001 as real)", today = today);
|
temp_string = format!(
|
||||||
|
"ivl / cast({today}-due+0.001 as real) {order}",
|
||||||
|
today = today
|
||||||
|
);
|
||||||
&temp_string
|
&temp_string
|
||||||
}
|
}
|
||||||
ReviewOrderSubclause::RelativeOverduenessFsrs { timing } => {
|
ReviewOrderSubclause::RetrievabilityFsrs { timing, order } => {
|
||||||
let today = timing.days_elapsed;
|
let today = timing.days_elapsed;
|
||||||
let next_day_at = timing.next_day_at.0;
|
let next_day_at = timing.next_day_at.0;
|
||||||
temp_string =
|
temp_string =
|
||||||
format!("extract_fsrs_relative_overdueness(data, due, {today}, ivl, {next_day_at}) desc");
|
format!("extract_fsrs_relative_retrievability(data, due, {today}, ivl, {next_day_at}) {order}");
|
||||||
&temp_string
|
&temp_string
|
||||||
}
|
}
|
||||||
ReviewOrderSubclause::Added => "nid asc, ord asc",
|
ReviewOrderSubclause::Added => "nid asc, ord asc",
|
||||||
|
@ -807,14 +813,11 @@ fn review_order_sql(order: ReviewCardOrder, timing: SchedTimingToday, fsrs: bool
|
||||||
} else {
|
} else {
|
||||||
ReviewOrderSubclause::EaseDescending
|
ReviewOrderSubclause::EaseDescending
|
||||||
}],
|
}],
|
||||||
ReviewCardOrder::RelativeOverdueness => {
|
ReviewCardOrder::RetrievabilityAscending => {
|
||||||
vec![if fsrs {
|
build_retrievability_clauses(fsrs, timing, SqlSortOrder::Ascending)
|
||||||
ReviewOrderSubclause::RelativeOverduenessFsrs { timing }
|
}
|
||||||
} else {
|
ReviewCardOrder::RetrievabilityDescending => {
|
||||||
ReviewOrderSubclause::RelativeOverdueness {
|
build_retrievability_clauses(fsrs, timing, SqlSortOrder::Descending)
|
||||||
today: timing.days_elapsed,
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
}
|
}
|
||||||
ReviewCardOrder::Random => vec![],
|
ReviewCardOrder::Random => vec![],
|
||||||
ReviewCardOrder::Added => vec![ReviewOrderSubclause::Added],
|
ReviewCardOrder::Added => vec![ReviewOrderSubclause::Added],
|
||||||
|
@ -829,6 +832,21 @@ fn review_order_sql(order: ReviewCardOrder, timing: SchedTimingToday, fsrs: bool
|
||||||
v.join(", ")
|
v.join(", ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_retrievability_clauses(
|
||||||
|
fsrs: bool,
|
||||||
|
timing: SchedTimingToday,
|
||||||
|
order: SqlSortOrder,
|
||||||
|
) -> Vec<ReviewOrderSubclause> {
|
||||||
|
vec![if fsrs {
|
||||||
|
ReviewOrderSubclause::RetrievabilityFsrs { timing, order }
|
||||||
|
} else {
|
||||||
|
ReviewOrderSubclause::RetrievabilitySm2 {
|
||||||
|
today: timing.days_elapsed,
|
||||||
|
order,
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub(crate) enum NewCardSorting {
|
pub(crate) enum NewCardSorting {
|
||||||
/// Ascending position, consecutive siblings,
|
/// Ascending position, consecutive siblings,
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
use std::fmt::Display;
|
||||||
use std::hash::Hasher;
|
use std::hash::Hasher;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -74,7 +75,7 @@ fn open_or_create_collection_db(path: &Path) -> Result<Connection> {
|
||||||
add_extract_custom_data_function(&db)?;
|
add_extract_custom_data_function(&db)?;
|
||||||
add_extract_fsrs_variable(&db)?;
|
add_extract_fsrs_variable(&db)?;
|
||||||
add_extract_fsrs_retrievability(&db)?;
|
add_extract_fsrs_retrievability(&db)?;
|
||||||
add_extract_fsrs_relative_overdueness(&db)?;
|
add_extract_fsrs_relative_retrievability(&db)?;
|
||||||
|
|
||||||
db.create_collation("unicase", unicase_compare)?;
|
db.create_collation("unicase", unicase_compare)?;
|
||||||
|
|
||||||
|
@ -333,12 +334,13 @@ fn add_extract_fsrs_retrievability(db: &Connection) -> rusqlite::Result<()> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// eg. extract_fsrs_relative_overdueness(card.data, card.due,
|
/// eg. extract_fsrs_relative_retrievability(card.data, card.due,
|
||||||
/// timing.days_elapsed, card.ivl, timing.next_day_at) -> float | null. The
|
/// timing.days_elapsed, card.ivl, timing.next_day_at) -> float | null. The
|
||||||
/// higher the number, the more overdue.
|
/// higher the number, the higher the card's retrievability relative to the
|
||||||
fn add_extract_fsrs_relative_overdueness(db: &Connection) -> rusqlite::Result<()> {
|
/// configured desired retention.
|
||||||
|
fn add_extract_fsrs_relative_retrievability(db: &Connection) -> rusqlite::Result<()> {
|
||||||
db.create_scalar_function(
|
db.create_scalar_function(
|
||||||
"extract_fsrs_relative_overdueness",
|
"extract_fsrs_relative_retrievability",
|
||||||
5,
|
5,
|
||||||
FunctionFlags::SQLITE_DETERMINISTIC,
|
FunctionFlags::SQLITE_DETERMINISTIC,
|
||||||
move |ctx| {
|
move |ctx| {
|
||||||
|
@ -386,7 +388,7 @@ fn add_extract_fsrs_relative_overdueness(db: &Connection) -> rusqlite::Result<()
|
||||||
.max(0.0001);
|
.max(0.0001);
|
||||||
|
|
||||||
Ok(Some(
|
Ok(Some(
|
||||||
(1. / current_retrievability - 1.) / (1. / desired_retrievability - 1.),
|
-(1. / current_retrievability - 1.) / (1. / desired_retrievability - 1.),
|
||||||
))
|
))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -590,3 +592,22 @@ impl SqliteStorage {
|
||||||
self.db.query_row(sql, [], |r| r.get(0)).map_err(Into::into)
|
self.db.query_row(sql, [], |r| r.get(0)).map_err(Into::into)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum SqlSortOrder {
|
||||||
|
Ascending,
|
||||||
|
Descending,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for SqlSortOrder {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
match self {
|
||||||
|
SqlSortOrder::Ascending => "asc",
|
||||||
|
SqlSortOrder::Descending => "desc",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -96,8 +96,12 @@ export function reviewOrderChoices(fsrs: boolean): Choice<DeckConfig_Config_Revi
|
||||||
...difficultyOrders(fsrs),
|
...difficultyOrders(fsrs),
|
||||||
...[
|
...[
|
||||||
{
|
{
|
||||||
label: tr.deckConfigSortOrderRelativeOverdueness(),
|
label: tr.deckConfigSortOrderRetrievabilityAscending(),
|
||||||
value: DeckConfig_Config_ReviewCardOrder.RELATIVE_OVERDUENESS,
|
value: DeckConfig_Config_ReviewCardOrder.RETRIEVABILITY_ASCENDING,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: tr.deckConfigSortOrderRetrievabilityDescending(),
|
||||||
|
value: DeckConfig_Config_ReviewCardOrder.RETRIEVABILITY_DESCENDING,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: tr.deckConfigSortOrderRandom(),
|
label: tr.deckConfigSortOrderRandom(),
|
||||||
|
|
Loading…
Reference in a new issue