mirror of
https://github.com/ankitects/anki.git
synced 2026-01-11 04:53:55 -05:00
Merge eb17bd6d70 into 8f2144534b
This commit is contained in:
commit
be7c6c4103
8 changed files with 141 additions and 1 deletions
|
|
@ -256,6 +256,7 @@ Eltaurus <https://github.com/Eltaurus-Lt>
|
||||||
jariji
|
jariji
|
||||||
Francisco Esteva <fr.esteva@duocuc.cl>
|
Francisco Esteva <fr.esteva@duocuc.cl>
|
||||||
SelfishPig <https://github.com/SelfishPig>
|
SelfishPig <https://github.com/SelfishPig>
|
||||||
|
David Grundberg <75159519+individ-divided@users.noreply.github.com>
|
||||||
|
|
||||||
********************
|
********************
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -142,6 +142,13 @@ deck-config-new-gather-priority-tooltip-2 =
|
||||||
can stop before all subdecks have been checked. This order is fastest in large collections, and
|
can stop before all subdecks have been checked. This order is fastest in large collections, and
|
||||||
allows you to prioritize subdecks that are closer to the top.
|
allows you to prioritize subdecks that are closer to the top.
|
||||||
|
|
||||||
|
`Interleaved Decks`: Gathers cards by taking one card at a time
|
||||||
|
from each deck in order. This allows new cards to be distributed
|
||||||
|
evenly across decks. Cards from each subdeck are gathered in
|
||||||
|
ascending position. If the number of decks are not equal to the
|
||||||
|
daily limit of the selected deck, the last round of cards will be
|
||||||
|
drawn from a random sample of decks.
|
||||||
|
|
||||||
`Ascending position`: Gathers cards by ascending position (due #), which is typically
|
`Ascending position`: Gathers cards by ascending position (due #), which is typically
|
||||||
the oldest-added first.
|
the oldest-added first.
|
||||||
|
|
||||||
|
|
@ -197,6 +204,8 @@ deck-config-display-order-will-use-current-deck =
|
||||||
deck-config-new-gather-priority-deck = Deck
|
deck-config-new-gather-priority-deck = Deck
|
||||||
# Gather new cards ordered by deck, then ordered by random notes, ensuring all cards of the same note are grouped together.
|
# Gather new cards ordered by deck, then ordered by random notes, ensuring all cards of the same note are grouped together.
|
||||||
deck-config-new-gather-priority-deck-then-random-notes = Deck, then random notes
|
deck-config-new-gather-priority-deck-then-random-notes = Deck, then random notes
|
||||||
|
# Gather new cards by interleaving decks, taking one card at a time from each deck in order.
|
||||||
|
deck-config-new-gather-priority-interleaved-decks = Interleaved Decks
|
||||||
# Gather new cards ordered by position number, ascending (lowest to highest).
|
# Gather new cards ordered by position number, ascending (lowest to highest).
|
||||||
deck-config-new-gather-priority-position-lowest-first = Ascending position
|
deck-config-new-gather-priority-position-lowest-first = Ascending position
|
||||||
# Gather new cards ordered by position number, descending (highest to lowest).
|
# Gather new cards ordered by position number, descending (highest to lowest).
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,8 @@ message DeckConfig {
|
||||||
// Notes are randomly picked from each deck in alphabetical order.
|
// Notes are randomly picked from each deck in alphabetical order.
|
||||||
// Siblings are consecutive, provided they have the same position.
|
// Siblings are consecutive, provided they have the same position.
|
||||||
NEW_CARD_GATHER_PRIORITY_DECK_THEN_RANDOM_NOTES = 5;
|
NEW_CARD_GATHER_PRIORITY_DECK_THEN_RANDOM_NOTES = 5;
|
||||||
|
// One card from each deck at a time, ascending position in each deck.
|
||||||
|
NEW_CARD_GATHER_PRIORITY_INTERLEAVED_DECKS = 6;
|
||||||
// Ascending position.
|
// Ascending position.
|
||||||
// Siblings are consecutive, provided they have the same position.
|
// Siblings are consecutive, provided they have the same position.
|
||||||
NEW_CARD_GATHER_PRIORITY_LOWEST_POSITION = 1;
|
NEW_CARD_GATHER_PRIORITY_LOWEST_POSITION = 1;
|
||||||
|
|
|
||||||
|
|
@ -351,7 +351,7 @@ impl LimitTreeMap {
|
||||||
.map(|node_id| self.get_node_limits(node_id))
|
.map(|node_id| self.get_node_limits(node_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_root_limits(&self) -> RemainingLimits {
|
pub(crate) fn get_root_limits(&self) -> RemainingLimits {
|
||||||
self.get_node_limits(self.tree.root_node_id().unwrap())
|
self.get_node_limits(self.tree.root_node_id().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
// Copyright: Ankitects Pty Ltd and contributors
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use rand::rng;
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
use super::DueCard;
|
use super::DueCard;
|
||||||
use super::NewCard;
|
use super::NewCard;
|
||||||
use super::QueueBuilder;
|
use super::QueueBuilder;
|
||||||
|
|
@ -69,6 +74,9 @@ impl QueueBuilder {
|
||||||
NewCardGatherPriority::DeckThenRandomNotes => {
|
NewCardGatherPriority::DeckThenRandomNotes => {
|
||||||
self.gather_new_cards_by_deck(col, NewCardSorting::RandomNotes(salt))
|
self.gather_new_cards_by_deck(col, NewCardSorting::RandomNotes(salt))
|
||||||
}
|
}
|
||||||
|
NewCardGatherPriority::InterleavedDecks => {
|
||||||
|
self.gather_new_cards_by_interleaved_decks(col, NewCardSorting::LowestPosition)
|
||||||
|
}
|
||||||
NewCardGatherPriority::LowestPosition => {
|
NewCardGatherPriority::LowestPosition => {
|
||||||
self.gather_new_cards_sorted(col, NewCardSorting::LowestPosition)
|
self.gather_new_cards_sorted(col, NewCardSorting::LowestPosition)
|
||||||
}
|
}
|
||||||
|
|
@ -110,6 +118,91 @@ impl QueueBuilder {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn gather_new_cards_by_interleaved_decks(
|
||||||
|
&mut self,
|
||||||
|
col: &mut Collection,
|
||||||
|
sort: NewCardSorting,
|
||||||
|
) -> Result<()> {
|
||||||
|
struct InterleavedDeckData {
|
||||||
|
deck_id: DeckId,
|
||||||
|
depleted: bool,
|
||||||
|
cards: std::iter::Peekable<std::vec::IntoIter<NewCard>>,
|
||||||
|
}
|
||||||
|
let mut decks: Vec<InterleavedDeckData> = vec![];
|
||||||
|
for deck_id in col.storage.get_active_deck_ids_sorted()? {
|
||||||
|
let x: std::iter::Peekable<std::vec::IntoIter<NewCard>> = col
|
||||||
|
.storage
|
||||||
|
.new_cards_in_deck(deck_id, sort)?
|
||||||
|
.into_iter()
|
||||||
|
.peekable();
|
||||||
|
decks.push(InterleavedDeckData {
|
||||||
|
deck_id,
|
||||||
|
depleted: false,
|
||||||
|
cards: x,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let mut rng = rng();
|
||||||
|
let mut do_continue = true;
|
||||||
|
while do_continue {
|
||||||
|
do_continue = false;
|
||||||
|
let mut non_depleted_decks = 0;
|
||||||
|
for deck_data in &mut decks {
|
||||||
|
if self
|
||||||
|
.limits
|
||||||
|
.limit_reached(deck_data.deck_id, LimitKind::New)?
|
||||||
|
{
|
||||||
|
deck_data.depleted = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if deck_data.cards.peek().is_none() {
|
||||||
|
deck_data.depleted = true;
|
||||||
|
} else {
|
||||||
|
non_depleted_decks += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let root_limit = self.limits.get_root_limits().get(LimitKind::New);
|
||||||
|
let mut sampled_deck_ids = HashSet::<DeckId>::new();
|
||||||
|
let sampling = root_limit < non_depleted_decks;
|
||||||
|
if sampling {
|
||||||
|
// switch to sampling
|
||||||
|
|
||||||
|
let mut deck_ids: Vec<DeckId> = vec![];
|
||||||
|
for deck_data in &decks {
|
||||||
|
if !deck_data.depleted {
|
||||||
|
deck_ids.push(deck_data.deck_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _i in 0..root_limit {
|
||||||
|
let selected_index = rng.random_range(0..deck_ids.len());
|
||||||
|
let selected_deck_id = deck_ids[selected_index];
|
||||||
|
sampled_deck_ids.insert(selected_deck_id);
|
||||||
|
deck_ids.swap_remove(selected_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for deck_data in &mut decks {
|
||||||
|
if self.limits.root_limit_reached(LimitKind::New) {
|
||||||
|
do_continue = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if sampling && !sampled_deck_ids.contains(&deck_data.deck_id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if deck_data.depleted {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Some(card) = deck_data.cards.next() {
|
||||||
|
if self.add_new_card(card) {
|
||||||
|
self.limits
|
||||||
|
.decrement_deck_and_parent_limits(deck_data.deck_id, LimitKind::New)?;
|
||||||
|
do_continue = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn gather_new_cards_sorted(
|
fn gather_new_cards_sorted(
|
||||||
&mut self,
|
&mut self,
|
||||||
col: &mut Collection,
|
col: &mut Collection,
|
||||||
|
|
|
||||||
|
|
@ -399,6 +399,19 @@ mod test {
|
||||||
];
|
];
|
||||||
assert_eq!(col.queue_as_deck_and_template(parent.id), cards);
|
assert_eq!(col.queue_as_deck_and_template(parent.id), cards);
|
||||||
|
|
||||||
|
col.set_deck_gather_order(&mut parent, NewCardGatherPriority::InterleavedDecks);
|
||||||
|
let cards = vec![
|
||||||
|
(parent.id, 0),
|
||||||
|
(child.id, 0),
|
||||||
|
(grandchild.id, 0),
|
||||||
|
(child_2.id, 0),
|
||||||
|
(parent.id, 1),
|
||||||
|
(child.id, 1),
|
||||||
|
(grandchild.id, 1),
|
||||||
|
(child_2.id, 1),
|
||||||
|
];
|
||||||
|
assert_eq!(col.queue_as_deck_and_template(parent.id), cards);
|
||||||
|
|
||||||
// insertion order
|
// insertion order
|
||||||
col.set_deck_gather_order(&mut parent, NewCardGatherPriority::LowestPosition);
|
col.set_deck_gather_order(&mut parent, NewCardGatherPriority::LowestPosition);
|
||||||
let cards = vec![
|
let cards = vec![
|
||||||
|
|
|
||||||
|
|
@ -341,6 +341,24 @@ impl super::SqliteStorage {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn new_cards_in_deck(
|
||||||
|
&self,
|
||||||
|
deck: DeckId,
|
||||||
|
sort: NewCardSorting,
|
||||||
|
) -> Result<Vec<NewCard>> {
|
||||||
|
let mut stmt = self.db.prepare_cached(&format!(
|
||||||
|
"{} ORDER BY {}",
|
||||||
|
include_str!("new_cards.sql"),
|
||||||
|
sort.write()
|
||||||
|
))?;
|
||||||
|
let mut rows = stmt.query(params![deck])?;
|
||||||
|
let mut names = Vec::new();
|
||||||
|
while let Some(row) = rows.next()? {
|
||||||
|
names.push(row_to_new_card(row)?);
|
||||||
|
}
|
||||||
|
Ok(names)
|
||||||
|
}
|
||||||
|
|
||||||
/// Call func() for each new card in the active decks, stopping when it
|
/// Call func() for each new card in the active decks, stopping when it
|
||||||
/// returns false or no more cards found.
|
/// returns false or no more cards found.
|
||||||
pub(crate) fn for_each_new_card_in_active_decks<F>(
|
pub(crate) fn for_each_new_card_in_active_decks<F>(
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,10 @@ export function newGatherPriorityChoices(): Choice<DeckConfig_Config_NewCardGath
|
||||||
label: tr.deckConfigNewGatherPriorityDeckThenRandomNotes(),
|
label: tr.deckConfigNewGatherPriorityDeckThenRandomNotes(),
|
||||||
value: DeckConfig_Config_NewCardGatherPriority.DECK_THEN_RANDOM_NOTES,
|
value: DeckConfig_Config_NewCardGatherPriority.DECK_THEN_RANDOM_NOTES,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: tr.deckConfigNewGatherPriorityInterleavedDecks(),
|
||||||
|
value: DeckConfig_Config_NewCardGatherPriority.INTERLEAVED_DECKS,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: tr.deckConfigNewGatherPriorityPositionLowestFirst(),
|
label: tr.deckConfigNewGatherPriorityPositionLowestFirst(),
|
||||||
value: DeckConfig_Config_NewCardGatherPriority.LOWEST_POSITION,
|
value: DeckConfig_Config_NewCardGatherPriority.LOWEST_POSITION,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue