mirror of
https://github.com/ankitects/anki.git
synced 2025-11-06 12:47:11 -05:00
Merge fd4e8457d8 into dac26ce671
This commit is contained in:
commit
2b7542fc9c
6 changed files with 79 additions and 5 deletions
|
|
@ -9,7 +9,9 @@ mod sorting;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
use std::hash::Hasher;
|
||||||
|
|
||||||
|
use fnv::FnvHasher;
|
||||||
use intersperser::Intersperser;
|
use intersperser::Intersperser;
|
||||||
use sized_chain::SizedChain;
|
use sized_chain::SizedChain;
|
||||||
|
|
||||||
|
|
@ -38,6 +40,7 @@ pub(crate) struct DueCard {
|
||||||
pub current_deck_id: DeckId,
|
pub current_deck_id: DeckId,
|
||||||
pub original_deck_id: DeckId,
|
pub original_deck_id: DeckId,
|
||||||
pub kind: DueCardKind,
|
pub kind: DueCardKind,
|
||||||
|
pub reps: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
|
@ -87,6 +90,7 @@ impl From<DueCard> for LearningQueueEntry {
|
||||||
due: TimestampSecs(c.due as i64),
|
due: TimestampSecs(c.due as i64),
|
||||||
id: c.id,
|
id: c.id,
|
||||||
mtime: c.mtime,
|
mtime: c.mtime,
|
||||||
|
reps: c.reps,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -274,9 +278,27 @@ fn merge_new(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sort_learning(mut learning: Vec<DueCard>) -> VecDeque<LearningQueueEntry> {
|
fn sort_learning(learning: Vec<DueCard>) -> VecDeque<LearningQueueEntry> {
|
||||||
learning.sort_unstable_by(|a, b| a.due.cmp(&b.due));
|
// Prioritize intraday learning cards that were previously attempted
|
||||||
learning.into_iter().map(LearningQueueEntry::from).collect()
|
// (reps > 0) before never-attempted cards (reps == 0). Sort previously
|
||||||
|
// attempted cards by due-time and never-attempted cards deterministically
|
||||||
|
// by an FNV hash of their id.
|
||||||
|
let (mut previously_attempted, mut never_attempted): (Vec<DueCard>, Vec<DueCard>) =
|
||||||
|
learning.into_iter().partition(|c| c.reps > 0);
|
||||||
|
|
||||||
|
previously_attempted.sort_unstable_by(|a, b| a.due.cmp(&b.due));
|
||||||
|
|
||||||
|
never_attempted.sort_unstable_by_key(|c| {
|
||||||
|
let mut hasher = FnvHasher::default();
|
||||||
|
hasher.write_i64(c.id.0);
|
||||||
|
hasher.finish()
|
||||||
|
});
|
||||||
|
|
||||||
|
previously_attempted
|
||||||
|
.into_iter()
|
||||||
|
.chain(never_attempted)
|
||||||
|
.map(LearningQueueEntry::from)
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Collection {
|
impl Collection {
|
||||||
|
|
@ -543,4 +565,49 @@ mod test {
|
||||||
col.set_current_deck(child.id).unwrap();
|
col.set_current_deck(child.id).unwrap();
|
||||||
assert_eq!(col.card_queue_len(), 0);
|
assert_eq!(col.card_queue_len(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn intraday_learning_prioritizes_previously_attempted() -> Result<()> {
|
||||||
|
let mut col = Collection::new();
|
||||||
|
|
||||||
|
// create two notes with one card each
|
||||||
|
let nt = col.get_notetype_by_name("Basic")?.unwrap();
|
||||||
|
let mut n1 = nt.new_note();
|
||||||
|
n1.set_field(0, "one")?;
|
||||||
|
col.add_note(&mut n1, DeckId(1))?;
|
||||||
|
let mut n2 = nt.new_note();
|
||||||
|
n2.set_field(0, "two")?;
|
||||||
|
col.add_note(&mut n2, DeckId(1))?;
|
||||||
|
|
||||||
|
// fetch their cards and make them intraday learning cards
|
||||||
|
let mut c1 = col.storage.get_card_by_ordinal(n1.id, 0)?.unwrap();
|
||||||
|
let mut c2 = col.storage.get_card_by_ordinal(n2.id, 0)?.unwrap();
|
||||||
|
|
||||||
|
c1.queue = CardQueue::Learn;
|
||||||
|
c2.queue = CardQueue::Learn;
|
||||||
|
|
||||||
|
// c1: never attempted, due earlier (0)
|
||||||
|
c1.due = 0;
|
||||||
|
c1.reps = 0;
|
||||||
|
// c2: previously attempted, due later (600)
|
||||||
|
c2.due = 600;
|
||||||
|
c2.reps = 1;
|
||||||
|
|
||||||
|
let id1 = c1.id;
|
||||||
|
let id2 = c2.id;
|
||||||
|
|
||||||
|
col.update_cards_maybe_undoable(vec![c1, c2], false)?;
|
||||||
|
|
||||||
|
// build queues and inspect intraday learning order
|
||||||
|
let queues = col.build_queues(DeckId(1))?;
|
||||||
|
let ids: Vec<_> = queues.intraday_learning.iter().map(|e| e.id).collect();
|
||||||
|
|
||||||
|
// ensure the previously attempted card appears before the never-attempted one
|
||||||
|
assert!(
|
||||||
|
ids.iter().position(|&id| id == id2).unwrap()
|
||||||
|
< ids.iter().position(|&id| id == id1).unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ impl From<&Card> for QueueEntry {
|
||||||
due: TimestampSecs(card.due as i64),
|
due: TimestampSecs(card.due as i64),
|
||||||
id: card.id,
|
id: card.id,
|
||||||
mtime: card.mtime,
|
mtime: card.mtime,
|
||||||
|
reps: card.reps,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
CardQueue::New => MainQueueEntryKind::New,
|
CardQueue::New => MainQueueEntryKind::New,
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ pub(crate) struct LearningQueueEntry {
|
||||||
pub due: TimestampSecs,
|
pub due: TimestampSecs,
|
||||||
pub id: CardId,
|
pub id: CardId,
|
||||||
pub mtime: TimestampSecs,
|
pub mtime: TimestampSecs,
|
||||||
|
pub reps: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CardQueues {
|
impl CardQueues {
|
||||||
|
|
@ -71,6 +72,7 @@ impl CardQueues {
|
||||||
due: TimestampSecs(card.due as i64),
|
due: TimestampSecs(card.due as i64),
|
||||||
id: card.id,
|
id: card.id,
|
||||||
mtime: card.mtime,
|
mtime: card.mtime,
|
||||||
|
reps: card.reps,
|
||||||
};
|
};
|
||||||
|
|
||||||
Some(self.requeue_learning_entry(entry))
|
Some(self.requeue_learning_entry(entry))
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ SELECT id,
|
||||||
cast(ivl AS integer),
|
cast(ivl AS integer),
|
||||||
cast(mod AS integer),
|
cast(mod AS integer),
|
||||||
did,
|
did,
|
||||||
odid
|
odid,
|
||||||
|
reps
|
||||||
FROM cards
|
FROM cards
|
||||||
WHERE did IN (
|
WHERE did IN (
|
||||||
SELECT id
|
SELECT id
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,8 @@ SELECT id,
|
||||||
due,
|
due,
|
||||||
cast(mod AS integer),
|
cast(mod AS integer),
|
||||||
did,
|
did,
|
||||||
odid
|
odid,
|
||||||
|
reps
|
||||||
FROM cards
|
FROM cards
|
||||||
WHERE did IN (
|
WHERE did IN (
|
||||||
SELECT id
|
SELECT id
|
||||||
|
|
|
||||||
|
|
@ -267,6 +267,7 @@ impl super::SqliteStorage {
|
||||||
mtime: row.get(3)?,
|
mtime: row.get(3)?,
|
||||||
current_deck_id: row.get(4)?,
|
current_deck_id: row.get(4)?,
|
||||||
original_deck_id: row.get(5)?,
|
original_deck_id: row.get(5)?,
|
||||||
|
reps: row.get(6)?,
|
||||||
kind: DueCardKind::Learning,
|
kind: DueCardKind::Learning,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -306,6 +307,7 @@ impl super::SqliteStorage {
|
||||||
mtime: row.get(4)?,
|
mtime: row.get(4)?,
|
||||||
current_deck_id: row.get(5)?,
|
current_deck_id: row.get(5)?,
|
||||||
original_deck_id: row.get(6)?,
|
original_deck_id: row.get(6)?,
|
||||||
|
reps: row.get(7)?,
|
||||||
kind,
|
kind,
|
||||||
})? {
|
})? {
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue