From 11ff765c53126e62ea565c4515a43fd70b6d51ea Mon Sep 17 00:00:00 2001 From: Jarrett Ye Date: Sat, 26 Nov 2022 18:19:57 +0800 Subject: [PATCH 1/7] Refactor/main architecture --- fsrs.go | 230 ++++++++++++++++++++++++++------------------------- fsrs_test.go | 37 +++++++++ go.mod | 4 +- models.go | 111 +++++++++++++------------ 4 files changed, 217 insertions(+), 165 deletions(-) create mode 100644 fsrs_test.go diff --git a/fsrs.go b/fsrs.go index ec4bf2a..8dfd5ff 100644 --- a/fsrs.go +++ b/fsrs.go @@ -1,133 +1,139 @@ package fsrs import ( - "fmt" "math" "time" - - "github.com/ImSingee/go-ex/set" ) -func (globalData *GlobalData) Learn(cardData *CardData, grade Grade) { - if grade < GradeNewCard || grade > GradeEasy { - panic(fmt.Sprintf("Invalid grade: %d", grade)) - } - - now := time.Now() - - if grade == GradeNewCard { // learn new card - addDay := math.Round(globalData.DefaultStability * math.Log(globalData.RequestRetention) / math.Log(0.9)) - - cardData.Due = now.Add(time.Duration(addDay * float64(24*time.Hour))) - cardData.Interval = 0 - cardData.Difficulty = globalData.DefaultDifficulty - cardData.Stability = globalData.DefaultStability - cardData.Retrievability = 1 - cardData.LastGrade = GradeNewCard - cardData.Review = now - cardData.Reps = 1 - cardData.Lapses = 0 - - return +const requestRetention = 0.9 +const maximumInterval = 36500.0 +const easyBonus = 1.3 +const hardFactor = 1.2 + +var intervalModifier = math.Log(requestRetention) / math.Log(0.9) + +func (w *Weights) Repeat(card *Card, now time.Time) *SchedulingCards { + + schedulingCards := new(SchedulingCards) + schedulingCards.init(card) + schedulingCards.updateState(card.State) + + switch card.State { + case New: + w.initDS(schedulingCards) + + easyInterval := w.nextInterval(schedulingCards.Easy.Stability * easyBonus) + schedulingCards.Easy.ScheduledDays = uint64(easyInterval) + schedulingCards.Easy.Due = now.Add(time.Duration(float64(easyInterval) * float64(24*time.Hour))) + return schedulingCards + case Learning, Relearning: + hardInterval := w.nextInterval(schedulingCards.Hard.Stability) + goodInterval := math.Max(w.nextInterval(schedulingCards.Good.Stability), hardInterval+1) + easyInterval := math.Max(w.nextInterval(schedulingCards.Easy.Stability*easyBonus), goodInterval+1) + + schedulingCards.schedule(now, hardInterval, goodInterval, easyInterval) + return schedulingCards + case Review: + interval := float64(now.Sub(card.LastReview)/time.Hour/24 + 1.0) + lastD := card.Difficulty + lastS := card.Stability + retrievability := math.Exp(math.Log(0.9) * interval / lastS) + w.nextDS(schedulingCards, lastD, lastS, retrievability) + + hardInterval := w.nextInterval(lastS * hardFactor) + goodInterval := math.Max(w.nextInterval(schedulingCards.Good.Stability), hardInterval+1) + easyInterval := math.Max(w.nextInterval(schedulingCards.Easy.Stability*easyBonus), goodInterval+1) + schedulingCards.schedule(now, hardInterval, goodInterval, easyInterval) + + return schedulingCards } + return schedulingCards +} - // review card after learn - lastDifficulty := cardData.Difficulty - lastStability := cardData.Stability - lastLapses := cardData.Lapses - lastReps := cardData.Reps - lastReview := cardData.Review - - h := cardData.CardDataItem - cardData.History = append(cardData.History, &h) - - diffDay := (time.Since(lastReview) / time.Hour / 24) + 1 - if diffDay > 0 { - cardData.Interval = uint64(diffDay) - } else { - cardData.Interval = 0 +func (s *SchedulingCards) updateState(state State) { + switch state { + case New: + s.Again.State = Learning + s.Hard.State = Learning + s.Good.State = Learning + s.Easy.State = Review + case Learning, Relearning: + s.Again.State = state + s.Hard.State = Review + s.Good.State = Review + s.Easy.State = Review + case Review: + s.Again.State = Relearning + s.Hard.State = Review + s.Good.State = Review + s.Easy.State = Review } +} - cardData.Review = now - cardData.Retrievability = math.Exp(math.Log(0.9) * float64(cardData.Interval) / lastStability) - cardData.Difficulty = math.Min(math.Max(lastDifficulty+cardData.Retrievability-float64(grade)+0.2, 1), 10) - - if grade == GradeForgetting { - cardData.Stability = globalData.DefaultStability * math.Exp(-0.3*float64(lastLapses+1)) - - if lastReps > 1 { - globalData.TotalDiff = globalData.TotalDiff - cardData.Retrievability - } +func (s *SchedulingCards) schedule(now time.Time, hardInterval float64, goodInterval float64, easyInterval float64) { + s.Hard.ScheduledDays = uint64(hardInterval) + s.Good.ScheduledDays = uint64(goodInterval) + s.Easy.ScheduledDays = uint64(easyInterval) + s.Hard.Due = now.Add(time.Duration(hardInterval * float64(24*time.Hour))) + s.Good.Due = now.Add(time.Duration(goodInterval * float64(24*time.Hour))) + s.Easy.Due = now.Add(time.Duration(easyInterval * float64(24*time.Hour))) +} - cardData.Lapses = lastLapses + 1 - cardData.Reps = 1 +func (w *Weights) initDS(s *SchedulingCards) { + s.Again.Difficulty = w.initDifficulty(Again) + s.Again.Stability = w.initStability(Again) + s.Hard.Difficulty = w.initDifficulty(Hard) + s.Hard.Stability = w.initStability(Hard) + s.Good.Difficulty = w.initDifficulty(Good) + s.Good.Stability = w.initStability(Good) + s.Easy.Difficulty = w.initDifficulty(Easy) + s.Easy.Stability = w.initStability(Easy) +} - } else { //grade == 1 || grade == 2 - cardData.Stability = lastStability * (1 + globalData.IncreaseFactor*math.Pow(cardData.Difficulty, globalData.DifficultyDecay)*math.Pow(lastStability, globalData.StabilityDecay)*(math.Exp(1-cardData.Retrievability)-1)) +func (w *Weights) nextDS(s *SchedulingCards, lastD float64, lastS float64, retrievability float64) { + s.Again.Difficulty = w.nextDifficulty(lastD, Again) + s.Again.Stability = w.nextForgetStability(s.Again.Difficulty, lastS, retrievability) + s.Hard.Difficulty = w.nextDifficulty(lastD, Hard) + s.Hard.Stability = w.nextRecallStability(s.Hard.Difficulty, lastS, retrievability) + s.Good.Difficulty = w.nextDifficulty(lastD, Good) + s.Good.Stability = w.nextRecallStability(s.Good.Difficulty, lastS, retrievability) + s.Easy.Difficulty = w.nextDifficulty(lastD, Easy) + s.Easy.Stability = w.nextRecallStability(s.Easy.Difficulty, lastS, retrievability) +} - if lastReps > 1 { - globalData.TotalDiff = globalData.TotalDiff + 1 - cardData.Retrievability - } +func (w *Weights) initStability(r Rating) float64 { + return math.Max(w[0]+w[1]*float64(r), 0.1) +} +func (w *Weights) initDifficulty(r Rating) float64 { + return constrainDifficulty(w[2] + w[3]*float64(r-2)) +} - cardData.Lapses = lastLapses - cardData.Reps = lastReps + 1 - } +func constrainDifficulty(d float64) float64 { + return math.Min(math.Max(d, 1), 10) +} - globalData.TotalCase++ - globalData.TotalReview++ +func (w *Weights) nextInterval(s float64) float64 { + newInterval := s * intervalModifier + return math.Max(math.Min(math.Round(newInterval), maximumInterval), 1) +} - addDay := math.Round(cardData.Stability * math.Log(globalData.RequestRetention) / math.Log(0.9)) - cardData.Due = now.Add(time.Duration(addDay * float64(24*time.Hour))) +func (w *Weights) nextDifficulty(d float64, r Rating) float64 { + nextD := d + w[4]*float64(r-2) + return constrainDifficulty(w.meanReversion(w[2], nextD)) +} - // Adaptive globalData.defaultDifficulty - if globalData.TotalCase > 100 { - globalData.DefaultDifficulty = 1.0/math.Pow(float64(globalData.TotalReview), 0.3)*math.Pow(math.Log(globalData.RequestRetention)/math.Max(math.Log(globalData.RequestRetention+globalData.TotalDiff/float64(globalData.TotalCase)), 0), 1/globalData.DifficultyDecay)*5 + (1-1/math.Pow(float64(globalData.TotalReview), 0.3))*globalData.DefaultDifficulty +func (w *Weights) meanReversion(init float64, current float64) float64 { + return w[5]*init + (1-w[5])*current +} - globalData.TotalDiff = 0 - globalData.TotalCase = 0 - } +func (w *Weights) nextRecallStability(d float64, s float64, r float64) float64 { + return s * (1 + math.Exp(w[6])* + (11-d)* + math.Pow(s, w[7])* + (math.Exp((1-r)*w[8])-1)) +} - // Adaptive globalData.defaultStability - if lastReps == 1 && lastLapses == 0 { - retrievability := uint64(0) - if grade > GradeForgetting { - retrievability = 1 - } - globalData.StabilityDataArray = append(globalData.StabilityDataArray, &StabilityData{ - Interval: cardData.Interval, - Retrievability: retrievability, - }) - - if len(globalData.StabilityDataArray) > 0 && len(globalData.StabilityDataArray)%50 == 0 { - intervalSetArray := set.New[uint64]() - - sumRI2S := float64(0) - sumI2S := float64(0) - - for s := 0; s < len(globalData.StabilityDataArray); s++ { - ivl := globalData.StabilityDataArray[s].Interval - - if !intervalSetArray.Has(ivl) { - intervalSetArray.Add(ivl) - - retrievabilitySum := uint64(0) - currentCount := 0 - for _, fi := range globalData.StabilityDataArray { - if fi.Interval == ivl { - retrievabilitySum += fi.Retrievability - currentCount++ - } - } - - if retrievabilitySum > 0 { - sumRI2S = sumRI2S + float64(ivl)*math.Log(float64(retrievabilitySum)/float64(currentCount))*float64(currentCount) - sumI2S = sumI2S + float64(ivl*ivl)*float64(currentCount) - } - } - - } - - globalData.DefaultStability = (math.Max(math.Log(0.9)/(sumRI2S/sumI2S), 0.1) + globalData.DefaultStability) / 2 - } - } +func (w *Weights) nextForgetStability(d float64, s float64, r float64) float64 { + return w[9] * math.Pow(d, w[10]) * math.Pow( + s, w[11]) * math.Exp((1-r)*w[12]) } diff --git a/fsrs_test.go b/fsrs_test.go new file mode 100644 index 0000000..ec53423 --- /dev/null +++ b/fsrs_test.go @@ -0,0 +1,37 @@ +package fsrs + +import ( + "encoding/json" + "testing" + "time" +) + +func TestRepeat(t *testing.T) { + w := DefaultWeights() + card := Card{ + Due: time.Now(), + Stability: 0, + Difficulty: 0, + ElapsedDays: 0, + ScheduledDays: 0, + Reps: 0, + Lapses: 0, + Leeched: false, + State: New, + LastReview: time.Now(), + ReviewLogs: []*ReviewLog{}, + } + schedulingCards := w.Repeat(&card, time.Now()) + schedule, _ := json.Marshal(schedulingCards) + t.Logf(string(schedule)) + + card = schedulingCards.Good + schedulingCards = w.Repeat(&card, time.Now()) + schedule, _ = json.Marshal(schedulingCards) + t.Logf(string(schedule)) + + card = schedulingCards.Good + schedulingCards = w.Repeat(&card, time.Now().Add(time.Duration(10*float64(24*time.Hour)))) + schedule, _ = json.Marshal(schedulingCards) + t.Logf(string(schedule)) +} diff --git a/go.mod b/go.mod index aa2b3b6..b2605df 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,5 @@ module github.com/open-spaced-repetition/go-fsrs -go 1.18 -require github.com/ImSingee/go-ex v0.4.10 + +go 1.19 diff --git a/models.go b/models.go index be6b570..794507d 100644 --- a/models.go +++ b/models.go @@ -1,69 +1,78 @@ package fsrs -import "time" +import ( + "time" +) -type GlobalData struct { - DifficultyDecay float64 `json:"difficultyDecay"` - StabilityDecay float64 `json:"stabilityDecay"` - IncreaseFactor float64 `json:"increaseFactor"` - RequestRetention float64 `json:"requestRetention"` - TotalCase uint64 `json:"totalCase"` - TotalDiff float64 `json:"totalDiff"` - TotalReview uint64 `json:"totalReview"` - DefaultDifficulty float64 `json:"defaultDifficulty"` - DefaultStability float64 `json:"defaultStability"` - StabilityDataArray []*StabilityData `json:"stabilityDataArray"` -} +type Weights [13]float64 -type StabilityData struct { - Interval uint64 - Retrievability uint64 +type Card struct { + Due time.Time `json:"Due"` + Stability float64 `json:"Stability"` + Difficulty float64 `json:"Difficulty"` + ElapsedDays uint64 `json:"ElapsedDays"` + ScheduledDays uint64 `json:"ScheduledDays"` + Reps uint64 `json:"Reps"` + Lapses uint64 `json:"Lapses"` + Leeched bool `json:"Leeched"` + State State `json:"State"` + LastReview time.Time `json:"LastReview"` + ReviewLogs []*ReviewLog `json:"ReviewLogs"` } -type CardData struct { - CardDataItem - - History []*CardDataItem +type ReviewLog struct { + Rating Rating + ScheduledDays uint64 + ElapsedDays uint64 + Reivew time.Time } -type CardDataItem struct { - Due time.Time - Interval uint64 // 上次复习间隔(单位为天) - Difficulty float64 - Stability float64 - Retrievability float64 - LastGrade Grade // 上次得分 - Review time.Time - Reps uint64 - Lapses uint64 +type SchedulingCards struct { + Again Card + Hard Card + Good Card + Easy Card } -func (item *CardDataItem) Copy() CardDataItem { - return *item +func (s *SchedulingCards) init(card *Card) { + s.Again = *card + s.Hard = *card + s.Good = *card + s.Easy = *card } -type Grade int8 +type Rating int8 const ( - GradeForgetting Grade = iota - GradeRemembered - GradeEasy - - GradeNewCard Grade = -1 + Again Rating = iota + Hard + Good + Easy ) -// DefaultGlobalData returns the default values of GlobalData -func DefaultGlobalData() GlobalData { - return GlobalData{ - DifficultyDecay: -0.7, - StabilityDecay: 0.2, - IncreaseFactor: 60, - RequestRetention: 0.9, - TotalCase: 0, - TotalDiff: 0, - TotalReview: 0, - DefaultDifficulty: 5, - DefaultStability: 2, - StabilityDataArray: nil, +func (s Rating) String() string { + switch s { + case Again: + return "Again" + case Hard: + return "Hard" + case Good: + return "Good" + case Easy: + return "Easy" } + return "unknown" +} + +type State int8 + +const ( + New State = iota + Learning + Review + Relearning +) + +func DefaultWeights() Weights { + return Weights{1, 1, 5, -0.5, -0.5, 0.2, 1.4, -0.12, 0.8, 2, -0.2, 0.2, 1} } From 121ccbc660422a98bb03de123207430e0f5f9d53 Mon Sep 17 00:00:00 2001 From: Jarrett Ye Date: Sat, 26 Nov 2022 19:55:08 +0800 Subject: [PATCH 2/7] Feat/review logs --- fsrs.go | 91 +++++++++++++++++++++++++++++++++++++++------------- fsrs_test.go | 39 +++++++++++++--------- models.go | 25 ++++++++++++--- 3 files changed, 112 insertions(+), 43 deletions(-) diff --git a/fsrs.go b/fsrs.go index 8dfd5ff..ea81c01 100644 --- a/fsrs.go +++ b/fsrs.go @@ -13,41 +13,46 @@ const hardFactor = 1.2 var intervalModifier = math.Log(requestRetention) / math.Log(0.9) func (w *Weights) Repeat(card *Card, now time.Time) *SchedulingCards { - - schedulingCards := new(SchedulingCards) - schedulingCards.init(card) - schedulingCards.updateState(card.State) + if card.State == New { + card.ElapsedDays = 0 + } else { + card.ElapsedDays = uint64(math.Round(float64(now.Sub(card.LastReview) / time.Hour / 24))) + } + card.LastReview = now + card.Reps += 1 + s := new(SchedulingCards) + s.init(card) + s.updateState(card.State) switch card.State { case New: - w.initDS(schedulingCards) + w.initDS(s) - easyInterval := w.nextInterval(schedulingCards.Easy.Stability * easyBonus) - schedulingCards.Easy.ScheduledDays = uint64(easyInterval) - schedulingCards.Easy.Due = now.Add(time.Duration(float64(easyInterval) * float64(24*time.Hour))) - return schedulingCards + easyInterval := w.nextInterval(s.Easy.Stability * easyBonus) + s.Easy.ScheduledDays = uint64(easyInterval) + s.Easy.Due = now.Add(time.Duration(float64(easyInterval) * float64(24*time.Hour))) case Learning, Relearning: - hardInterval := w.nextInterval(schedulingCards.Hard.Stability) - goodInterval := math.Max(w.nextInterval(schedulingCards.Good.Stability), hardInterval+1) - easyInterval := math.Max(w.nextInterval(schedulingCards.Easy.Stability*easyBonus), goodInterval+1) + hardInterval := w.nextInterval(s.Hard.Stability) + goodInterval := math.Max(w.nextInterval(s.Good.Stability), hardInterval+1) + easyInterval := math.Max(w.nextInterval(s.Easy.Stability*easyBonus), goodInterval+1) - schedulingCards.schedule(now, hardInterval, goodInterval, easyInterval) - return schedulingCards + s.schedule(now, hardInterval, goodInterval, easyInterval) case Review: - interval := float64(now.Sub(card.LastReview)/time.Hour/24 + 1.0) + interval := float64(card.ElapsedDays) lastD := card.Difficulty lastS := card.Stability retrievability := math.Exp(math.Log(0.9) * interval / lastS) - w.nextDS(schedulingCards, lastD, lastS, retrievability) + w.nextDS(s, lastD, lastS, retrievability) hardInterval := w.nextInterval(lastS * hardFactor) - goodInterval := math.Max(w.nextInterval(schedulingCards.Good.Stability), hardInterval+1) - easyInterval := math.Max(w.nextInterval(schedulingCards.Easy.Stability*easyBonus), goodInterval+1) - schedulingCards.schedule(now, hardInterval, goodInterval, easyInterval) - - return schedulingCards + goodInterval := w.nextInterval(s.Good.Stability) + hardInterval = math.Min(hardInterval, goodInterval) + goodInterval = math.Max(goodInterval, hardInterval+1) + easyInterval := math.Max(w.nextInterval(s.Easy.Stability*easyBonus), goodInterval+1) + s.schedule(now, hardInterval, goodInterval, easyInterval) } - return schedulingCards + s.recordLog(card.State, now) + return s } func (s *SchedulingCards) updateState(state State) { @@ -57,6 +62,7 @@ func (s *SchedulingCards) updateState(state State) { s.Hard.State = Learning s.Good.State = Learning s.Easy.State = Review + s.Again.Lapses += 1 case Learning, Relearning: s.Again.State = state s.Hard.State = Review @@ -67,18 +73,59 @@ func (s *SchedulingCards) updateState(state State) { s.Hard.State = Review s.Good.State = Review s.Easy.State = Review + s.Again.Lapses += 1 } } func (s *SchedulingCards) schedule(now time.Time, hardInterval float64, goodInterval float64, easyInterval float64) { + s.Again.ScheduledDays = 0 s.Hard.ScheduledDays = uint64(hardInterval) s.Good.ScheduledDays = uint64(goodInterval) s.Easy.ScheduledDays = uint64(easyInterval) + s.Again.Due = now s.Hard.Due = now.Add(time.Duration(hardInterval * float64(24*time.Hour))) s.Good.Due = now.Add(time.Duration(goodInterval * float64(24*time.Hour))) s.Easy.Due = now.Add(time.Duration(easyInterval * float64(24*time.Hour))) } +func (s *SchedulingCards) recordLog(state State, now time.Time) { + s.Again.ReviewLogs = append(s.Again.ReviewLogs, + &ReviewLog{ + Rating: Again, + ScheduledDays: s.Again.ScheduledDays, + ElapsedDays: s.Again.ElapsedDays, + Review: now, + State: state, + }) + + s.Hard.ReviewLogs = append(s.Hard.ReviewLogs, + &ReviewLog{ + Rating: Hard, + ScheduledDays: s.Hard.ScheduledDays, + ElapsedDays: s.Hard.ElapsedDays, + Review: now, + State: state, + }) + + s.Good.ReviewLogs = append(s.Good.ReviewLogs, + &ReviewLog{ + Rating: Good, + ScheduledDays: s.Good.ScheduledDays, + ElapsedDays: s.Good.ElapsedDays, + Review: now, + State: state, + }) + + s.Easy.ReviewLogs = append(s.Easy.ReviewLogs, + &ReviewLog{ + Rating: Easy, + ScheduledDays: s.Easy.ScheduledDays, + ElapsedDays: s.Easy.ElapsedDays, + Review: now, + State: state, + }) +} + func (w *Weights) initDS(s *SchedulingCards) { s.Again.Difficulty = w.initDifficulty(Again) s.Again.Stability = w.initStability(Again) diff --git a/fsrs_test.go b/fsrs_test.go index ec53423..1584c4a 100644 --- a/fsrs_test.go +++ b/fsrs_test.go @@ -8,30 +8,37 @@ import ( func TestRepeat(t *testing.T) { w := DefaultWeights() - card := Card{ - Due: time.Now(), - Stability: 0, - Difficulty: 0, - ElapsedDays: 0, - ScheduledDays: 0, - Reps: 0, - Lapses: 0, - Leeched: false, - State: New, - LastReview: time.Now(), - ReviewLogs: []*ReviewLog{}, - } - schedulingCards := w.Repeat(&card, time.Now()) + card := NewCard() + now := time.Now() + schedulingCards := w.Repeat(&card, now) schedule, _ := json.Marshal(schedulingCards) t.Logf(string(schedule)) card = schedulingCards.Good - schedulingCards = w.Repeat(&card, time.Now()) + interval := card.ScheduledDays + now = now.Add(time.Duration(float64(interval) * float64(24*time.Hour))) + schedulingCards = w.Repeat(&card, now) schedule, _ = json.Marshal(schedulingCards) t.Logf(string(schedule)) card = schedulingCards.Good - schedulingCards = w.Repeat(&card, time.Now().Add(time.Duration(10*float64(24*time.Hour)))) + interval = card.ScheduledDays + now = now.Add(time.Duration(float64(interval) * float64(24*time.Hour))) + schedulingCards = w.Repeat(&card, now) + schedule, _ = json.Marshal(schedulingCards) + t.Logf(string(schedule)) + + card = schedulingCards.Again + interval = card.ScheduledDays + now = now.Add(time.Duration(float64(interval) * float64(24*time.Hour))) + schedulingCards = w.Repeat(&card, now) + schedule, _ = json.Marshal(schedulingCards) + t.Logf(string(schedule)) + + card = schedulingCards.Good + interval = card.ScheduledDays + now = now.Add(time.Duration(float64(interval) * float64(24*time.Hour))) + schedulingCards = w.Repeat(&card, now) schedule, _ = json.Marshal(schedulingCards) t.Logf(string(schedule)) } diff --git a/models.go b/models.go index 794507d..8e20082 100644 --- a/models.go +++ b/models.go @@ -14,17 +14,32 @@ type Card struct { ScheduledDays uint64 `json:"ScheduledDays"` Reps uint64 `json:"Reps"` Lapses uint64 `json:"Lapses"` - Leeched bool `json:"Leeched"` State State `json:"State"` LastReview time.Time `json:"LastReview"` ReviewLogs []*ReviewLog `json:"ReviewLogs"` } +func NewCard() Card { + return Card{ + Due: time.Time{}, + Stability: 0, + Difficulty: 0, + ElapsedDays: 0, + ScheduledDays: 0, + Reps: 0, + Lapses: 0, + State: New, + LastReview: time.Time{}, + ReviewLogs: []*ReviewLog{}, + } +} + type ReviewLog struct { - Rating Rating - ScheduledDays uint64 - ElapsedDays uint64 - Reivew time.Time + Rating Rating `json:"Rating"` + ScheduledDays uint64 `json:"ScheduledDays"` + ElapsedDays uint64 `json:"ElapsedDays"` + Review time.Time `json:"Review"` + State State `json:"State"` } type SchedulingCards struct { From c089335c9aaa9b9f73c153f09c58dc7d344f90e7 Mon Sep 17 00:00:00 2001 From: Jarrett Ye Date: Sat, 26 Nov 2022 20:02:37 +0800 Subject: [PATCH 3/7] remove unused pkg --- go.mod | 2 -- go.sum | 2 -- 2 files changed, 4 deletions(-) delete mode 100644 go.sum diff --git a/go.mod b/go.mod index b2605df..39a9ed2 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,3 @@ module github.com/open-spaced-repetition/go-fsrs - - go 1.19 diff --git a/go.sum b/go.sum deleted file mode 100644 index cfd93f1..0000000 --- a/go.sum +++ /dev/null @@ -1,2 +0,0 @@ -github.com/ImSingee/go-ex v0.4.10 h1:ZJA6J8NbU7Bnwu8uTAEMBxZ7n7SzCPCztzJHi+ZVCqs= -github.com/ImSingee/go-ex v0.4.10/go.mod h1:CNc3Fqk9GkQfm/1x53vGnQ0BEHH+52siqBJFkVwdy8U= From c89faba55d6161845a6cfcad1962e29a9558960d Mon Sep 17 00:00:00 2001 From: Jarrett Ye Date: Sun, 27 Nov 2022 13:02:02 +0800 Subject: [PATCH 4/7] Add/CardId & LogId Fix/Short-term schedule Feat/Custom Parameters --- fsrs.go | 122 ++++++++++++++++++++++++++------------------------- fsrs_test.go | 24 +++++----- models.go | 32 +++++++++++--- 3 files changed, 100 insertions(+), 78 deletions(-) diff --git a/fsrs.go b/fsrs.go index ea81c01..be5531f 100644 --- a/fsrs.go +++ b/fsrs.go @@ -5,14 +5,7 @@ import ( "time" ) -const requestRetention = 0.9 -const maximumInterval = 36500.0 -const easyBonus = 1.3 -const hardFactor = 1.2 - -var intervalModifier = math.Log(requestRetention) / math.Log(0.9) - -func (w *Weights) Repeat(card *Card, now time.Time) *SchedulingCards { +func (p *Parameters) Repeat(card *Card, now time.Time) *SchedulingCards { if card.State == New { card.ElapsedDays = 0 } else { @@ -26,15 +19,18 @@ func (w *Weights) Repeat(card *Card, now time.Time) *SchedulingCards { switch card.State { case New: - w.initDS(s) + p.initDS(s) - easyInterval := w.nextInterval(s.Easy.Stability * easyBonus) + s.Again.Due = now.Add(1 * time.Minute) + s.Hard.Due = now.Add(5 * time.Minute) + s.Good.Due = now.Add(10 * time.Minute) + easyInterval := p.nextInterval(s.Easy.Stability * p.EasyBonus) s.Easy.ScheduledDays = uint64(easyInterval) - s.Easy.Due = now.Add(time.Duration(float64(easyInterval) * float64(24*time.Hour))) + s.Easy.Due = now.Add(time.Duration(easyInterval) * 24 * time.Hour) case Learning, Relearning: - hardInterval := w.nextInterval(s.Hard.Stability) - goodInterval := math.Max(w.nextInterval(s.Good.Stability), hardInterval+1) - easyInterval := math.Max(w.nextInterval(s.Easy.Stability*easyBonus), goodInterval+1) + hardInterval := p.nextInterval(s.Hard.Stability) + goodInterval := math.Max(p.nextInterval(s.Good.Stability), hardInterval+1) + easyInterval := math.Max(p.nextInterval(s.Easy.Stability*p.EasyBonus), goodInterval+1) s.schedule(now, hardInterval, goodInterval, easyInterval) case Review: @@ -42,13 +38,13 @@ func (w *Weights) Repeat(card *Card, now time.Time) *SchedulingCards { lastD := card.Difficulty lastS := card.Stability retrievability := math.Exp(math.Log(0.9) * interval / lastS) - w.nextDS(s, lastD, lastS, retrievability) + p.nextDS(s, lastD, lastS, retrievability) - hardInterval := w.nextInterval(lastS * hardFactor) - goodInterval := w.nextInterval(s.Good.Stability) + hardInterval := p.nextInterval(lastS * p.HardFactor) + goodInterval := p.nextInterval(s.Good.Stability) hardInterval = math.Min(hardInterval, goodInterval) goodInterval = math.Max(goodInterval, hardInterval+1) - easyInterval := math.Max(w.nextInterval(s.Easy.Stability*easyBonus), goodInterval+1) + easyInterval := math.Max(p.nextInterval(s.Easy.Stability*p.EasyBonus), goodInterval+1) s.schedule(now, hardInterval, goodInterval, easyInterval) } s.recordLog(card.State, now) @@ -82,15 +78,17 @@ func (s *SchedulingCards) schedule(now time.Time, hardInterval float64, goodInte s.Hard.ScheduledDays = uint64(hardInterval) s.Good.ScheduledDays = uint64(goodInterval) s.Easy.ScheduledDays = uint64(easyInterval) - s.Again.Due = now - s.Hard.Due = now.Add(time.Duration(hardInterval * float64(24*time.Hour))) - s.Good.Due = now.Add(time.Duration(goodInterval * float64(24*time.Hour))) - s.Easy.Due = now.Add(time.Duration(easyInterval * float64(24*time.Hour))) + s.Again.Due = now.Add(5 * time.Minute) + s.Hard.Due = now.Add(time.Duration(hardInterval) * 24 * time.Hour) + s.Good.Due = now.Add(time.Duration(goodInterval) * 24 * time.Hour) + s.Easy.Due = now.Add(time.Duration(easyInterval) * 24 * time.Hour) } func (s *SchedulingCards) recordLog(state State, now time.Time) { s.Again.ReviewLogs = append(s.Again.ReviewLogs, &ReviewLog{ + Id: now.UnixNano(), + CardId: s.Again.Id, Rating: Again, ScheduledDays: s.Again.ScheduledDays, ElapsedDays: s.Again.ElapsedDays, @@ -100,6 +98,8 @@ func (s *SchedulingCards) recordLog(state State, now time.Time) { s.Hard.ReviewLogs = append(s.Hard.ReviewLogs, &ReviewLog{ + Id: now.UnixNano(), + CardId: s.Hard.Id, Rating: Hard, ScheduledDays: s.Hard.ScheduledDays, ElapsedDays: s.Hard.ElapsedDays, @@ -109,6 +109,8 @@ func (s *SchedulingCards) recordLog(state State, now time.Time) { s.Good.ReviewLogs = append(s.Good.ReviewLogs, &ReviewLog{ + Id: now.UnixNano(), + CardId: s.Good.Id, Rating: Good, ScheduledDays: s.Good.ScheduledDays, ElapsedDays: s.Good.ElapsedDays, @@ -118,6 +120,8 @@ func (s *SchedulingCards) recordLog(state State, now time.Time) { s.Easy.ReviewLogs = append(s.Easy.ReviewLogs, &ReviewLog{ + Id: now.UnixNano(), + CardId: s.Easy.Id, Rating: Easy, ScheduledDays: s.Easy.ScheduledDays, ElapsedDays: s.Easy.ElapsedDays, @@ -126,61 +130,61 @@ func (s *SchedulingCards) recordLog(state State, now time.Time) { }) } -func (w *Weights) initDS(s *SchedulingCards) { - s.Again.Difficulty = w.initDifficulty(Again) - s.Again.Stability = w.initStability(Again) - s.Hard.Difficulty = w.initDifficulty(Hard) - s.Hard.Stability = w.initStability(Hard) - s.Good.Difficulty = w.initDifficulty(Good) - s.Good.Stability = w.initStability(Good) - s.Easy.Difficulty = w.initDifficulty(Easy) - s.Easy.Stability = w.initStability(Easy) +func (p *Parameters) initDS(s *SchedulingCards) { + s.Again.Difficulty = p.initDifficulty(Again) + s.Again.Stability = p.initStability(Again) + s.Hard.Difficulty = p.initDifficulty(Hard) + s.Hard.Stability = p.initStability(Hard) + s.Good.Difficulty = p.initDifficulty(Good) + s.Good.Stability = p.initStability(Good) + s.Easy.Difficulty = p.initDifficulty(Easy) + s.Easy.Stability = p.initStability(Easy) } -func (w *Weights) nextDS(s *SchedulingCards, lastD float64, lastS float64, retrievability float64) { - s.Again.Difficulty = w.nextDifficulty(lastD, Again) - s.Again.Stability = w.nextForgetStability(s.Again.Difficulty, lastS, retrievability) - s.Hard.Difficulty = w.nextDifficulty(lastD, Hard) - s.Hard.Stability = w.nextRecallStability(s.Hard.Difficulty, lastS, retrievability) - s.Good.Difficulty = w.nextDifficulty(lastD, Good) - s.Good.Stability = w.nextRecallStability(s.Good.Difficulty, lastS, retrievability) - s.Easy.Difficulty = w.nextDifficulty(lastD, Easy) - s.Easy.Stability = w.nextRecallStability(s.Easy.Difficulty, lastS, retrievability) +func (p *Parameters) nextDS(s *SchedulingCards, lastD float64, lastS float64, retrievability float64) { + s.Again.Difficulty = p.nextDifficulty(lastD, Again) + s.Again.Stability = p.nextForgetStability(s.Again.Difficulty, lastS, retrievability) + s.Hard.Difficulty = p.nextDifficulty(lastD, Hard) + s.Hard.Stability = p.nextRecallStability(s.Hard.Difficulty, lastS, retrievability) + s.Good.Difficulty = p.nextDifficulty(lastD, Good) + s.Good.Stability = p.nextRecallStability(s.Good.Difficulty, lastS, retrievability) + s.Easy.Difficulty = p.nextDifficulty(lastD, Easy) + s.Easy.Stability = p.nextRecallStability(s.Easy.Difficulty, lastS, retrievability) } -func (w *Weights) initStability(r Rating) float64 { - return math.Max(w[0]+w[1]*float64(r), 0.1) +func (p *Parameters) initStability(r Rating) float64 { + return math.Max(p.W[0]+p.W[1]*float64(r), 0.1) } -func (w *Weights) initDifficulty(r Rating) float64 { - return constrainDifficulty(w[2] + w[3]*float64(r-2)) +func (p *Parameters) initDifficulty(r Rating) float64 { + return constrainDifficulty(p.W[2] + p.W[3]*float64(r-2)) } func constrainDifficulty(d float64) float64 { return math.Min(math.Max(d, 1), 10) } -func (w *Weights) nextInterval(s float64) float64 { - newInterval := s * intervalModifier - return math.Max(math.Min(math.Round(newInterval), maximumInterval), 1) +func (p *Parameters) nextInterval(s float64) float64 { + newInterval := s * math.Log(p.RequestRetention) / math.Log(0.9) + return math.Max(math.Min(math.Round(newInterval), p.MaximumInterval), 1) } -func (w *Weights) nextDifficulty(d float64, r Rating) float64 { - nextD := d + w[4]*float64(r-2) - return constrainDifficulty(w.meanReversion(w[2], nextD)) +func (p *Parameters) nextDifficulty(d float64, r Rating) float64 { + nextD := d + p.W[4]*float64(r-2) + return constrainDifficulty(p.meanReversion(p.W[2], nextD)) } -func (w *Weights) meanReversion(init float64, current float64) float64 { - return w[5]*init + (1-w[5])*current +func (p *Parameters) meanReversion(init float64, current float64) float64 { + return p.W[5]*init + (1-p.W[5])*current } -func (w *Weights) nextRecallStability(d float64, s float64, r float64) float64 { - return s * (1 + math.Exp(w[6])* +func (p *Parameters) nextRecallStability(d float64, s float64, r float64) float64 { + return s * (1 + math.Exp(p.W[6])* (11-d)* - math.Pow(s, w[7])* - (math.Exp((1-r)*w[8])-1)) + math.Pow(s, p.W[7])* + (math.Exp((1-r)*p.W[8])-1)) } -func (w *Weights) nextForgetStability(d float64, s float64, r float64) float64 { - return w[9] * math.Pow(d, w[10]) * math.Pow( - s, w[11]) * math.Exp((1-r)*w[12]) +func (p *Parameters) nextForgetStability(d float64, s float64, r float64) float64 { + return p.W[9] * math.Pow(d, p.W[10]) * math.Pow( + s, p.W[11]) * math.Exp((1-r)*p.W[12]) } diff --git a/fsrs_test.go b/fsrs_test.go index 1584c4a..03c7e94 100644 --- a/fsrs_test.go +++ b/fsrs_test.go @@ -7,38 +7,34 @@ import ( ) func TestRepeat(t *testing.T) { - w := DefaultWeights() + p := DefaultParam() card := NewCard() now := time.Now() - schedulingCards := w.Repeat(&card, now) + schedulingCards := p.Repeat(&card, now) schedule, _ := json.Marshal(schedulingCards) t.Logf(string(schedule)) card = schedulingCards.Good - interval := card.ScheduledDays - now = now.Add(time.Duration(float64(interval) * float64(24*time.Hour))) - schedulingCards = w.Repeat(&card, now) + now = card.Due + schedulingCards = p.Repeat(&card, now) schedule, _ = json.Marshal(schedulingCards) t.Logf(string(schedule)) card = schedulingCards.Good - interval = card.ScheduledDays - now = now.Add(time.Duration(float64(interval) * float64(24*time.Hour))) - schedulingCards = w.Repeat(&card, now) + now = card.Due + schedulingCards = p.Repeat(&card, now) schedule, _ = json.Marshal(schedulingCards) t.Logf(string(schedule)) card = schedulingCards.Again - interval = card.ScheduledDays - now = now.Add(time.Duration(float64(interval) * float64(24*time.Hour))) - schedulingCards = w.Repeat(&card, now) + now = card.Due + schedulingCards = p.Repeat(&card, now) schedule, _ = json.Marshal(schedulingCards) t.Logf(string(schedule)) card = schedulingCards.Good - interval = card.ScheduledDays - now = now.Add(time.Duration(float64(interval) * float64(24*time.Hour))) - schedulingCards = w.Repeat(&card, now) + now = card.Due + schedulingCards = p.Repeat(&card, now) schedule, _ = json.Marshal(schedulingCards) t.Logf(string(schedule)) } diff --git a/models.go b/models.go index 8e20082..f350f0e 100644 --- a/models.go +++ b/models.go @@ -4,9 +4,32 @@ import ( "time" ) -type Weights [13]float64 +type weights [13]float64 + +func defaultWeights() weights { + return weights{1, 1, 5, -0.5, -0.5, 0.2, 1.4, -0.12, 0.8, 2, -0.2, 0.2, 1} +} + +type Parameters struct { + RequestRetention float64 + MaximumInterval float64 + EasyBonus float64 + HardFactor float64 + W weights +} + +func DefaultParam() Parameters { + return Parameters{ + RequestRetention: 0.9, + MaximumInterval: 36500, + EasyBonus: 1.3, + HardFactor: 1.2, + W: defaultWeights(), + } +} type Card struct { + Id int64 `json:"Id"` Due time.Time `json:"Due"` Stability float64 `json:"Stability"` Difficulty float64 `json:"Difficulty"` @@ -21,6 +44,7 @@ type Card struct { func NewCard() Card { return Card{ + Id: time.Now().UnixNano(), Due: time.Time{}, Stability: 0, Difficulty: 0, @@ -35,6 +59,8 @@ func NewCard() Card { } type ReviewLog struct { + Id int64 `json:"Id"` + CardId int64 `json:"CardId"` Rating Rating `json:"Rating"` ScheduledDays uint64 `json:"ScheduledDays"` ElapsedDays uint64 `json:"ElapsedDays"` @@ -87,7 +113,3 @@ const ( Review Relearning ) - -func DefaultWeights() Weights { - return Weights{1, 1, 5, -0.5, -0.5, 0.2, 1.4, -0.12, 0.8, 2, -0.2, 0.2, 1} -} From 16135f455c859d37fafeb5b74567b468ff1f6f0c Mon Sep 17 00:00:00 2001 From: Jarrett Ye Date: Tue, 29 Nov 2022 16:31:04 +0800 Subject: [PATCH 5/7] separate review log from card --- fsrs.go | 57 ++++++++++++++++++++++++---------------------------- fsrs_test.go | 10 ++++----- models.go | 27 ++++++++++++++----------- 3 files changed, 46 insertions(+), 48 deletions(-) diff --git a/fsrs.go b/fsrs.go index be5531f..7705cc2 100644 --- a/fsrs.go +++ b/fsrs.go @@ -5,7 +5,7 @@ import ( "time" ) -func (p *Parameters) Repeat(card *Card, now time.Time) *SchedulingCards { +func (p *Parameters) Repeat(card *Card, now time.Time) map[Rating]SchedulingInfo { if card.State == New { card.ElapsedDays = 0 } else { @@ -47,8 +47,7 @@ func (p *Parameters) Repeat(card *Card, now time.Time) *SchedulingCards { easyInterval := math.Max(p.nextInterval(s.Easy.Stability*p.EasyBonus), goodInterval+1) s.schedule(now, hardInterval, goodInterval, easyInterval) } - s.recordLog(card.State, now) - return s + return s.recordLog(*card, now) } func (s *SchedulingCards) updateState(state State) { @@ -84,50 +83,46 @@ func (s *SchedulingCards) schedule(now time.Time, hardInterval float64, goodInte s.Easy.Due = now.Add(time.Duration(easyInterval) * 24 * time.Hour) } -func (s *SchedulingCards) recordLog(state State, now time.Time) { - s.Again.ReviewLogs = append(s.Again.ReviewLogs, - &ReviewLog{ +func (s *SchedulingCards) recordLog(card Card, now time.Time) map[Rating]SchedulingInfo { + m := map[Rating]SchedulingInfo{ + Again: {s.Again, ReviewLog{ Id: now.UnixNano(), - CardId: s.Again.Id, + CardId: card.Id, Rating: Again, ScheduledDays: s.Again.ScheduledDays, - ElapsedDays: s.Again.ElapsedDays, + ElapsedDays: card.ElapsedDays, Review: now, - State: state, - }) - - s.Hard.ReviewLogs = append(s.Hard.ReviewLogs, - &ReviewLog{ + State: card.State, + }}, + Hard: {s.Hard, ReviewLog{ Id: now.UnixNano(), - CardId: s.Hard.Id, + CardId: card.Id, Rating: Hard, ScheduledDays: s.Hard.ScheduledDays, - ElapsedDays: s.Hard.ElapsedDays, + ElapsedDays: card.ElapsedDays, Review: now, - State: state, - }) - - s.Good.ReviewLogs = append(s.Good.ReviewLogs, - &ReviewLog{ + State: card.State, + }}, + Good: {s.Good, ReviewLog{ Id: now.UnixNano(), - CardId: s.Good.Id, + CardId: card.Id, Rating: Good, ScheduledDays: s.Good.ScheduledDays, - ElapsedDays: s.Good.ElapsedDays, + ElapsedDays: card.ElapsedDays, Review: now, - State: state, - }) - - s.Easy.ReviewLogs = append(s.Easy.ReviewLogs, - &ReviewLog{ + State: card.State, + }}, + Easy: {s.Easy, ReviewLog{ Id: now.UnixNano(), - CardId: s.Easy.Id, + CardId: card.Id, Rating: Easy, ScheduledDays: s.Easy.ScheduledDays, - ElapsedDays: s.Easy.ElapsedDays, + ElapsedDays: card.ElapsedDays, Review: now, - State: state, - }) + State: card.State, + }}, + } + return m } func (p *Parameters) initDS(s *SchedulingCards) { diff --git a/fsrs_test.go b/fsrs_test.go index 03c7e94..4ab2e35 100644 --- a/fsrs_test.go +++ b/fsrs_test.go @@ -9,30 +9,30 @@ import ( func TestRepeat(t *testing.T) { p := DefaultParam() card := NewCard() - now := time.Now() + now := time.Date(2022, 11, 29, 12, 30, 0, 0, time.UTC) schedulingCards := p.Repeat(&card, now) schedule, _ := json.Marshal(schedulingCards) t.Logf(string(schedule)) - card = schedulingCards.Good + card = schedulingCards[Good].Card now = card.Due schedulingCards = p.Repeat(&card, now) schedule, _ = json.Marshal(schedulingCards) t.Logf(string(schedule)) - card = schedulingCards.Good + card = schedulingCards[Good].Card now = card.Due schedulingCards = p.Repeat(&card, now) schedule, _ = json.Marshal(schedulingCards) t.Logf(string(schedule)) - card = schedulingCards.Again + card = schedulingCards[Again].Card now = card.Due schedulingCards = p.Repeat(&card, now) schedule, _ = json.Marshal(schedulingCards) t.Logf(string(schedule)) - card = schedulingCards.Good + card = schedulingCards[Good].Card now = card.Due schedulingCards = p.Repeat(&card, now) schedule, _ = json.Marshal(schedulingCards) diff --git a/models.go b/models.go index f350f0e..7c47903 100644 --- a/models.go +++ b/models.go @@ -29,17 +29,16 @@ func DefaultParam() Parameters { } type Card struct { - Id int64 `json:"Id"` - Due time.Time `json:"Due"` - Stability float64 `json:"Stability"` - Difficulty float64 `json:"Difficulty"` - ElapsedDays uint64 `json:"ElapsedDays"` - ScheduledDays uint64 `json:"ScheduledDays"` - Reps uint64 `json:"Reps"` - Lapses uint64 `json:"Lapses"` - State State `json:"State"` - LastReview time.Time `json:"LastReview"` - ReviewLogs []*ReviewLog `json:"ReviewLogs"` + Id int64 `json:"Id"` + Due time.Time `json:"Due"` + Stability float64 `json:"Stability"` + Difficulty float64 `json:"Difficulty"` + ElapsedDays uint64 `json:"ElapsedDays"` + ScheduledDays uint64 `json:"ScheduledDays"` + Reps uint64 `json:"Reps"` + Lapses uint64 `json:"Lapses"` + State State `json:"State"` + LastReview time.Time `json:"LastReview"` } func NewCard() Card { @@ -54,7 +53,6 @@ func NewCard() Card { Lapses: 0, State: New, LastReview: time.Time{}, - ReviewLogs: []*ReviewLog{}, } } @@ -82,6 +80,11 @@ func (s *SchedulingCards) init(card *Card) { s.Easy = *card } +type SchedulingInfo struct { + Card Card + ReviewLog ReviewLog +} + type Rating int8 const ( From 6731dadd1a6c5df9bc957259f5613c5a25ad4c9a Mon Sep 17 00:00:00 2001 From: Jarrett Ye Date: Tue, 29 Nov 2022 21:41:32 +0800 Subject: [PATCH 6/7] Repeat input Card instead of *Card --- fsrs.go | 4 ++-- fsrs_test.go | 10 +++++----- models.go | 10 +++++----- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/fsrs.go b/fsrs.go index 7705cc2..bb8b91f 100644 --- a/fsrs.go +++ b/fsrs.go @@ -5,7 +5,7 @@ import ( "time" ) -func (p *Parameters) Repeat(card *Card, now time.Time) map[Rating]SchedulingInfo { +func (p *Parameters) Repeat(card Card, now time.Time) map[Rating]SchedulingInfo { if card.State == New { card.ElapsedDays = 0 } else { @@ -47,7 +47,7 @@ func (p *Parameters) Repeat(card *Card, now time.Time) map[Rating]SchedulingInfo easyInterval := math.Max(p.nextInterval(s.Easy.Stability*p.EasyBonus), goodInterval+1) s.schedule(now, hardInterval, goodInterval, easyInterval) } - return s.recordLog(*card, now) + return s.recordLog(card, now) } func (s *SchedulingCards) updateState(state State) { diff --git a/fsrs_test.go b/fsrs_test.go index 4ab2e35..57597c9 100644 --- a/fsrs_test.go +++ b/fsrs_test.go @@ -10,31 +10,31 @@ func TestRepeat(t *testing.T) { p := DefaultParam() card := NewCard() now := time.Date(2022, 11, 29, 12, 30, 0, 0, time.UTC) - schedulingCards := p.Repeat(&card, now) + schedulingCards := p.Repeat(card, now) schedule, _ := json.Marshal(schedulingCards) t.Logf(string(schedule)) card = schedulingCards[Good].Card now = card.Due - schedulingCards = p.Repeat(&card, now) + schedulingCards = p.Repeat(card, now) schedule, _ = json.Marshal(schedulingCards) t.Logf(string(schedule)) card = schedulingCards[Good].Card now = card.Due - schedulingCards = p.Repeat(&card, now) + schedulingCards = p.Repeat(card, now) schedule, _ = json.Marshal(schedulingCards) t.Logf(string(schedule)) card = schedulingCards[Again].Card now = card.Due - schedulingCards = p.Repeat(&card, now) + schedulingCards = p.Repeat(card, now) schedule, _ = json.Marshal(schedulingCards) t.Logf(string(schedule)) card = schedulingCards[Good].Card now = card.Due - schedulingCards = p.Repeat(&card, now) + schedulingCards = p.Repeat(card, now) schedule, _ = json.Marshal(schedulingCards) t.Logf(string(schedule)) } diff --git a/models.go b/models.go index 7c47903..8f36c53 100644 --- a/models.go +++ b/models.go @@ -73,11 +73,11 @@ type SchedulingCards struct { Easy Card } -func (s *SchedulingCards) init(card *Card) { - s.Again = *card - s.Hard = *card - s.Good = *card - s.Easy = *card +func (s *SchedulingCards) init(card Card) { + s.Again = card + s.Hard = card + s.Good = card + s.Easy = card } type SchedulingInfo struct { From c13057c08dcb135b81afc4d6d568512193925fa5 Mon Sep 17 00:00:00 2001 From: Jarrett Ye Date: Wed, 30 Nov 2022 09:25:40 +0800 Subject: [PATCH 7/7] remove CardID LogID --- fsrs.go | 8 -------- models.go | 4 ---- 2 files changed, 12 deletions(-) diff --git a/fsrs.go b/fsrs.go index bb8b91f..c9ea253 100644 --- a/fsrs.go +++ b/fsrs.go @@ -86,8 +86,6 @@ func (s *SchedulingCards) schedule(now time.Time, hardInterval float64, goodInte func (s *SchedulingCards) recordLog(card Card, now time.Time) map[Rating]SchedulingInfo { m := map[Rating]SchedulingInfo{ Again: {s.Again, ReviewLog{ - Id: now.UnixNano(), - CardId: card.Id, Rating: Again, ScheduledDays: s.Again.ScheduledDays, ElapsedDays: card.ElapsedDays, @@ -95,8 +93,6 @@ func (s *SchedulingCards) recordLog(card Card, now time.Time) map[Rating]Schedul State: card.State, }}, Hard: {s.Hard, ReviewLog{ - Id: now.UnixNano(), - CardId: card.Id, Rating: Hard, ScheduledDays: s.Hard.ScheduledDays, ElapsedDays: card.ElapsedDays, @@ -104,8 +100,6 @@ func (s *SchedulingCards) recordLog(card Card, now time.Time) map[Rating]Schedul State: card.State, }}, Good: {s.Good, ReviewLog{ - Id: now.UnixNano(), - CardId: card.Id, Rating: Good, ScheduledDays: s.Good.ScheduledDays, ElapsedDays: card.ElapsedDays, @@ -113,8 +107,6 @@ func (s *SchedulingCards) recordLog(card Card, now time.Time) map[Rating]Schedul State: card.State, }}, Easy: {s.Easy, ReviewLog{ - Id: now.UnixNano(), - CardId: card.Id, Rating: Easy, ScheduledDays: s.Easy.ScheduledDays, ElapsedDays: card.ElapsedDays, diff --git a/models.go b/models.go index 8f36c53..98c5378 100644 --- a/models.go +++ b/models.go @@ -29,7 +29,6 @@ func DefaultParam() Parameters { } type Card struct { - Id int64 `json:"Id"` Due time.Time `json:"Due"` Stability float64 `json:"Stability"` Difficulty float64 `json:"Difficulty"` @@ -43,7 +42,6 @@ type Card struct { func NewCard() Card { return Card{ - Id: time.Now().UnixNano(), Due: time.Time{}, Stability: 0, Difficulty: 0, @@ -57,8 +55,6 @@ func NewCard() Card { } type ReviewLog struct { - Id int64 `json:"Id"` - CardId int64 `json:"CardId"` Rating Rating `json:"Rating"` ScheduledDays uint64 `json:"ScheduledDays"` ElapsedDays uint64 `json:"ElapsedDays"`