From de3094da85d327eede1075cf8124c735c1269597 Mon Sep 17 00:00:00 2001 From: xiety Date: Sat, 7 Sep 2024 11:55:50 +0300 Subject: [PATCH] Remove custom implementation in favor of ts-fsrs --- src/App.vue | 37 +++++------ src/IFsrsCalculator.ts | 23 ------- src/fsrsCalculator.ts | 139 ---------------------------------------- src/tsFsrsCalculator.ts | 37 ++++++++--- 4 files changed, 44 insertions(+), 192 deletions(-) delete mode 100644 src/IFsrsCalculator.ts delete mode 100644 src/fsrsCalculator.ts diff --git a/src/App.vue b/src/App.vue index 2d181a4..8e2f2f0 100644 --- a/src/App.vue +++ b/src/App.vue @@ -37,13 +37,9 @@ -
- - -
-
+
- +
@@ -130,9 +126,7 @@ import { Colors, } from 'chart.js'; import type { ChartData, ChartDataset } from 'chart.js'; -import { Card, type IFsrsCalculator } from "./IFsrsCalculator"; -import { FsrsCalculator } from './fsrsCalculator'; -import { TsFsrsCalculator } from './tsFsrsCalculator'; +import { Card, TsFsrsCalculator } from './tsFsrsCalculator'; import { sliders, additionalSliders, default_parameters, initial_reviews } from './sliderInfo'; import { useManualRefHistory } from '@vueuse/core'; import zoomPlugin from 'chartjs-plugin-zoom'; @@ -145,16 +139,15 @@ ChartJS.register(Title, Tooltip, Legend, PointElement, LineElement, CategoryScal function nameof(name: keyof T) { return name; } -const mode = ref("interval"); +const mode = ref('interval'); const animation = ref(true); -const tsfsrs = ref(false); -const names = ["", "Again", "Hard", "Good", "Easy"]; +const names = ['', 'Again', 'Hard', 'Good', 'Easy']; -const short_term_desc= ref('When disabled, this allow user to skip the short-term scheduler and directly switch to the long-term scheduler.') +const short_term_desc = 'When disabled, this allow user to skip the short-term scheduler and directly switch to the long-term scheduler.' //can't disable animation using reactive options, so using watch watch(animation, a => { - if (typeof options.animation == "object") { + if (typeof options.animation == 'object') { options.animation.duration = (a ? 500 : 0); } }); @@ -165,7 +158,7 @@ const options = createOptions({ return `Days: ${unique.join(', ')}`; }, tooltip_function: (item: MyData) => { - const review_text = item.review.join(""); + const review_text = item.review.join(''); return `${review_text}: ${names[item.x]}, Stability: ${item.card.stability.toFixed(2)}, Difficulty: ${item.card.displayDifficulty.toFixed(0)}%`; } }); @@ -196,7 +189,7 @@ const initial_m = [0.9]; const fsrs_params = ref({ w: [...default_parameters], m: [...initial_m], - short_term: true, + short_term: false, }); const { commit, undo, redo, canUndo, canRedo, undoStack, redoStack } = useManualRefHistory(fsrs_params, { clone: true }); @@ -207,16 +200,14 @@ function createLabels() { } function createData(): ChartData<'line', MyData[]> { - const calc: IFsrsCalculator = tsfsrs.value - ? new TsFsrsCalculator(fsrs_params.value.w, fsrs_params.value.m, fsrs_params.value.short_term) - : new FsrsCalculator(fsrs_params.value.w, fsrs_params.value.m, fsrs_params.value.short_term); + const calc = new TsFsrsCalculator(fsrs_params.value.w, fsrs_params.value.m, fsrs_params.value.short_term); // could not use dataset's yAxisKey here because chart component is not watching it and doesn't update automatically return { labels: createLabels(), datasets: reviews.value.map(review => { return { - label: review.join(""), + label: review.join(''), pointRadius: 4, pointHoverRadius: 5, data: calc.steps(review).map(a => convertCardToMyData(a, review)), @@ -229,9 +220,13 @@ const data = computed(createData); const w_text = computed({ get: () => fsrs_params.value.w.map(f => f.toFixed(4)).join(', '), - set: (newValue) => fsrs_params.value.w = newValue.replace(', ', ',').split(',').map(parseFloat) + set: (newValue) => fsrs_params.value.w = resize_array(newValue.replace(', ', ',').split(',').map(a => parseFloat(a) || 0), default_parameters.length, 0.0) }); +function resize_array(arr: T[], length: number, filler: T) : T[] { + return arr.concat(new Array(Math.max(length - arr.length, 0)).fill(filler)); +} + function reset() { for (let i in default_parameters) { fsrs_params.value.w[i] = default_parameters[i]; diff --git a/src/IFsrsCalculator.ts b/src/IFsrsCalculator.ts deleted file mode 100644 index 4f33e5f..0000000 --- a/src/IFsrsCalculator.ts +++ /dev/null @@ -1,23 +0,0 @@ -export interface IFsrsCalculator { - steps(reviews: number[]): Card[]; -} - -export class Card { - state: number; - difficulty: number; - displayDifficulty: number; - stability: number; - interval: number; - cumulativeInterval: number; - grade: number; - - public constructor(state: number, difficulty: number, displayDifficulty: number, stability: number, interval: number, cumulativeInterval: number, grade: number) { - this.state = state; - this.difficulty = difficulty; - this.displayDifficulty = displayDifficulty; - this.stability = stability; - this.interval = interval; - this.cumulativeInterval = cumulativeInterval; - this.grade = grade; - } -} diff --git a/src/fsrsCalculator.ts b/src/fsrsCalculator.ts deleted file mode 100644 index 2b89b21..0000000 --- a/src/fsrsCalculator.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { Card, type IFsrsCalculator } from "./IFsrsCalculator"; - -export class FsrsCalculator implements IFsrsCalculator { - readonly w: number[]; - readonly desiredR: number; - readonly decay: number; - readonly factor: number; - readonly enableShortTerm: boolean; - - public constructor(w: number[], m: number[], shortTerm: boolean) { - this.w = w; - this.desiredR = m[0]; - this.decay = -0.5; - this.factor = 19.0 / 81.0; - this.enableShortTerm = shortTerm - } - - calcInterval(r: number, s: number): number { - return Math.max(1, Math.round((s / this.factor) * (Math.pow(r, 1.0 / this.decay) - 1.0))); - } - - calcRetention(interval: number, s: number): number { - return Math.pow(1 + (this.factor * interval) / s, this.decay); - } - - calcStabilityStart(g: number): number { - return this.w[g - 1]; - } - - calcDifficultyStart(g: number): number { - const d = this.w[4] - Math.exp((g - 1) * this.w[5]) + 1; - return this.clamp(d, 1, 10); - } - - calcDifficultyNormal(d: number, g: number): number { - const dn = d - this.w[6] * (g - 3.0); - const dn2 = this.w[7] * this.calcDifficultyStart(4) + (1.0 - this.w[7]) * dn; - return this.clamp(dn2, 1, 10); - } - - calcRevExp(w: number, r: number): number { - return Math.exp(w * (1.0 - r)); - } - - calcStabilityNormal(d: number, s: number, r: number, g: number): number { - let p = 1.0; - if (g == 2) - p = this.w[15]; - else if (g == 4) - p = this.w[16]; - const sinc = Math.exp(this.w[8]) * (11.0 - d) * Math.pow(s, -this.w[9]) * (this.calcRevExp(this.w[10], r) - 1.0) * p; - return s * (1.0 + sinc); - } - - calcStabilityFailed(d: number, s: number, r: number): number { - return this.w[11] * Math.pow(d, -this.w[12]) * (Math.pow(s + 1.0, this.w[13]) - 1.0) * this.calcRevExp(this.w[14], r); - } - - calcStabilityShortTerm(s: number, g: number): number { - return s * Math.exp(this.w[17] * (g - 3 + this.w[18])); - } - - calcDisplayDifficulty(d: number) { - return (d - 1.0) / 9.0 * 100.0; - } - - calcState(state: number, grade: number, interval: number): number { - // state : 0 =New, 1=Learning, 2=Review, 3=Relearning - // grade : 1=Again, 2=Hard, 3=Good, 4=Easy - if (!this.enableShortTerm) { - return 2; - } - switch (state) { - case 0: - return grade == 4 ? 2 : 1; - case 1: - case 3: - return (grade == 1 || interval < 1) ? state : 2; - case 2: - return grade == 1 ? 3 : 2; - } - return state; - } - - clamp(number: number, min: number, max: number) { - return Math.max(min, Math.min(number, max)); - } - - public step(card: Card, grade: number): Card { - if (grade < 1 || grade > 4) - return card; - - const retention = this.calcRetention(card.interval, card.stability); - const difficulty = this.calcNextDifficulty(card, grade); - const stability = this.calcNextStability(card, grade, retention); - const displayDifficulty = this.calcDisplayDifficulty(difficulty); - const interval = this.calcInterval(this.desiredR, stability); - const cumulativeInterval = card.cumulativeInterval + interval; - const state = this.calcState(card.state, grade, interval); - - return new Card(state, difficulty, displayDifficulty, stability, interval, cumulativeInterval, grade); - } - - private calcNextDifficulty(card: Card, grade: number): number { - if (card.state == 0) { - return this.calcDifficultyStart(grade); - } else { - return this.calcDifficultyNormal(card.difficulty, grade); - } - } - - private calcNextStability(card: Card, grade: number, retention: number): number { - if (card.state == 0) { - return this.calcStabilityStart(grade); - } else { - if (card.state == 1 || card.state == 3) { - return this.calcStabilityShortTerm(card.stability, grade); - } else { - if (grade == 1) { - return this.calcStabilityFailed(card.difficulty, card.stability, retention); - } else { - return this.calcStabilityNormal(card.difficulty, card.stability, retention, grade); - } - } - } - } - - public steps(reviews: number[]): Card[] { - let card = new Card(0, 0.0, 0.0, 0.0, 0.0, 0.0, 0); - const list = []; - - for (const review of reviews) { - card = this.step(card, review); - list.push(card); - } - - return list; - } -} diff --git a/src/tsFsrsCalculator.ts b/src/tsFsrsCalculator.ts index cc09bef..4764540 100644 --- a/src/tsFsrsCalculator.ts +++ b/src/tsFsrsCalculator.ts @@ -1,15 +1,14 @@ -import { State, createEmptyCard, fsrs, generatorParameters, type Grade } from "ts-fsrs"; -import { Card, type IFsrsCalculator } from "./IFsrsCalculator"; +import { createEmptyCard, fsrs, generatorParameters, type Grade } from "ts-fsrs"; -export class TsFsrsCalculator implements IFsrsCalculator { +export class TsFsrsCalculator { readonly w: number[]; - readonly desiredR: number; - readonly enableShortTerm: boolean; + readonly request_retention: number; + readonly enable_short_term: boolean; public constructor(w: number[], m: number[], enable: boolean) { this.w = w; - this.desiredR = m[0]; - this.enableShortTerm = enable; + this.request_retention = m[0]; + this.enable_short_term = enable; } calcDisplayDifficulty(d: number) { @@ -24,8 +23,8 @@ export class TsFsrsCalculator implements IFsrsCalculator { const f = fsrs(generatorParameters({ w: this.w, - request_retention: this.desiredR, - enable_short_term: this.enableShortTerm + request_retention: this.request_retention, + enable_short_term: this.enable_short_term })); for (const review of reviews) { @@ -48,3 +47,23 @@ export class TsFsrsCalculator implements IFsrsCalculator { return list; } } + +export class Card { + state: number; + difficulty: number; + displayDifficulty: number; + stability: number; + interval: number; + cumulativeInterval: number; + grade: number; + + public constructor(state: number, difficulty: number, displayDifficulty: number, stability: number, interval: number, cumulativeInterval: number, grade: number) { + this.state = state; + this.difficulty = difficulty; + this.displayDifficulty = displayDifficulty; + this.stability = stability; + this.interval = interval; + this.cumulativeInterval = cumulativeInterval; + this.grade = grade; + } +}