ts-fsrs 2.0.2 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/lib/fsrs.d.ts +126 -126
- package/lib/fsrs.js +189 -189
- package/lib/help.d.ts +19 -19
- package/lib/help.js +70 -70
- package/lib/index.d.ts +25 -25
- package/lib/index.js +64 -64
- package/lib/models.d.ts +52 -52
- package/lib/models.js +23 -23
- package/lib/scheduler.d.ts +53 -53
- package/lib/scheduler.js +95 -95
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# About The
|
|
2
2
|
|
|
3
|
-
ts-fsrs is a TypeScript package used to implement the Free Spaced Repetition Scheduler (FSRS) algorithm. It helps
|
|
4
|
-
developers apply FSRS to their flashcard applications,
|
|
3
|
+
ts-fsrs is a TypeScript package used to implement the [Free Spaced Repetition Scheduler (FSRS) algorithm](https://github.com/open-spaced-repetition/free-spaced-repetition-scheduler). It helps
|
|
4
|
+
developers apply FSRS to their flashcard applications, there by improving the user learning experience.
|
|
5
5
|
|
|
6
6
|
# Usage
|
|
7
7
|
|
package/lib/fsrs.d.ts
CHANGED
|
@@ -1,126 +1,126 @@
|
|
|
1
|
-
import { Card, FSRSParameters, Rating, SchedulingCard, State } from "./index";
|
|
2
|
-
import { int } from './help';
|
|
3
|
-
export default class FSRS {
|
|
4
|
-
private param;
|
|
5
|
-
private readonly intervalModifier;
|
|
6
|
-
private seed?;
|
|
7
|
-
constructor(param?: FSRSParameters);
|
|
8
|
-
repeat: (card: Card, now: Date) => {
|
|
9
|
-
0: {
|
|
10
|
-
card: Card;
|
|
11
|
-
log: {
|
|
12
|
-
rating: Rating;
|
|
13
|
-
state: State;
|
|
14
|
-
elapsed_days: number;
|
|
15
|
-
scheduled_days: number;
|
|
16
|
-
review: Date;
|
|
17
|
-
};
|
|
18
|
-
};
|
|
19
|
-
1: {
|
|
20
|
-
card: Card;
|
|
21
|
-
log: {
|
|
22
|
-
rating: Rating;
|
|
23
|
-
state: State;
|
|
24
|
-
elapsed_days: number;
|
|
25
|
-
scheduled_days: number;
|
|
26
|
-
review: Date;
|
|
27
|
-
};
|
|
28
|
-
};
|
|
29
|
-
2: {
|
|
30
|
-
card: Card;
|
|
31
|
-
log: {
|
|
32
|
-
rating: Rating;
|
|
33
|
-
state: State;
|
|
34
|
-
elapsed_days: number;
|
|
35
|
-
scheduled_days: number;
|
|
36
|
-
review: Date;
|
|
37
|
-
};
|
|
38
|
-
};
|
|
39
|
-
3: {
|
|
40
|
-
card: Card;
|
|
41
|
-
log: {
|
|
42
|
-
rating: Rating;
|
|
43
|
-
state: State;
|
|
44
|
-
elapsed_days: number;
|
|
45
|
-
scheduled_days: number;
|
|
46
|
-
review: Date;
|
|
47
|
-
};
|
|
48
|
-
};
|
|
49
|
-
};
|
|
50
|
-
get_retrievability: (card: Card, now: Date) => undefined | string;
|
|
51
|
-
init_ds(s: SchedulingCard): void;
|
|
52
|
-
/**
|
|
53
|
-
*
|
|
54
|
-
* @param s scheduling Card
|
|
55
|
-
* @param last_d Difficulty
|
|
56
|
-
* @param last_s Stability
|
|
57
|
-
* @param retrievability Retrievability
|
|
58
|
-
*/
|
|
59
|
-
next_ds(s: SchedulingCard, last_d: number, last_s: number, retrievability: number): void;
|
|
60
|
-
/**
|
|
61
|
-
* The formula used is :
|
|
62
|
-
* $$S_0(G) = w_0 + G \cdot w_1$$
|
|
63
|
-
* $$\max \{S_0,0.1\}$$
|
|
64
|
-
* @param g Grade (rating at Anki) [0.again,1.hard,2.good,3.easy]
|
|
65
|
-
* @return Stability (interval when R=90%)
|
|
66
|
-
*/
|
|
67
|
-
init_stability(g: number): number;
|
|
68
|
-
/**
|
|
69
|
-
* The formula used is :
|
|
70
|
-
* $$D_0(G) = w_2 + (G-2) \cdot w_3$$
|
|
71
|
-
* $$\min \{\max \{D_0(G),1\},10\}$$
|
|
72
|
-
* @param g Grade (rating at Anki) [0.again,1.hard,2.good,3.easy]
|
|
73
|
-
* @return Difficulty D \in [1,10]
|
|
74
|
-
*/
|
|
75
|
-
init_difficulty(g: number): number;
|
|
76
|
-
apply_fuzz(ivl: number): number;
|
|
77
|
-
next_interval(s: number): int;
|
|
78
|
-
/**
|
|
79
|
-
* The formula used is :
|
|
80
|
-
* $$next_d = D + w_4 \cdot (R - 2)$$
|
|
81
|
-
* $$D^\prime(D,R) = w_5 \cdot D_0(2) +(1 - w_5) \cdot next_d$$
|
|
82
|
-
* @param d
|
|
83
|
-
* @param g Grade (Rating[0.again,1.hard,2.good,3.easy])
|
|
84
|
-
* @return next_D
|
|
85
|
-
*/
|
|
86
|
-
next_difficulty(d: number, g: number): number;
|
|
87
|
-
/**
|
|
88
|
-
* The formula used is :
|
|
89
|
-
* $$\min \{\max \{D_0,1\},10\}$$
|
|
90
|
-
*/
|
|
91
|
-
constrain_difficulty(difficulty: number): number;
|
|
92
|
-
/**
|
|
93
|
-
* The formula used is :
|
|
94
|
-
* $$w_5 \cdot init +(1 - w_5) \cdot current$$
|
|
95
|
-
* @param init $$w_2 : D_0(2) = w_2 + (R-2) \cdot w_3= w_2$$
|
|
96
|
-
* @param current $$D + w_4 \cdot (R - 2)$$
|
|
97
|
-
* @return difficulty
|
|
98
|
-
*/
|
|
99
|
-
mean_reversion(init: number, current: number): number;
|
|
100
|
-
/**
|
|
101
|
-
* The formula used is :
|
|
102
|
-
* $$S^\prime_r(D,S,R) = S\cdot(e^{w_6}\cdot (11-D)\cdot S^{w_7}\cdot(e^{w_8\cdot(1-R)}-1)+1)$$
|
|
103
|
-
* @param d Difficulty D \in [1,10]
|
|
104
|
-
* @param s Stability (interval when R=90%)
|
|
105
|
-
* @param r Retrievability (probability of recall)
|
|
106
|
-
* @return S^\prime_r new stability after recall
|
|
107
|
-
*/
|
|
108
|
-
next_recall_stability(d: number, s: number, r: number): number;
|
|
109
|
-
/**
|
|
110
|
-
* The formula used is :
|
|
111
|
-
* $$S^\prime_f(D,S,R) = w_9\cdot D^{w_{10}}\cdot S^{w_{11}}\cdot e^{w_{12}\cdot(1-R)}.$$
|
|
112
|
-
* @param d Difficulty D \in [1,10]
|
|
113
|
-
* @param s Stability (interval when R=90%)
|
|
114
|
-
* @param r Retrievability (probability of recall)
|
|
115
|
-
* @return S^\prime_f new stability after forgetting
|
|
116
|
-
*/
|
|
117
|
-
next_forget_stability(d: number, s: number, r: number): number;
|
|
118
|
-
/**
|
|
119
|
-
* The formula used is :
|
|
120
|
-
* $$R(t,S) = 0.9^{\frac{t}{S}}$$
|
|
121
|
-
* @param t t days since the last review
|
|
122
|
-
* @param s Stability (interval when R=90%)
|
|
123
|
-
* @return r Retrievability (probability of recall)
|
|
124
|
-
*/
|
|
125
|
-
current_retrievability(t: number, s: number): number;
|
|
126
|
-
}
|
|
1
|
+
import { Card, FSRSParameters, Rating, SchedulingCard, State } from "./index";
|
|
2
|
+
import { int } from './help';
|
|
3
|
+
export default class FSRS {
|
|
4
|
+
private param;
|
|
5
|
+
private readonly intervalModifier;
|
|
6
|
+
private seed?;
|
|
7
|
+
constructor(param?: FSRSParameters);
|
|
8
|
+
repeat: (card: Card, now: Date) => {
|
|
9
|
+
0: {
|
|
10
|
+
card: Card;
|
|
11
|
+
log: {
|
|
12
|
+
rating: Rating;
|
|
13
|
+
state: State;
|
|
14
|
+
elapsed_days: number;
|
|
15
|
+
scheduled_days: number;
|
|
16
|
+
review: Date;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
1: {
|
|
20
|
+
card: Card;
|
|
21
|
+
log: {
|
|
22
|
+
rating: Rating;
|
|
23
|
+
state: State;
|
|
24
|
+
elapsed_days: number;
|
|
25
|
+
scheduled_days: number;
|
|
26
|
+
review: Date;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
2: {
|
|
30
|
+
card: Card;
|
|
31
|
+
log: {
|
|
32
|
+
rating: Rating;
|
|
33
|
+
state: State;
|
|
34
|
+
elapsed_days: number;
|
|
35
|
+
scheduled_days: number;
|
|
36
|
+
review: Date;
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
3: {
|
|
40
|
+
card: Card;
|
|
41
|
+
log: {
|
|
42
|
+
rating: Rating;
|
|
43
|
+
state: State;
|
|
44
|
+
elapsed_days: number;
|
|
45
|
+
scheduled_days: number;
|
|
46
|
+
review: Date;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
get_retrievability: (card: Card, now: Date) => undefined | string;
|
|
51
|
+
init_ds(s: SchedulingCard): void;
|
|
52
|
+
/**
|
|
53
|
+
*
|
|
54
|
+
* @param s scheduling Card
|
|
55
|
+
* @param last_d Difficulty
|
|
56
|
+
* @param last_s Stability
|
|
57
|
+
* @param retrievability Retrievability
|
|
58
|
+
*/
|
|
59
|
+
next_ds(s: SchedulingCard, last_d: number, last_s: number, retrievability: number): void;
|
|
60
|
+
/**
|
|
61
|
+
* The formula used is :
|
|
62
|
+
* $$S_0(G) = w_0 + G \cdot w_1$$
|
|
63
|
+
* $$\max \{S_0,0.1\}$$
|
|
64
|
+
* @param g Grade (rating at Anki) [0.again,1.hard,2.good,3.easy]
|
|
65
|
+
* @return Stability (interval when R=90%)
|
|
66
|
+
*/
|
|
67
|
+
init_stability(g: number): number;
|
|
68
|
+
/**
|
|
69
|
+
* The formula used is :
|
|
70
|
+
* $$D_0(G) = w_2 + (G-2) \cdot w_3$$
|
|
71
|
+
* $$\min \{\max \{D_0(G),1\},10\}$$
|
|
72
|
+
* @param g Grade (rating at Anki) [0.again,1.hard,2.good,3.easy]
|
|
73
|
+
* @return Difficulty D \in [1,10]
|
|
74
|
+
*/
|
|
75
|
+
init_difficulty(g: number): number;
|
|
76
|
+
apply_fuzz(ivl: number): number;
|
|
77
|
+
next_interval(s: number): int;
|
|
78
|
+
/**
|
|
79
|
+
* The formula used is :
|
|
80
|
+
* $$next_d = D + w_4 \cdot (R - 2)$$
|
|
81
|
+
* $$D^\prime(D,R) = w_5 \cdot D_0(2) +(1 - w_5) \cdot next_d$$
|
|
82
|
+
* @param d
|
|
83
|
+
* @param g Grade (Rating[0.again,1.hard,2.good,3.easy])
|
|
84
|
+
* @return next_D
|
|
85
|
+
*/
|
|
86
|
+
next_difficulty(d: number, g: number): number;
|
|
87
|
+
/**
|
|
88
|
+
* The formula used is :
|
|
89
|
+
* $$\min \{\max \{D_0,1\},10\}$$
|
|
90
|
+
*/
|
|
91
|
+
constrain_difficulty(difficulty: number): number;
|
|
92
|
+
/**
|
|
93
|
+
* The formula used is :
|
|
94
|
+
* $$w_5 \cdot init +(1 - w_5) \cdot current$$
|
|
95
|
+
* @param init $$w_2 : D_0(2) = w_2 + (R-2) \cdot w_3= w_2$$
|
|
96
|
+
* @param current $$D + w_4 \cdot (R - 2)$$
|
|
97
|
+
* @return difficulty
|
|
98
|
+
*/
|
|
99
|
+
mean_reversion(init: number, current: number): number;
|
|
100
|
+
/**
|
|
101
|
+
* The formula used is :
|
|
102
|
+
* $$S^\prime_r(D,S,R) = S\cdot(e^{w_6}\cdot (11-D)\cdot S^{w_7}\cdot(e^{w_8\cdot(1-R)}-1)+1)$$
|
|
103
|
+
* @param d Difficulty D \in [1,10]
|
|
104
|
+
* @param s Stability (interval when R=90%)
|
|
105
|
+
* @param r Retrievability (probability of recall)
|
|
106
|
+
* @return S^\prime_r new stability after recall
|
|
107
|
+
*/
|
|
108
|
+
next_recall_stability(d: number, s: number, r: number): number;
|
|
109
|
+
/**
|
|
110
|
+
* The formula used is :
|
|
111
|
+
* $$S^\prime_f(D,S,R) = w_9\cdot D^{w_{10}}\cdot S^{w_{11}}\cdot e^{w_{12}\cdot(1-R)}.$$
|
|
112
|
+
* @param d Difficulty D \in [1,10]
|
|
113
|
+
* @param s Stability (interval when R=90%)
|
|
114
|
+
* @param r Retrievability (probability of recall)
|
|
115
|
+
* @return S^\prime_f new stability after forgetting
|
|
116
|
+
*/
|
|
117
|
+
next_forget_stability(d: number, s: number, r: number): number;
|
|
118
|
+
/**
|
|
119
|
+
* The formula used is :
|
|
120
|
+
* $$R(t,S) = 0.9^{\frac{t}{S}}$$
|
|
121
|
+
* @param t t days since the last review
|
|
122
|
+
* @param s Stability (interval when R=90%)
|
|
123
|
+
* @return r Retrievability (probability of recall)
|
|
124
|
+
*/
|
|
125
|
+
current_retrievability(t: number, s: number): number;
|
|
126
|
+
}
|
package/lib/fsrs.js
CHANGED
|
@@ -1,189 +1,189 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const seedrandom_1 = __importDefault(require("seedrandom"));
|
|
7
|
-
const index_1 = require("./index");
|
|
8
|
-
class FSRS {
|
|
9
|
-
constructor(param) {
|
|
10
|
-
this.repeat = (card, now) => {
|
|
11
|
-
card = Object.assign({}, card);
|
|
12
|
-
now = new Date(now.getTime());
|
|
13
|
-
card.elapsed_days = card.state === index_1.State.New ? 0 : now.diff(card.last_review, "days"); //相距时间
|
|
14
|
-
card.last_review = now; // 上次复习时间
|
|
15
|
-
card.reps += 1;
|
|
16
|
-
const s = new index_1.SchedulingCard(card);
|
|
17
|
-
s.update_state(card.state);
|
|
18
|
-
this.seed = String(card.last_review.getTime()) + String(card.elapsed_days);
|
|
19
|
-
let easy_interval, good_interval, hard_interval;
|
|
20
|
-
switch (card.state) {
|
|
21
|
-
case index_1.State.New:
|
|
22
|
-
this.init_ds(s);
|
|
23
|
-
s.again.due = now.scheduler(1);
|
|
24
|
-
s.hard.due = now.scheduler(5);
|
|
25
|
-
s.good.due = now.scheduler(10);
|
|
26
|
-
easy_interval = this.next_interval(s.easy.stability * this.param.easy_bonus);
|
|
27
|
-
s.easy.scheduled_days = easy_interval;
|
|
28
|
-
s.easy.due = now.scheduler(easy_interval, true);
|
|
29
|
-
break;
|
|
30
|
-
case index_1.State.Learning:
|
|
31
|
-
case index_1.State.Relearning:
|
|
32
|
-
hard_interval = 0;
|
|
33
|
-
good_interval = this.next_interval(s.good.stability);
|
|
34
|
-
easy_interval = Math.max(this.next_interval(s.easy.stability * this.param.easy_bonus), good_interval + 1);
|
|
35
|
-
s.schedule(now, hard_interval, good_interval, easy_interval);
|
|
36
|
-
break;
|
|
37
|
-
case index_1.State.Review:
|
|
38
|
-
const interval = card.elapsed_days;
|
|
39
|
-
const last_d = card.difficulty;
|
|
40
|
-
const last_s = card.stability;
|
|
41
|
-
const retrievability = this.current_retrievability(interval, last_s);
|
|
42
|
-
this.next_ds(s, last_d, last_s, retrievability);
|
|
43
|
-
hard_interval = this.next_interval(last_s * this.param.hard_factor);
|
|
44
|
-
good_interval = this.next_interval(s.good.stability);
|
|
45
|
-
hard_interval = Math.min(hard_interval, good_interval);
|
|
46
|
-
good_interval = Math.max(good_interval, hard_interval + 1);
|
|
47
|
-
easy_interval = Math.max(this.next_interval(s.easy.stability * this.param.easy_bonus), good_interval + 1);
|
|
48
|
-
s.schedule(now, hard_interval, good_interval, easy_interval);
|
|
49
|
-
break;
|
|
50
|
-
}
|
|
51
|
-
return s.record_log(card, now);
|
|
52
|
-
};
|
|
53
|
-
this.get_retrievability = (card, now) => {
|
|
54
|
-
if (card.state !== index_1.State.Review) {
|
|
55
|
-
return undefined;
|
|
56
|
-
}
|
|
57
|
-
const t = Math.max(now.diff(card.last_review, "days"), 0);
|
|
58
|
-
return (this.current_retrievability(t, card.stability) * 100).toFixed(2) + '%';
|
|
59
|
-
};
|
|
60
|
-
this.param = param || (0, index_1.generatorParameters)();
|
|
61
|
-
this.intervalModifier = Math.log(this.param.request_retention) / Math.log(0.9);
|
|
62
|
-
}
|
|
63
|
-
init_ds(s) {
|
|
64
|
-
s.again.difficulty = this.init_difficulty(index_1.Rating.Again);
|
|
65
|
-
s.again.stability = this.init_stability(index_1.Rating.Again);
|
|
66
|
-
s.hard.difficulty = this.init_difficulty(index_1.Rating.Hard);
|
|
67
|
-
s.hard.stability = this.init_stability(index_1.Rating.Hard);
|
|
68
|
-
s.good.difficulty = this.init_difficulty(index_1.Rating.Good);
|
|
69
|
-
s.good.stability = this.init_stability(index_1.Rating.Good);
|
|
70
|
-
s.easy.difficulty = this.init_difficulty(index_1.Rating.Easy);
|
|
71
|
-
s.easy.stability = this.init_stability(index_1.Rating.Easy);
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
*
|
|
75
|
-
* @param s scheduling Card
|
|
76
|
-
* @param last_d Difficulty
|
|
77
|
-
* @param last_s Stability
|
|
78
|
-
* @param retrievability Retrievability
|
|
79
|
-
*/
|
|
80
|
-
next_ds(s, last_d, last_s, retrievability) {
|
|
81
|
-
s.again.difficulty = this.next_difficulty(last_d, index_1.Rating.Again);
|
|
82
|
-
s.again.stability = this.next_forget_stability(s.again.difficulty, last_s, retrievability);
|
|
83
|
-
s.hard.difficulty = this.next_difficulty(last_d, index_1.Rating.Hard);
|
|
84
|
-
s.hard.stability = this.next_recall_stability(s.hard.difficulty, last_s, retrievability);
|
|
85
|
-
s.good.difficulty = this.next_difficulty(last_d, index_1.Rating.Good);
|
|
86
|
-
s.good.stability = this.next_recall_stability(s.good.difficulty, last_s, retrievability);
|
|
87
|
-
s.easy.difficulty = this.next_difficulty(last_d, index_1.Rating.Easy);
|
|
88
|
-
s.easy.stability = this.next_recall_stability(s.easy.difficulty, last_s, retrievability);
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* The formula used is :
|
|
92
|
-
* $$S_0(G) = w_0 + G \cdot w_1$$
|
|
93
|
-
* $$\max \{S_0,0.1\}$$
|
|
94
|
-
* @param g Grade (rating at Anki) [0.again,1.hard,2.good,3.easy]
|
|
95
|
-
* @return Stability (interval when R=90%)
|
|
96
|
-
*/
|
|
97
|
-
init_stability(g) {
|
|
98
|
-
return Math.max(this.param.w[0] + this.param.w[1] * g, 0.1);
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* The formula used is :
|
|
102
|
-
* $$D_0(G) = w_2 + (G-2) \cdot w_3$$
|
|
103
|
-
* $$\min \{\max \{D_0(G),1\},10\}$$
|
|
104
|
-
* @param g Grade (rating at Anki) [0.again,1.hard,2.good,3.easy]
|
|
105
|
-
* @return Difficulty D \in [1,10]
|
|
106
|
-
*/
|
|
107
|
-
init_difficulty(g) {
|
|
108
|
-
return Math.min(Math.max(this.param.w[2] + this.param.w[3] * (g - 2), 1), 10);
|
|
109
|
-
}
|
|
110
|
-
apply_fuzz(ivl) {
|
|
111
|
-
if (!this.param.enable_fuzz || ivl < 2.5)
|
|
112
|
-
return ivl;
|
|
113
|
-
const generator = (0, seedrandom_1.default)(this.seed);
|
|
114
|
-
const fuzz_factor = generator();
|
|
115
|
-
ivl = Math.round(ivl);
|
|
116
|
-
const min_ivl = Math.max(2, Math.round(ivl * 0.95 - 1));
|
|
117
|
-
const max_ivl = Math.round(ivl * 1.05 + 1);
|
|
118
|
-
return Math.floor(fuzz_factor * (max_ivl - min_ivl + 1) + min_ivl);
|
|
119
|
-
}
|
|
120
|
-
next_interval(s) {
|
|
121
|
-
const newInterval = this.apply_fuzz(s * this.intervalModifier);
|
|
122
|
-
return Math.min(Math.max(Math.round(newInterval), 1), this.param.maximum_interval);
|
|
123
|
-
}
|
|
124
|
-
/**
|
|
125
|
-
* The formula used is :
|
|
126
|
-
* $$next_d = D + w_4 \cdot (R - 2)$$
|
|
127
|
-
* $$D^\prime(D,R) = w_5 \cdot D_0(2) +(1 - w_5) \cdot next_d$$
|
|
128
|
-
* @param d
|
|
129
|
-
* @param g Grade (Rating[0.again,1.hard,2.good,3.easy])
|
|
130
|
-
* @return next_D
|
|
131
|
-
*/
|
|
132
|
-
next_difficulty(d, g) {
|
|
133
|
-
const next_d = d + this.param.w[4] * (g - 2);
|
|
134
|
-
return this.constrain_difficulty(this.mean_reversion(this.param.w[2], next_d));
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* The formula used is :
|
|
138
|
-
* $$\min \{\max \{D_0,1\},10\}$$
|
|
139
|
-
*/
|
|
140
|
-
constrain_difficulty(difficulty) {
|
|
141
|
-
return Math.min(Math.max(Number(difficulty.toFixed(2)), 1), 10);
|
|
142
|
-
}
|
|
143
|
-
/**
|
|
144
|
-
* The formula used is :
|
|
145
|
-
* $$w_5 \cdot init +(1 - w_5) \cdot current$$
|
|
146
|
-
* @param init $$w_2 : D_0(2) = w_2 + (R-2) \cdot w_3= w_2$$
|
|
147
|
-
* @param current $$D + w_4 \cdot (R - 2)$$
|
|
148
|
-
* @return difficulty
|
|
149
|
-
*/
|
|
150
|
-
mean_reversion(init, current) {
|
|
151
|
-
return this.param.w[5] * init + (1 - this.param.w[5]) * current;
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* The formula used is :
|
|
155
|
-
* $$S^\prime_r(D,S,R) = S\cdot(e^{w_6}\cdot (11-D)\cdot S^{w_7}\cdot(e^{w_8\cdot(1-R)}-1)+1)$$
|
|
156
|
-
* @param d Difficulty D \in [1,10]
|
|
157
|
-
* @param s Stability (interval when R=90%)
|
|
158
|
-
* @param r Retrievability (probability of recall)
|
|
159
|
-
* @return S^\prime_r new stability after recall
|
|
160
|
-
*/
|
|
161
|
-
next_recall_stability(d, s, r) {
|
|
162
|
-
return s * (1 + Math.exp(this.param.w[6]) *
|
|
163
|
-
(11 - d) *
|
|
164
|
-
Math.pow(s, this.param.w[7]) *
|
|
165
|
-
(Math.exp((1 - r) * this.param.w[8]) - 1));
|
|
166
|
-
}
|
|
167
|
-
/**
|
|
168
|
-
* The formula used is :
|
|
169
|
-
* $$S^\prime_f(D,S,R) = w_9\cdot D^{w_{10}}\cdot S^{w_{11}}\cdot e^{w_{12}\cdot(1-R)}.$$
|
|
170
|
-
* @param d Difficulty D \in [1,10]
|
|
171
|
-
* @param s Stability (interval when R=90%)
|
|
172
|
-
* @param r Retrievability (probability of recall)
|
|
173
|
-
* @return S^\prime_f new stability after forgetting
|
|
174
|
-
*/
|
|
175
|
-
next_forget_stability(d, s, r) {
|
|
176
|
-
return this.param.w[9] * Math.pow(d, this.param.w[10]) * Math.pow(s, this.param.w[11]) * Math.exp((1 - r) * this.param.w[12]);
|
|
177
|
-
}
|
|
178
|
-
/**
|
|
179
|
-
* The formula used is :
|
|
180
|
-
* $$R(t,S) = 0.9^{\frac{t}{S}}$$
|
|
181
|
-
* @param t t days since the last review
|
|
182
|
-
* @param s Stability (interval when R=90%)
|
|
183
|
-
* @return r Retrievability (probability of recall)
|
|
184
|
-
*/
|
|
185
|
-
current_retrievability(t, s) {
|
|
186
|
-
return Math.exp(Math.log(0.9) * t / s);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
exports.default = FSRS;
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const seedrandom_1 = __importDefault(require("seedrandom"));
|
|
7
|
+
const index_1 = require("./index");
|
|
8
|
+
class FSRS {
|
|
9
|
+
constructor(param) {
|
|
10
|
+
this.repeat = (card, now) => {
|
|
11
|
+
card = Object.assign({}, card);
|
|
12
|
+
now = new Date(now.getTime());
|
|
13
|
+
card.elapsed_days = card.state === index_1.State.New ? 0 : now.diff(card.last_review, "days"); //相距时间
|
|
14
|
+
card.last_review = now; // 上次复习时间
|
|
15
|
+
card.reps += 1;
|
|
16
|
+
const s = new index_1.SchedulingCard(card);
|
|
17
|
+
s.update_state(card.state);
|
|
18
|
+
this.seed = String(card.last_review.getTime()) + String(card.elapsed_days);
|
|
19
|
+
let easy_interval, good_interval, hard_interval;
|
|
20
|
+
switch (card.state) {
|
|
21
|
+
case index_1.State.New:
|
|
22
|
+
this.init_ds(s);
|
|
23
|
+
s.again.due = now.scheduler(1);
|
|
24
|
+
s.hard.due = now.scheduler(5);
|
|
25
|
+
s.good.due = now.scheduler(10);
|
|
26
|
+
easy_interval = this.next_interval(s.easy.stability * this.param.easy_bonus);
|
|
27
|
+
s.easy.scheduled_days = easy_interval;
|
|
28
|
+
s.easy.due = now.scheduler(easy_interval, true);
|
|
29
|
+
break;
|
|
30
|
+
case index_1.State.Learning:
|
|
31
|
+
case index_1.State.Relearning:
|
|
32
|
+
hard_interval = 0;
|
|
33
|
+
good_interval = this.next_interval(s.good.stability);
|
|
34
|
+
easy_interval = Math.max(this.next_interval(s.easy.stability * this.param.easy_bonus), good_interval + 1);
|
|
35
|
+
s.schedule(now, hard_interval, good_interval, easy_interval);
|
|
36
|
+
break;
|
|
37
|
+
case index_1.State.Review:
|
|
38
|
+
const interval = card.elapsed_days;
|
|
39
|
+
const last_d = card.difficulty;
|
|
40
|
+
const last_s = card.stability;
|
|
41
|
+
const retrievability = this.current_retrievability(interval, last_s);
|
|
42
|
+
this.next_ds(s, last_d, last_s, retrievability);
|
|
43
|
+
hard_interval = this.next_interval(last_s * this.param.hard_factor);
|
|
44
|
+
good_interval = this.next_interval(s.good.stability);
|
|
45
|
+
hard_interval = Math.min(hard_interval, good_interval);
|
|
46
|
+
good_interval = Math.max(good_interval, hard_interval + 1);
|
|
47
|
+
easy_interval = Math.max(this.next_interval(s.easy.stability * this.param.easy_bonus), good_interval + 1);
|
|
48
|
+
s.schedule(now, hard_interval, good_interval, easy_interval);
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
return s.record_log(card, now);
|
|
52
|
+
};
|
|
53
|
+
this.get_retrievability = (card, now) => {
|
|
54
|
+
if (card.state !== index_1.State.Review) {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
const t = Math.max(now.diff(card.last_review, "days"), 0);
|
|
58
|
+
return (this.current_retrievability(t, card.stability) * 100).toFixed(2) + '%';
|
|
59
|
+
};
|
|
60
|
+
this.param = param || (0, index_1.generatorParameters)();
|
|
61
|
+
this.intervalModifier = Math.log(this.param.request_retention) / Math.log(0.9);
|
|
62
|
+
}
|
|
63
|
+
init_ds(s) {
|
|
64
|
+
s.again.difficulty = this.init_difficulty(index_1.Rating.Again);
|
|
65
|
+
s.again.stability = this.init_stability(index_1.Rating.Again);
|
|
66
|
+
s.hard.difficulty = this.init_difficulty(index_1.Rating.Hard);
|
|
67
|
+
s.hard.stability = this.init_stability(index_1.Rating.Hard);
|
|
68
|
+
s.good.difficulty = this.init_difficulty(index_1.Rating.Good);
|
|
69
|
+
s.good.stability = this.init_stability(index_1.Rating.Good);
|
|
70
|
+
s.easy.difficulty = this.init_difficulty(index_1.Rating.Easy);
|
|
71
|
+
s.easy.stability = this.init_stability(index_1.Rating.Easy);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
*
|
|
75
|
+
* @param s scheduling Card
|
|
76
|
+
* @param last_d Difficulty
|
|
77
|
+
* @param last_s Stability
|
|
78
|
+
* @param retrievability Retrievability
|
|
79
|
+
*/
|
|
80
|
+
next_ds(s, last_d, last_s, retrievability) {
|
|
81
|
+
s.again.difficulty = this.next_difficulty(last_d, index_1.Rating.Again);
|
|
82
|
+
s.again.stability = this.next_forget_stability(s.again.difficulty, last_s, retrievability);
|
|
83
|
+
s.hard.difficulty = this.next_difficulty(last_d, index_1.Rating.Hard);
|
|
84
|
+
s.hard.stability = this.next_recall_stability(s.hard.difficulty, last_s, retrievability);
|
|
85
|
+
s.good.difficulty = this.next_difficulty(last_d, index_1.Rating.Good);
|
|
86
|
+
s.good.stability = this.next_recall_stability(s.good.difficulty, last_s, retrievability);
|
|
87
|
+
s.easy.difficulty = this.next_difficulty(last_d, index_1.Rating.Easy);
|
|
88
|
+
s.easy.stability = this.next_recall_stability(s.easy.difficulty, last_s, retrievability);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* The formula used is :
|
|
92
|
+
* $$S_0(G) = w_0 + G \cdot w_1$$
|
|
93
|
+
* $$\max \{S_0,0.1\}$$
|
|
94
|
+
* @param g Grade (rating at Anki) [0.again,1.hard,2.good,3.easy]
|
|
95
|
+
* @return Stability (interval when R=90%)
|
|
96
|
+
*/
|
|
97
|
+
init_stability(g) {
|
|
98
|
+
return Math.max(this.param.w[0] + this.param.w[1] * g, 0.1);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The formula used is :
|
|
102
|
+
* $$D_0(G) = w_2 + (G-2) \cdot w_3$$
|
|
103
|
+
* $$\min \{\max \{D_0(G),1\},10\}$$
|
|
104
|
+
* @param g Grade (rating at Anki) [0.again,1.hard,2.good,3.easy]
|
|
105
|
+
* @return Difficulty D \in [1,10]
|
|
106
|
+
*/
|
|
107
|
+
init_difficulty(g) {
|
|
108
|
+
return Math.min(Math.max(this.param.w[2] + this.param.w[3] * (g - 2), 1), 10);
|
|
109
|
+
}
|
|
110
|
+
apply_fuzz(ivl) {
|
|
111
|
+
if (!this.param.enable_fuzz || ivl < 2.5)
|
|
112
|
+
return ivl;
|
|
113
|
+
const generator = (0, seedrandom_1.default)(this.seed);
|
|
114
|
+
const fuzz_factor = generator();
|
|
115
|
+
ivl = Math.round(ivl);
|
|
116
|
+
const min_ivl = Math.max(2, Math.round(ivl * 0.95 - 1));
|
|
117
|
+
const max_ivl = Math.round(ivl * 1.05 + 1);
|
|
118
|
+
return Math.floor(fuzz_factor * (max_ivl - min_ivl + 1) + min_ivl);
|
|
119
|
+
}
|
|
120
|
+
next_interval(s) {
|
|
121
|
+
const newInterval = this.apply_fuzz(s * this.intervalModifier);
|
|
122
|
+
return Math.min(Math.max(Math.round(newInterval), 1), this.param.maximum_interval);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* The formula used is :
|
|
126
|
+
* $$next_d = D + w_4 \cdot (R - 2)$$
|
|
127
|
+
* $$D^\prime(D,R) = w_5 \cdot D_0(2) +(1 - w_5) \cdot next_d$$
|
|
128
|
+
* @param d
|
|
129
|
+
* @param g Grade (Rating[0.again,1.hard,2.good,3.easy])
|
|
130
|
+
* @return next_D
|
|
131
|
+
*/
|
|
132
|
+
next_difficulty(d, g) {
|
|
133
|
+
const next_d = d + this.param.w[4] * (g - 2);
|
|
134
|
+
return this.constrain_difficulty(this.mean_reversion(this.param.w[2], next_d));
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* The formula used is :
|
|
138
|
+
* $$\min \{\max \{D_0,1\},10\}$$
|
|
139
|
+
*/
|
|
140
|
+
constrain_difficulty(difficulty) {
|
|
141
|
+
return Math.min(Math.max(Number(difficulty.toFixed(2)), 1), 10);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* The formula used is :
|
|
145
|
+
* $$w_5 \cdot init +(1 - w_5) \cdot current$$
|
|
146
|
+
* @param init $$w_2 : D_0(2) = w_2 + (R-2) \cdot w_3= w_2$$
|
|
147
|
+
* @param current $$D + w_4 \cdot (R - 2)$$
|
|
148
|
+
* @return difficulty
|
|
149
|
+
*/
|
|
150
|
+
mean_reversion(init, current) {
|
|
151
|
+
return this.param.w[5] * init + (1 - this.param.w[5]) * current;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* The formula used is :
|
|
155
|
+
* $$S^\prime_r(D,S,R) = S\cdot(e^{w_6}\cdot (11-D)\cdot S^{w_7}\cdot(e^{w_8\cdot(1-R)}-1)+1)$$
|
|
156
|
+
* @param d Difficulty D \in [1,10]
|
|
157
|
+
* @param s Stability (interval when R=90%)
|
|
158
|
+
* @param r Retrievability (probability of recall)
|
|
159
|
+
* @return S^\prime_r new stability after recall
|
|
160
|
+
*/
|
|
161
|
+
next_recall_stability(d, s, r) {
|
|
162
|
+
return s * (1 + Math.exp(this.param.w[6]) *
|
|
163
|
+
(11 - d) *
|
|
164
|
+
Math.pow(s, this.param.w[7]) *
|
|
165
|
+
(Math.exp((1 - r) * this.param.w[8]) - 1));
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* The formula used is :
|
|
169
|
+
* $$S^\prime_f(D,S,R) = w_9\cdot D^{w_{10}}\cdot S^{w_{11}}\cdot e^{w_{12}\cdot(1-R)}.$$
|
|
170
|
+
* @param d Difficulty D \in [1,10]
|
|
171
|
+
* @param s Stability (interval when R=90%)
|
|
172
|
+
* @param r Retrievability (probability of recall)
|
|
173
|
+
* @return S^\prime_f new stability after forgetting
|
|
174
|
+
*/
|
|
175
|
+
next_forget_stability(d, s, r) {
|
|
176
|
+
return this.param.w[9] * Math.pow(d, this.param.w[10]) * Math.pow(s, this.param.w[11]) * Math.exp((1 - r) * this.param.w[12]);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* The formula used is :
|
|
180
|
+
* $$R(t,S) = 0.9^{\frac{t}{S}}$$
|
|
181
|
+
* @param t t days since the last review
|
|
182
|
+
* @param s Stability (interval when R=90%)
|
|
183
|
+
* @return r Retrievability (probability of recall)
|
|
184
|
+
*/
|
|
185
|
+
current_retrievability(t, s) {
|
|
186
|
+
return Math.exp(Math.log(0.9) * t / s);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
exports.default = FSRS;
|