万码皆空的博客
Published on

间隔重复记忆之SM-2

Authors
  • avatar
    Name
    万码皆空
    Twitter

最近读了一篇彼得·沃兹尼亚克博士(Dr Piotr Wozniak)写的文章,介绍间隔重复记忆法的历史,文章地址:The true history of spaced repetition。我还没有读完,读到了Algorithm SM-2。

SM-2算法

SM-2是间隔重复记忆的第一代算法,目前还有很多软件在使用,内容如下:

  1. 将知识点拆分成最小单元。

  2. 将每个知识点分配一个E-Factor,初始值2.5。

  3. 使用以下公式重复记忆知识点:

    I(1):=1

    I(2):=6

    for n>2: I(n):=I(n-1)*EF

    其中:

    I(n) 代表第n次重复记忆时的间隔

    EF 代表每个知识点关联的E-Factor。

    如果计算出来的间隔时间是小数,四舍五入取整。

  4. 每次重复一个知识点后,给出一个记忆质量值,取值范围在0-5:

    • 5 - 知识点完全记住。
    • 4 - 能回忆起来,略有迟疑。
    • 3 - 能回忆起来,但是有难度。
    • 2 - 回忆错误,但是正确答案好像很容易能记起来。
    • 1 - 回忆错误,但是正确答案记住了。
    • 0 - 回忆不起来。
  5. 每次重复一个知识点后,更新其关联的E-Factor值,新的E-Factor计算公式如下:

    EF’:=EF+(0.1-(5-q)*(0.08+(5-q)*0.02))

    其中:

    EF’ 代表新的E-Factor值。

    EF 代表就得E-Factor值。

    q 代表步骤4中给出的记忆质量值。

    如果EF’ 值小于1.3,则EF’=1.3

  6. 如果第4步中的记忆质量值小于3,则该知识点按新知识重新计算间隔记忆。

  7. 每一天的重复记忆中记忆质量小于4的知识点需要再次重复,直到记忆质量大于等于4再结束当天的重复记忆过程。

TypeScript实现

export interface SM2Item {
    interval?: number,
    count?: number,
    efactor?: number,
    quality: number,
}

export interface SM2Result {
    item: SM2Item,
    needRepeat: boolean,
}

/**
 * Calculates the E-factor of a given quality.
 *
 * @param {number} efactor - The E-factor to calculate.
 * @param {number} quality - The quality to use in the calculation.
 * @return {number} The calculated E-factor.
 */
function calculateEFactor(efactor: number, quality: number): number {
    let nextEFactor = efactor + (0.1 - (5 - quality) * (0.08 + (5 - quality) * 0.02));

    if (nextEFactor > 2.5) return 2.5;
    else if (nextEFactor < 1.3) return 1.3;
    else return Number(nextEFactor.toFixed(3));
}

/**
 * Calculates the result of multiplying an interval by an efactor.
 *
 * @param {number} interval - The interval to be multiplied.
 * @param {number} efactor - The efactor to multiply the interval by.
 * @return {number} The result of the multiplication.
 */
function calculateInterval(interval: number, efactor: number): number {
    return Math.round(interval * efactor);
}

/**
 * Calculates the next interval and other values for a given SM2Item, based on its
 * current values. If the quality is less than 4, the item is marked for repetition.
 *
 * @param {SM2Item} item - the SM2Item to calculate the next interval for
 * @return {SM2Result} an object with the next SM2Item and a boolean indicating if the item needs to be repeated
 */
function sm2(item: SM2Item): SM2Result {
    let { interval = 1, count = 1, efactor = 2.5, quality } = item;

    if (interval < 1) interval = 1;
    if (count < 1) count = 1;
    if (efactor < 1.3) efactor = 1.3;
    if (efactor > 2.5) efactor = 2.5;
    if (quality < 0) quality = 0;
    if (quality > 5) quality = 5;
    
    const needRepeat: boolean = quality < 4;
    const nextItem: SM2Item = {
        interval,
        count,
        efactor,
        quality,
    };

    if (quality < 3) {
        nextItem.interval = 1;
        nextItem.count = 1;
        nextItem.efactor = 2.5;
    } else {
        switch (count) {
            case 1:
                nextItem.interval = 1;
                break;
            case 2:
                nextItem.interval = 6;
                break;
            default:
                nextItem.interval = calculateInterval(interval, efactor);
                break;
        }

        nextItem.count = count + 1;
        nextItem.efactor = calculateEFactor(efactor, quality);
    }

    return { item: nextItem, needRepeat }
}

export default sm2;

使用实例:

import sm2, { SM2Item } from '../src/sm2';

interface Card {
    question: string,
    answer: string,
    interval?: number,
    count?: number,
    efactor?: number,
};

function getCardsFromSomewhere(): Card[] {
    const cards: Card[] = [
        { question: 'a', answer: 'A' },
        { question: 'b', answer: 'B', interval: 1, count: 1, efactor: 2.5 },
        { question: 'c', answer: 'C', interval: 6, count: 2, efactor: 1.8 },
        { question: 'd', answer: 'D', interval: 8, count: 3, efactor: 1.3 },
        { question: 'e', answer: 'E', interval: 16, count: 4, efactor: 2.1 },
    ];

    return cards;
}

function getQualityFromUserResponse(card: Card): number {
    return Math.round(Math.random() * 5);
}

function updateCard(card: Card, item: SM2Item) {
    const { interval, count, efactor } = item;
    const cardUpdated: Card = { ...card, interval, count, efactor };

    console.log('card with new sm info', cardUpdated);
}

function main() {
    const cards = getCardsFromSomewhere();

    for (const card of cards) {
        const { interval, count, efactor } = card;
        const quality = getQualityFromUserResponse(card);
        const {item, needRepeat} = sm2({ interval, count, efactor, quality });
        updateCard(card, item);

        if(needRepeat) {
            console.log('Card need to remember again today until quality >= 4.', card);
        }
    }
}

main();

NPM

已经发布到了npm,可以直接安装使用:

npm install spaced-repetition.js

GITHUB

Github仓库地址:spaced-repetition.js