From c0e4d68681024666dad3f8cdbd3a6a9fca73e5bf Mon Sep 17 00:00:00 2001 From: ishiko Date: Sat, 7 Sep 2024 20:28:33 +1000 Subject: [PATCH] Feat/Support iteration in the `repeat` (#121) * Feat/Support iteration in the `repeat` method * update test * update README * bump version to 4.2.0 --- README.md | 14 +++++--- README_CN.md | 12 ++++--- README_JA.md | 12 ++++--- __tests__/FSRSV5.test.ts | 13 ++------ __tests__/impl/abstract_scheduler.test.ts | 40 +++++++++++++++++++++++ __tests__/impl/basic_schduler.test.ts | 18 ++++++++++ package.json | 2 +- src/fsrs/abstract_schduler.ts | 16 ++++++--- src/fsrs/default.ts | 2 +- src/fsrs/fsrs.ts | 6 ++-- src/fsrs/types.ts | 7 ++-- 11 files changed, 108 insertions(+), 34 deletions(-) create mode 100644 __tests__/impl/abstract_scheduler.test.ts diff --git a/README.md b/README.md index 3ef551db..2e9d1be7 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ The workflow for TS-FSRS can be referenced from the following resources: # Usage -The ts-fsrs@3.x package requires Node.js version `16.0.0` or higher. Starting with `ts-fsrs@4.x`, the minimum required Node.js version is 18.0.0. +The `ts-fsrs@3.x` package requires Node.js version `16.0.0` or higher. Starting with `ts-fsrs@4.x`, the minimum required Node.js version is `18.0.0`. From version `3.5.6` onwards, ts-fsrs supports CommonJS, ESM, and UMD module systems. ``` @@ -32,15 +32,17 @@ bun install ts-fsrs ```typescript import {createEmptyCard, formatDate, fsrs, generatorParameters, Rating, Grades} from 'ts-fsrs'; -const params = generatorParameters({ enable_fuzz: true }); +const params = generatorParameters({ enable_fuzz: true, enable_short_term: false }); const f = fsrs(params); const card = createEmptyCard(new Date('2022-2-1 10:00:00'));// createEmptyCard(); const now = new Date('2022-2-2 10:00:00');// new Date(); const scheduling_cards = f.repeat(card, now); // console.log(scheduling_cards); -Grades.forEach(grade => { // [Rating.Again, Rating.Hard, Rating.Good, Rating.Easy] - const { log, card } = scheduling_cards[grade]; +for (const item of scheduling_cards) { + // grades = [Rating.Again, Rating.Hard, Rating.Good, Rating.Easy] + const grade = item.log.rating + const { log, card } = item; console.group(`${Rating[grade]}`); console.table({ [`card_${Rating[grade]}`]: { @@ -57,7 +59,7 @@ Grades.forEach(grade => { // [Rating.Again, Rating.Hard, Rating.Good, Rating.Eas }); console.groupEnd(); console.log('----------------------------------------------------------------'); -}); +} ``` More refer: @@ -105,6 +107,8 @@ import { let card: Card = createEmptyCard(); const f: FSRS = new FSRS(); // or const f: FSRS = fsrs(params); let scheduling_cards: RecordLog = f.repeat(card, new Date()); +// if you want to specify the grade, you can use the following code: (ts-fsrs >=4.0.0) +// let scheduling_card: RecordLog = f.next(card, new Date(), Rating.Good); ``` ## 4. **Retrieving Scheduled Cards**: diff --git a/README_CN.md b/README_CN.md index a9fe2b96..acc95b21 100644 --- a/README_CN.md +++ b/README_CN.md @@ -30,15 +30,17 @@ bun install ts-fsrs ```typescript import {createEmptyCard, formatDate, fsrs, generatorParameters, Rating, Grades} from 'ts-fsrs'; -const params = generatorParameters({enable_fuzz: true}); +const params = generatorParameters({ enable_fuzz: true, enable_short_term: false }); const f = fsrs(params); const card = createEmptyCard(new Date('2022-2-1 10:00:00'));// createEmptyCard(); const now = new Date('2022-2-2 10:00:00');// new Date(); const scheduling_cards = f.repeat(card, now); // console.log(scheduling_cards); -Grades.forEach(grade => { // [Rating.Again, Rating.Hard, Rating.Good, Rating.Easy] - const {log, card} = scheduling_cards[grade]; +for (const item of scheduling_cards) { + // grades = [Rating.Again, Rating.Hard, Rating.Good, Rating.Easy] + const grade = item.log.rating + const { log, card } = item; console.group(`${Rating[grade]}`); console.table({ [`card_${Rating[grade]}`]: { @@ -55,7 +57,7 @@ Grades.forEach(grade => { // [Rating.Again, Rating.Hard, Rating.Good, Rating.Eas }); console.groupEnd(); console.log('----------------------------------------------------------------'); -}); +} ``` 更多的参考: @@ -109,6 +111,8 @@ import { let card: Card = createEmptyCard(); const f: FSRS = new FSRS(); // or const f: FSRS = fsrs(params); let scheduling_cards: RecordLog = f.repeat(card, new Date()); +// 如果你想要指定一个特定的评级,你可以这样做:(ts-fsrs版本必须 >= 4.0.0) +// let scheduling_cards: RecordLog = f.next(card, new Date(), Rating.Good); ``` ## 4. **检查调度卡片信息**: diff --git a/README_JA.md b/README_JA.md index e042eecd..384c13e1 100644 --- a/README_JA.md +++ b/README_JA.md @@ -33,15 +33,17 @@ bun install ts-fsrs ```typescript import {createEmptyCard, formatDate, fsrs, generatorParameters, Rating, Grades} from 'ts-fsrs'; -const params = generatorParameters({enable_fuzz: true}); +const params = generatorParameters({ enable_fuzz: true, enable_short_term: false }); const f = fsrs(params); const card = createEmptyCard(new Date('2022-2-1 10:00:00'));// createEmptyCard(); const now = new Date('2022-2-2 10:00:00');// new Date(); const scheduling_cards = f.repeat(card, now); // console.log(scheduling_cards); -Grades.forEach(grade => { // [Rating.Again, Rating.Hard, Rating.Good, Rating.Easy] - const {log, card} = scheduling_cards[grade]; +for (const item of scheduling_cards) { + // grades = [Rating.Again, Rating.Hard, Rating.Good, Rating.Easy] + const grade = item.log.rating + const { log, card } = item; console.group(`${Rating[grade]}`); console.table({ [`card_${Rating[grade]}`]: { @@ -58,7 +60,7 @@ Grades.forEach(grade => { // [Rating.Again, Rating.Hard, Rating.Good, Rating.Eas }); console.groupEnd(); console.log('----------------------------------------------------------------'); -}); +} ``` もっと: @@ -114,6 +116,8 @@ import { let card: Card = createEmptyCard(); const f: FSRS = new FSRS(); // or const f: FSRS = fsrs(params); let scheduling_cards: RecordLog = f.repeat(card, new Date()); +// もしくは、開発者が評価を指定する場合:(TS-FSRSのバージョンは4.0.0以降である必要があります) +// let scheduling_cards: RecordLog = f.repeat(card, new Date(), Rating.Good); ``` ## 4. **スケジュールされたカードの取得**: diff --git a/__tests__/FSRSV5.test.ts b/__tests__/FSRSV5.test.ts index 0e1bd06f..a9d1b190 100644 --- a/__tests__/FSRSV5.test.ts +++ b/__tests__/FSRSV5.test.ts @@ -15,7 +15,6 @@ describe('FSRS V5 ', () => { 0.6468, ] const f: FSRS = fsrs({ w }) - const grade: Grade[] = [Rating.Again, Rating.Hard, Rating.Good, Rating.Easy] it('ivl_history', () => { let card = createEmptyCard() let now = new Date(2022, 11, 29, 12, 30, 0, 0) @@ -37,7 +36,7 @@ describe('FSRS V5 ', () => { ] const ivl_history: number[] = [] for (const rating of ratings) { - for (const check of grade) { + for (const check of Grades) { const rollbackCard = f.rollback( scheduling_cards[check].card, scheduling_cards[check].log @@ -89,12 +88,6 @@ describe('FSRS V5 ', () => { const card = createEmptyCard() const now = new Date(2022, 11, 29, 12, 30, 0, 0) const scheduling_cards = f.repeat(card, now) - const grades: Grade[] = [ - Rating.Again, - Rating.Hard, - Rating.Good, - Rating.Easy, - ] const stability: number[] = [] const difficulty: number[] = [] @@ -103,8 +96,8 @@ describe('FSRS V5 ', () => { const reps: number[] = [] const lapses: number[] = [] const states: State[] = [] - for (const rating of grades) { - const first_card = scheduling_cards[rating].card + for (const item of scheduling_cards) { + const first_card = item.card stability.push(first_card.stability) difficulty.push(first_card.difficulty) reps.push(first_card.reps) diff --git a/__tests__/impl/abstract_scheduler.test.ts b/__tests__/impl/abstract_scheduler.test.ts new file mode 100644 index 00000000..8d15ba81 --- /dev/null +++ b/__tests__/impl/abstract_scheduler.test.ts @@ -0,0 +1,40 @@ +import { + createEmptyCard, + fsrs, + Grade, + type IPreview, + Rating, +} from '../../src/fsrs' + +describe('basic schduler', () => { + const now = new Date() + + it('[Symbol.iterator]', () => { + const card = createEmptyCard(now) + const f = fsrs() + const preview = f.repeat(card, now) + const again = f.next(card, now, Rating.Again) + const hard = f.next(card, now, Rating.Hard) + const good = f.next(card, now, Rating.Good) + const easy = f.next(card, now, Rating.Easy) + + const expect_preview = { + [Rating.Again]: again, + [Rating.Hard]: hard, + [Rating.Good]: good, + [Rating.Easy]: easy, + [Symbol.iterator]: preview[Symbol.iterator], + } satisfies IPreview + expect(preview).toEqual(expect_preview) + for (const item of preview) { + expect(item).toEqual(expect_preview[item.log.rating]) + } + const iterator = preview[Symbol.iterator]() + expect(iterator.next().value).toEqual(again) + expect(iterator.next().value).toEqual(hard) + expect(iterator.next().value).toEqual(good) + expect(iterator.next().value).toEqual(easy) + expect(iterator.next().done).toBeTruthy() + }) + +}) diff --git a/__tests__/impl/basic_schduler.test.ts b/__tests__/impl/basic_schduler.test.ts index 460570cc..cc1ac34a 100644 --- a/__tests__/impl/basic_schduler.test.ts +++ b/__tests__/impl/basic_schduler.test.ts @@ -25,7 +25,17 @@ describe('basic schduler', () => { [Rating.Hard]: hard, [Rating.Good]: good, [Rating.Easy]: easy, + [Symbol.iterator]: basicScheduler[`previewIterator`].bind(basicScheduler), }) + for (const item of preview) { + expect(item).toEqual(basicScheduler.review(item.log.rating)) + } + const iterator = preview[Symbol.iterator]() + expect(iterator.next().value).toEqual(again) + expect(iterator.next().value).toEqual(hard) + expect(iterator.next().value).toEqual(good) + expect(iterator.next().value).toEqual(easy) + expect(iterator.next().done).toBeTruthy() }) it('[State.New]invalid grade', () => { const card = createEmptyCard(now) @@ -52,7 +62,11 @@ describe('basic schduler', () => { [Rating.Hard]: hard, [Rating.Good]: good, [Rating.Easy]: easy, + [Symbol.iterator]: basicScheduler[`previewIterator`].bind(basicScheduler), }) + for (const item of preview) { + expect(item).toEqual(basicScheduler.review(item.log.rating)) + } }) it('[State.Learning]invalid grade', () => { const cardByNew = createEmptyCard(now) @@ -82,7 +96,11 @@ describe('basic schduler', () => { [Rating.Hard]: hard, [Rating.Good]: good, [Rating.Easy]: easy, + [Symbol.iterator]: basicScheduler[`previewIterator`].bind(basicScheduler), }) + for (const item of preview) { + expect(item).toEqual(basicScheduler.review(item.log.rating)) + } }) it('[State.Review]invalid grade', () => { const cardByNew = createEmptyCard(now) diff --git a/package.json b/package.json index 53423844..42c3d60a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ts-fsrs", - "version": "4.1.3", + "version": "4.2.0", "description": "ts-fsrs is a versatile package based on TypeScript that supports ES modules, CommonJS, and UMD. It implements the Free Spaced Repetition Scheduler (FSRS) algorithm, enabling developers to integrate FSRS into their flashcard applications to enhance the user learning experience.", "main": "dist/index.cjs", "umd": "dist/index.umd.js", diff --git a/src/fsrs/abstract_schduler.ts b/src/fsrs/abstract_schduler.ts index b6eb6604..0f87068a 100644 --- a/src/fsrs/abstract_schduler.ts +++ b/src/fsrs/abstract_schduler.ts @@ -1,8 +1,8 @@ import { FSRSAlgorithm } from './algorithm' import { TypeConvert } from './convert' +import { Grades } from './help' import { type Card, - type RecordLog, type Grade, type RecordLogItem, State, @@ -11,7 +11,7 @@ import { type CardInput, type DateInput, } from './models' -import type { IScheduler } from './types' +import type { IPreview, IScheduler } from './types' export abstract class AbstractScheduler implements IScheduler { protected last: Card @@ -45,14 +45,22 @@ export abstract class AbstractScheduler implements IScheduler { this.initSeed() } - public preview(): RecordLog { + public preview(): IPreview { return { [Rating.Again]: this.review(Rating.Again), [Rating.Hard]: this.review(Rating.Hard), [Rating.Good]: this.review(Rating.Good), [Rating.Easy]: this.review(Rating.Easy), - } satisfies RecordLog + [Symbol.iterator]: this.previewIterator.bind(this), + } satisfies IPreview } + + private *previewIterator(): IterableIterator { + for (const grade of Grades) { + yield this.review(grade) + } + } + public review(grade: Grade): RecordLogItem { const { state } = this.last let item: RecordLogItem | undefined diff --git a/src/fsrs/default.ts b/src/fsrs/default.ts index 3b6a06f1..ffc484b8 100644 --- a/src/fsrs/default.ts +++ b/src/fsrs/default.ts @@ -11,7 +11,7 @@ export const default_w = [ export const default_enable_fuzz = false export const default_enable_short_term = true -export const FSRSVersion: string = 'v4.1.3 using FSRS V5.0' +export const FSRSVersion: string = 'v4.2.0 using FSRS V5.0' export const generatorParameters = ( props?: Partial diff --git a/src/fsrs/fsrs.ts b/src/fsrs/fsrs.ts index 58941a5e..659fa610 100644 --- a/src/fsrs/fsrs.ts +++ b/src/fsrs/fsrs.ts @@ -13,7 +13,7 @@ import { ReviewLogInput, State, } from './models' -import type { int } from './types' +import type { int, IPreview } from './types' import { FSRSAlgorithm } from './algorithm' import { TypeConvert } from './convert' import BasicScheduler from './impl/basic_schduler' @@ -106,10 +106,10 @@ export class FSRS extends FSRSAlgorithm { * const recordLog = f.repeat(card, new Date(), repeatAfterHandler); * ``` */ - repeat( + repeat( card: CardInput | Card, now: DateInput, - afterHandler?: (recordLog: RecordLog) => R + afterHandler?: (recordLog: IPreview) => R ): R { const Schduler = this.Schduler const instace = new Schduler(card, now, this satisfies FSRSAlgorithm) diff --git a/src/fsrs/types.ts b/src/fsrs/types.ts index f2cc7256..8f3ab179 100644 --- a/src/fsrs/types.ts +++ b/src/fsrs/types.ts @@ -1,5 +1,4 @@ import type { - Card, Grade, RecordLog, RecordLogItem, @@ -9,7 +8,11 @@ export type unit = 'days' | 'minutes' export type int = number & { __int__: void } export type double = number & { __double__: void } +export interface IPreview extends RecordLog { + [Symbol.iterator](): IterableIterator +} + export interface IScheduler { - preview(): RecordLog + preview(): IPreview review(state: Grade): RecordLogItem }