Fsrs #12

Merged
micialware merged 6 commits from fsrs into master 2026-08-29 22:05:16 +03:00
9 changed files with 64 additions and 73 deletions
Showing only changes of commit d5a2a575fe - Show all commits

No files matched your search

+11 -15
View File
@@ -1,5 +1,5 @@
use crate::app::AppState;
use crate::core::srs::OrderModule::{WorstWordsSRS, FSRS, RandomSRS, SemiRandomSRS};
use crate::core::srs::OrderModule::{Fsrs, RandomSRS, SemiRandomSRS, WorstWordsSRS};
use crate::core::srs::{
FsrsModule, OrderMode, OrderModule, RandomSRSModule, SRSModule, SemiRandomSRSModule,
WordOpenMode, WorstWordsSRSModule,
@@ -48,7 +48,7 @@ impl DeckData {
words,
current_word_index: None,
state: state_for,
order_module: FSRS(FsrsModule::new()),
order_module: Fsrs(FsrsModule::new()),
open_mode: settings.open_mode,
settings: settings.clone(),
})
@@ -75,7 +75,7 @@ impl DeckData {
}
module.next(self.set.as_slice())
}
FSRS(ref mut module) => {
Fsrs(ref mut module) => {
if !module.initialized {
module.init(self.set.as_slice())
}
@@ -103,22 +103,18 @@ impl DeckData {
WorstWordsSRS(ref mut module) => {
module.open(status, index, word);
}
FSRS(ref mut module) => {
Fsrs(ref mut module) => {
module.open(status, index, word);
}
}
if let FSRS(ref fsrs) = self.order_module {
if fsrs.finished() {
self.order_module = match self.open_mode {
OrderMode::Default => {
SemiRandomSRS(SemiRandomSRSModule::new())
}
OrderMode::TrainWorstFirst => {
WorstWordsSRS(WorstWordsSRSModule::new())
}
OrderMode::FullRandom => RandomSRS(RandomSRSModule::new()),
}
if let Fsrs(ref fsrs) = self.order_module
&& fsrs.finished()
{
self.order_module = match self.open_mode {
OrderMode::Default => SemiRandomSRS(SemiRandomSRSModule::new()),
OrderMode::TrainWorstFirst => WorstWordsSRS(WorstWordsSRSModule::new()),
OrderMode::FullRandom => RandomSRS(RandomSRSModule::new()),
}
}
update_stat_score(word, &self.state.lock().connection)?;
+43 -22
View File
@@ -1,10 +1,10 @@
use crate::data::entities::CardStatistics;
use chrono::Local;
use chrono::{Local, TimeDelta};
use fsrs::FSRS;
use rand::distr::weighted::WeightedIndex;
use rand::prelude::ThreadRng;
use rand::prelude::{Distribution, SliceRandom};
use rand::rng;
use rand::{RngExt, rng};
const MAX_HISTORY_LEN: usize = 20;
const MAX_HISTORY_LEN_PART: f32 = 0.33;
@@ -28,7 +28,7 @@ pub enum OrderModule {
SemiRandomSRS(SemiRandomSRSModule),
RandomSRS(RandomSRSModule),
WorstWordsSRS(WorstWordsSRSModule),
FSRS(FsrsModule),
Fsrs(FsrsModule),
}
pub trait SRSModule {
fn next(&mut self, set: &[CardStatistics]) -> usize;
@@ -183,12 +183,12 @@ pub struct FsrsModule {
queue: Vec<usize>,
rng: ThreadRng,
fsrs: FSRS,
pub(crate) initialized: bool
pub(crate) initialized: bool,
}
impl SRSModule for FsrsModule {
fn next(&mut self, set: &[CardStatistics]) -> usize {
if self.solved.len() == self.current_group.len() {
fn next(&mut self, _: &[CardStatistics]) -> usize {
if self.current_group.is_empty() {
self.fill_partial_pool();
}
@@ -197,16 +197,39 @@ impl SRSModule for FsrsModule {
self.queue.shuffle(&mut self.rng);
}
self.queue.pop().expect("empty queue")
self.queue.pop().unwrap_or(0)
}
fn open(&mut self, status: WordOpenMode, index: usize, updated_word: &mut CardStatistics) {
let today = Local::now().date_naive();
let delta = today - updated_word.fsrs_opened;
let delta_days = delta.num_days();
if updated_word.fsrs_open_day != today{
let memory = self
.fsrs
.next_states(updated_word.fsrs_memory, 0.9, delta_days as u32)
.expect("Wrong next stage calculation");
let next = match status {
WordOpenMode::Easy => memory.easy,
WordOpenMode::Ok => memory.good,
WordOpenMode::Hard => memory.hard,
WordOpenMode::None => memory.again,
};
let mut next_day = today + TimeDelta::days(next.interval.round() as i64);
if (next_day - today).num_days() >= 5 {
let offset = ((rng().random::<f32>() - 0.5) * 3.0).round() as i32;
next_day += TimeDelta::days(offset as i64);
}
updated_word.fsrs_memory = Some(next.memory);
updated_word.fsrs_open_day = next_day;
updated_word.fsrs_opened = updated_word.last_open.date_naive();
if updated_word.fsrs_open_day != today {
self.solved.push(index);
let index = self.current_group.iter().position(|x| *x == index).unwrap();
self.current_group.remove(index);
}
}
@@ -226,8 +249,8 @@ impl SRSModule for FsrsModule {
}
impl FsrsModule {
pub fn new() -> Self{
Self{
pub fn new() -> Self {
Self {
today_set: vec![],
current_group: vec![],
queue: vec![],
@@ -238,21 +261,19 @@ impl FsrsModule {
}
}
fn fill_partial_pool(&mut self) {
self.current_group.clear();
self.current_group
.extend(self.today_set.iter().take(PARTIAL_POOL_SIZE));
self.current_group.iter().for_each(|_| {
self.today_set.remove(0);
});
self.current_group.extend(
self.today_set
.iter()
.filter(|x| !self.solved.contains(*x))
.take(PARTIAL_POOL_SIZE),
);
self.queue.clear();
self.queue
.extend(self.current_group.clone());
self.solved.clear();
self.queue.extend(self.current_group.clone());
}
fn fill_queue(&mut self) {
self.queue.clear();
self.queue.extend(self.current_group.iter().filter(|c| !self.today_set.contains(*c)));
self.queue.extend(self.current_group.clone());
}
pub fn finished(&self) -> bool {
+2 -26
View File
@@ -1,10 +1,9 @@
use crate::app::AppState;
use crate::core::srs::{FADE_PER_DAY, MAX_SCORE, OrderMode, WordOpenMode};
use crate::data::{INVALID_ID, Id};
use chrono::{DateTime, Local, NaiveDate, Utc};
use fsrs::{FSRS, MemoryState};
use chrono::{DateTime, NaiveDate, Utc};
use fsrs::MemoryState;
use log::info;
use rand::{RngExt, random, rng};
use rayon::prelude::*;
use rhai::{Engine, Scope};
use std::collections::HashMap;
@@ -162,29 +161,6 @@ impl CardStatistics {
self.last_open = Utc::now();
}
pub fn update_fsrs(&mut self, status: WordOpenMode, by_fsrs: bool, fsrs: FSRS)
{
let today = Local::now().date_naive().to_epoch_days();
let written = self.fsrs_opened.to_epoch_days();
let memory = fsrs
.next_states(self.fsrs_memory, 0.9, (today - written) as u32)
.expect("Wrong next stage calculation");
let next = match status {
WordOpenMode::Easy => memory.easy,
WordOpenMode::Ok => memory.good,
WordOpenMode::Hard => memory.hard,
WordOpenMode::None => memory.again,
};
let mut next_day = today + next.interval.round() as i32;
if next_day - today >= 5 {
let offset = ((rng().random::<f32>() - 0.5) * 3.0).round() as i32;
next_day += offset;
}
self.fsrs_memory = Some(next.memory);
self.fsrs_open_day = NaiveDate::from_epoch_days(next_day).unwrap();
self.fsrs_opened = self.last_open.date_naive();
}
pub fn calculated_score(&self) -> f32 {
let time = Utc::now() - self.last_open;
let days = time.num_days();
-1
View File
@@ -4,7 +4,6 @@ use crate::notification::{MaybeError, NotificationData};
use chrono::NaiveDate;
use fsrs::MemoryState;
use log::info;
use reqwest::get;
use rusqlite::{Connection, params};
use std::time::Instant;
-4
View File
@@ -1,11 +1,7 @@
use crate::core::srs::OrderMode;
use crate::data::Id;
use crate::data::entities::DeckSettings;
use crate::notification::NotificationData::{DbLoadError, DbSaveError};
use crate::notification::{MaybeError, NotificationData};
use chrono::{Datelike, Local, NaiveDate};
use fsrs::Card;
use iced::futures::StreamExt;
use rusqlite::Connection;
pub fn load_sets(connection: &Connection) -> Result<Vec<DeckSettings>, NotificationData> {
+5 -2
View File
@@ -5,13 +5,16 @@ use crate::fs::*;
use crate::notification::NotificationData::PermissionDenied;
use crate::notification::{MaybeError, NotificationData};
use chrono::{DateTime, NaiveDate};
use rusqlite::Connection;
use std::fs::{File, OpenOptions};
use std::io::Write;
use std::io::{BufRead, BufReader};
use std::path::PathBuf;
use rusqlite::Connection;
pub fn get_open_count_of_set(id: Id, connection: &Connection) -> Result<Vec<(NaiveDate, u32)>, NotificationData>{
pub fn get_open_count_of_set(
id: Id,
connection: &Connection,
) -> Result<Vec<(NaiveDate, u32)>, NotificationData> {
todo!()
}
+1 -1
View File
@@ -1,5 +1,5 @@
pub(crate) mod decks;
pub(crate) mod card_states;
pub(crate) mod decks;
pub(crate) mod history;
pub(crate) mod import;
pub(crate) mod settings;
+1 -1
View File
@@ -1,7 +1,7 @@
use crate::app::AppState;
use crate::data::entities::DeckSettings;
use crate::data_provider::decks::{delete_set, update_deck};
use crate::data_provider::card_states::{delete_stats, load_stats_of_deck};
use crate::data_provider::decks::{delete_set, update_deck};
use crate::navigation::Page::PreviousPage;
use crate::navigation::{NavigatedPage, Page, RootMessage};
use crate::notification::MaybeError;
+1 -1
View File
@@ -2,8 +2,8 @@ use crate::app::AppState;
use crate::core::srs::{AppendMode, OrderMode};
use crate::data::Id;
use crate::data::entities::{CardStatistics, DeckSettings};
use crate::data_provider::decks::update_deck;
use crate::data_provider::card_states::{add_stat_list, load_stats_of_deck};
use crate::data_provider::decks::update_deck;
use crate::navigation::Page::{History, PreviousPage, Repetition, RepetitionSettings};
use crate::navigation::{NavigatedPage, Page, RootMessage};
use crate::notification::{MaybeError, NotificationRequest};