mirror of
https://github.com/ankitects/anki.git
synced 2026-01-09 03:53:55 -05:00
Merge 833c1c4e7c into 8f2144534b
This commit is contained in:
commit
a890d4fd81
7 changed files with 46 additions and 37 deletions
|
|
@ -9,7 +9,9 @@ mod sorting;
|
|||
|
||||
use std::collections::HashMap;
|
||||
use std::collections::VecDeque;
|
||||
use std::hash::Hasher;
|
||||
|
||||
use fnv::FnvHasher;
|
||||
use intersperser::Intersperser;
|
||||
use sized_chain::SizedChain;
|
||||
|
||||
|
|
@ -38,6 +40,7 @@ pub(crate) struct DueCard {
|
|||
pub current_deck_id: DeckId,
|
||||
pub original_deck_id: DeckId,
|
||||
pub kind: DueCardKind,
|
||||
pub reps: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
|
|
@ -87,6 +90,7 @@ impl From<DueCard> for LearningQueueEntry {
|
|||
due: TimestampSecs(c.due as i64),
|
||||
id: c.id,
|
||||
mtime: c.mtime,
|
||||
reps: c.reps,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -188,12 +192,8 @@ impl QueueBuilder {
|
|||
let intraday_learning = sort_learning(self.learning);
|
||||
let now = TimestampSecs::now();
|
||||
let cutoff = now.adding_secs(learn_ahead_secs);
|
||||
let learn_count = intraday_learning
|
||||
.iter()
|
||||
.take_while(|e| e.due <= cutoff)
|
||||
.count()
|
||||
+ self.day_learning.len();
|
||||
|
||||
let learn_count =
|
||||
intraday_learning.iter().filter(|e| e.due <= cutoff).count() + self.day_learning.len();
|
||||
let review_count = self.review.len();
|
||||
let new_count = self.new.len();
|
||||
|
||||
|
|
@ -274,9 +274,27 @@ fn merge_new(
|
|||
}
|
||||
}
|
||||
|
||||
fn sort_learning(mut learning: Vec<DueCard>) -> VecDeque<LearningQueueEntry> {
|
||||
learning.sort_unstable_by(|a, b| a.due.cmp(&b.due));
|
||||
learning.into_iter().map(LearningQueueEntry::from).collect()
|
||||
fn sort_learning(learning: Vec<DueCard>) -> VecDeque<LearningQueueEntry> {
|
||||
// Prioritize intraday learning cards that were previously attempted
|
||||
// (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 {
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ impl From<&Card> for QueueEntry {
|
|||
due: TimestampSecs(card.due as i64),
|
||||
id: card.id,
|
||||
mtime: card.mtime,
|
||||
reps: card.reps,
|
||||
});
|
||||
}
|
||||
CardQueue::New => MainQueueEntryKind::New,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ pub(crate) struct LearningQueueEntry {
|
|||
pub due: TimestampSecs,
|
||||
pub id: CardId,
|
||||
pub mtime: TimestampSecs,
|
||||
pub reps: u32,
|
||||
}
|
||||
|
||||
impl CardQueues {
|
||||
|
|
@ -20,7 +21,7 @@ impl CardQueues {
|
|||
let cutoff = self.current_learning_cutoff;
|
||||
self.intraday_learning
|
||||
.iter()
|
||||
.take_while(move |e| e.due <= cutoff)
|
||||
.filter(move |e| e.due <= cutoff)
|
||||
}
|
||||
|
||||
/// Intraday learning cards that can be shown after the main queue is empty.
|
||||
|
|
@ -29,8 +30,7 @@ impl CardQueues {
|
|||
let ahead_cutoff = self.current_learn_ahead_cutoff();
|
||||
self.intraday_learning
|
||||
.iter()
|
||||
.skip_while(move |e| e.due <= cutoff)
|
||||
.take_while(move |e| e.due <= ahead_cutoff)
|
||||
.filter(move |e| e.due > cutoff && e.due <= ahead_cutoff)
|
||||
}
|
||||
|
||||
/// Increase the cutoff to the current time, and increase the learning count
|
||||
|
|
@ -46,8 +46,7 @@ impl CardQueues {
|
|||
let new_learning_cards = self
|
||||
.intraday_learning
|
||||
.iter()
|
||||
.skip_while(|e| e.due <= last_ahead_cutoff)
|
||||
.take_while(|e| e.due <= new_ahead_cutoff)
|
||||
.filter(|e| e.due > last_ahead_cutoff && e.due <= new_ahead_cutoff)
|
||||
.count();
|
||||
self.counts.learning += new_learning_cards;
|
||||
|
||||
|
|
@ -71,6 +70,7 @@ impl CardQueues {
|
|||
due: TimestampSecs(card.due as i64),
|
||||
id: card.id,
|
||||
mtime: card.mtime,
|
||||
reps: card.reps,
|
||||
};
|
||||
|
||||
Some(self.requeue_learning_entry(entry))
|
||||
|
|
@ -116,16 +116,6 @@ impl CardQueues {
|
|||
self.main.is_empty()
|
||||
}
|
||||
|
||||
/// Remove the head of the intraday learning queue, and update counts.
|
||||
pub(super) fn pop_intraday_learning(&mut self) -> Option<LearningQueueEntry> {
|
||||
self.intraday_learning.pop_front().inspect(|_head| {
|
||||
// FIXME:
|
||||
// under normal circumstances this should not go below 0, but currently
|
||||
// the Python unit tests answer learning cards before they're due
|
||||
self.counts.learning = self.counts.learning.saturating_sub(1);
|
||||
})
|
||||
}
|
||||
|
||||
/// Add an undone entry to the top of the intraday learning queue.
|
||||
pub(super) fn push_intraday_learning(&mut self, entry: LearningQueueEntry) {
|
||||
self.intraday_learning.push_front(entry);
|
||||
|
|
|
|||
|
|
@ -159,17 +159,13 @@ impl CardQueues {
|
|||
/// Remove the provided card from the top of the queues and
|
||||
/// adjust the counts. If it was not at the top, return an error.
|
||||
fn pop_entry(&mut self, id: CardId) -> Result<QueueEntry> {
|
||||
// This ignores the current cutoff, so may match if the provided
|
||||
// learning card is not yet due. It should not happen in normal
|
||||
// practice, but does happen in the Python unit tests, as they answer
|
||||
// learning cards early.
|
||||
if self
|
||||
.intraday_learning
|
||||
.front()
|
||||
.filter(|e| e.id == id)
|
||||
.is_some()
|
||||
{
|
||||
Ok(self.pop_intraday_learning().unwrap().into())
|
||||
if let Some(pos) = self.intraday_learning.iter().position(|e| e.id == id) {
|
||||
let entry = self.intraday_learning.remove(pos).unwrap();
|
||||
// FIXME:
|
||||
// under normal circumstances this should not go below 0, but currently
|
||||
// the Python unit tests answer learning cards before they're due
|
||||
self.counts.learning = self.counts.learning.saturating_sub(1);
|
||||
Ok(entry.into())
|
||||
} else if self.main.front().filter(|e| e.id == id).is_some() {
|
||||
Ok(self.pop_main().unwrap().into())
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ SELECT id,
|
|||
cast(ivl AS integer),
|
||||
cast(mod AS integer),
|
||||
did,
|
||||
odid
|
||||
odid,
|
||||
reps
|
||||
FROM cards
|
||||
WHERE did IN (
|
||||
SELECT id
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ SELECT id,
|
|||
due,
|
||||
cast(mod AS integer),
|
||||
did,
|
||||
odid
|
||||
odid,
|
||||
reps
|
||||
FROM cards
|
||||
WHERE did IN (
|
||||
SELECT id
|
||||
|
|
|
|||
|
|
@ -267,6 +267,7 @@ impl super::SqliteStorage {
|
|||
mtime: row.get(3)?,
|
||||
current_deck_id: row.get(4)?,
|
||||
original_deck_id: row.get(5)?,
|
||||
reps: row.get(6)?,
|
||||
kind: DueCardKind::Learning,
|
||||
})
|
||||
}
|
||||
|
|
@ -306,6 +307,7 @@ impl super::SqliteStorage {
|
|||
mtime: row.get(4)?,
|
||||
current_deck_id: row.get(5)?,
|
||||
original_deck_id: row.get(6)?,
|
||||
reps: row.get(7)?,
|
||||
kind,
|
||||
})? {
|
||||
break;
|
||||
|
|
|
|||
Loading…
Reference in a new issue