mirror of
https://github.com/ankitects/anki.git
synced 2026-01-11 04: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::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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -188,12 +192,8 @@ impl QueueBuilder {
|
||||||
let intraday_learning = sort_learning(self.learning);
|
let intraday_learning = sort_learning(self.learning);
|
||||||
let now = TimestampSecs::now();
|
let now = TimestampSecs::now();
|
||||||
let cutoff = now.adding_secs(learn_ahead_secs);
|
let cutoff = now.adding_secs(learn_ahead_secs);
|
||||||
let learn_count = intraday_learning
|
let learn_count =
|
||||||
.iter()
|
intraday_learning.iter().filter(|e| e.due <= cutoff).count() + self.day_learning.len();
|
||||||
.take_while(|e| e.due <= cutoff)
|
|
||||||
.count()
|
|
||||||
+ self.day_learning.len();
|
|
||||||
|
|
||||||
let review_count = self.review.len();
|
let review_count = self.review.len();
|
||||||
let new_count = self.new.len();
|
let new_count = self.new.len();
|
||||||
|
|
||||||
|
|
@ -274,9 +274,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 {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
@ -20,7 +21,7 @@ impl CardQueues {
|
||||||
let cutoff = self.current_learning_cutoff;
|
let cutoff = self.current_learning_cutoff;
|
||||||
self.intraday_learning
|
self.intraday_learning
|
||||||
.iter()
|
.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.
|
/// 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();
|
let ahead_cutoff = self.current_learn_ahead_cutoff();
|
||||||
self.intraday_learning
|
self.intraday_learning
|
||||||
.iter()
|
.iter()
|
||||||
.skip_while(move |e| e.due <= cutoff)
|
.filter(move |e| e.due > cutoff && e.due <= ahead_cutoff)
|
||||||
.take_while(move |e| e.due <= ahead_cutoff)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Increase the cutoff to the current time, and increase the learning count
|
/// Increase the cutoff to the current time, and increase the learning count
|
||||||
|
|
@ -46,8 +46,7 @@ impl CardQueues {
|
||||||
let new_learning_cards = self
|
let new_learning_cards = self
|
||||||
.intraday_learning
|
.intraday_learning
|
||||||
.iter()
|
.iter()
|
||||||
.skip_while(|e| e.due <= last_ahead_cutoff)
|
.filter(|e| e.due > last_ahead_cutoff && e.due <= new_ahead_cutoff)
|
||||||
.take_while(|e| e.due <= new_ahead_cutoff)
|
|
||||||
.count();
|
.count();
|
||||||
self.counts.learning += new_learning_cards;
|
self.counts.learning += new_learning_cards;
|
||||||
|
|
||||||
|
|
@ -71,6 +70,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))
|
||||||
|
|
@ -116,16 +116,6 @@ impl CardQueues {
|
||||||
self.main.is_empty()
|
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.
|
/// Add an undone entry to the top of the intraday learning queue.
|
||||||
pub(super) fn push_intraday_learning(&mut self, entry: LearningQueueEntry) {
|
pub(super) fn push_intraday_learning(&mut self, entry: LearningQueueEntry) {
|
||||||
self.intraday_learning.push_front(entry);
|
self.intraday_learning.push_front(entry);
|
||||||
|
|
|
||||||
|
|
@ -159,17 +159,13 @@ impl CardQueues {
|
||||||
/// Remove the provided card from the top of the queues and
|
/// Remove the provided card from the top of the queues and
|
||||||
/// adjust the counts. If it was not at the top, return an error.
|
/// adjust the counts. If it was not at the top, return an error.
|
||||||
fn pop_entry(&mut self, id: CardId) -> Result<QueueEntry> {
|
fn pop_entry(&mut self, id: CardId) -> Result<QueueEntry> {
|
||||||
// This ignores the current cutoff, so may match if the provided
|
if let Some(pos) = self.intraday_learning.iter().position(|e| e.id == id) {
|
||||||
// learning card is not yet due. It should not happen in normal
|
let entry = self.intraday_learning.remove(pos).unwrap();
|
||||||
// practice, but does happen in the Python unit tests, as they answer
|
// FIXME:
|
||||||
// learning cards early.
|
// under normal circumstances this should not go below 0, but currently
|
||||||
if self
|
// the Python unit tests answer learning cards before they're due
|
||||||
.intraday_learning
|
self.counts.learning = self.counts.learning.saturating_sub(1);
|
||||||
.front()
|
Ok(entry.into())
|
||||||
.filter(|e| e.id == id)
|
|
||||||
.is_some()
|
|
||||||
{
|
|
||||||
Ok(self.pop_intraday_learning().unwrap().into())
|
|
||||||
} else if self.main.front().filter(|e| e.id == id).is_some() {
|
} else if self.main.front().filter(|e| e.id == id).is_some() {
|
||||||
Ok(self.pop_main().unwrap().into())
|
Ok(self.pop_main().unwrap().into())
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -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