背景

随着闪卡系统素材量的进一步增加, 每天的复习时间越来越多. 而且因为只有十分重要和有价值的素材, 我才会选择使用闪卡系统.这就导致我每次复习都必须高度集中注意力.

长时间集中注意力, 一个显而易见的后果就是: 疲劳! 对, 就是疲劳! 而过度的疲劳, 随之而来的就是大脑的抗拒和抵触.

我不是 奋斗型 人格, 我也不会尝试去以 “自律” 的名义 来强迫自己的大脑去做事. – 尽管, 我曾经这么做过. 我现在更倾向于: 我要试着尊重自己的感觉. 我要接纳大脑的疲倦. 原因也非常简单. 跑的快的人, 不一定跑的远.因为方向不对, 跑的越来, 反倒会偏离终点更远. 而和大脑和谐共处, 尊重大脑的感受, 方能不时地审视自己的处境, 寻找更好的方向,更好的方法. 简言之: 我认为人应该和自己的大脑和解.尊重和接纳自己.如此, 跑的慢, 未必就走得不比别人远.

现在闪卡系统占用的时间已经到了 小时 级别. 我不得不重视. 我发现我无法说服大脑, 在本该放松的周末, 来去复习这些闪卡.我尊重它的选择. 我也十分认同, 周末就应该好好放松.玩玩 LOL 的无限活力, 真的很让人放松. 玩的过程中, 其实也真的会思考一些事情. 比如: 我在做的事, 是不是真的有必要? 有没有更好的选择? 远离家人, 待在异国他乡, 是不是一个最优的选择? 如果必须投入巨量资源解决语言问题, 是不是欧洲国家,更合适一些? 其实是有一些很认真的思考和权衡的. 如果不久的将来, 我选择离开日本, 我也不觉得是我的问题. 对, 是这个国家的损失. 它失去的不是我, 而是我这一类人, 我这一类更注重精神感受的人. 当然, 未来的事, 未来再说吧. 最重要的是, 要学会尊重自己的大脑. 让大脑适时处于略松弛的状态, 让它帮我们好好想想, 除了眼下的事, 还有没有其他的可能.

单纯考虑时间, 在一定的假期之后, 尝试恢复闪卡复习节奏, 也是很困难的. 这次是3天假期. 如果我选择完成所有闪卡的复习, 就意味着我周一几乎啥都不用干了. 显而易见, 这是不可能的事.

另一个层面, 所谓的 “间隔复习” 的周期, 并没有那么神奇. 间隔复习, 是有必要的. 但是到底是否遗忘, 在你复习之前, 你可能是无法知晓的. 这是一个悖论. 我想表达的是: 让自己好好休息. 如果遗忘了, 卡片会自己回到前一周期, 后续会额外多复习; 没有遗忘的, 当然更好.

在写这些东西之前, 我还专门检索了下. 目前还没有看到能够简单直观地支持 “假期” 的闪卡系统. 彷佛: 每日复习, 是一个不容触碰的 间隔复习的底线. — 这很搞笑. 就好像说: 我必须每天编码, 才能保持较高的编码水平一样, 一样扯淡!

基础需求:

  • 周六日, 自动设置为假期.
  • 其他法定假期, 能够支持手动添加和配置.
  • 计算卡片复习时间时, 能够自动跳过 假期.

设计难点:

  • 如何跳过假期? – 说实话, 并没有太好的方法.真的就是遍历, 每天判定.

  • 如何高性能运转. – 这是一个可以预期的问题. 每次用到卡片时, 都独立运算一次, 性能上肯定达不到要求. 当然, 稳妥起见, 初版还是直接运算的. 切换卡组时, 也是毫不意外的, UI卡住了.

核心代码:

基础思路, 非常简单. 就是简单的在计算复习时间,跳过那些节假日就行了. 所以, 我只贴一些核心的修改.

卡片状态, 增加一个 holiday 的配置:

下面的例子, 就是把 2024-02-22024-02-26 设置为了假期. holiday 用的是 hashMap, 而不是数组,当然也是为了性能考虑的小心思了. 可以预见, holiday 肯定会越来越多的.

    "config": {
        "stage": {
            "1": 3,
            "2": 5,
            "3": 8,
            "4": 13,
            "5": 21,
            "6": null
        },
        "holiday": {
            "2024-02-23": true,
            "2024-02-26": true
        }
    },

每个卡片新增两个字段: reviewDate 和 reviewDateHash:

  • reviewDate: 下次复习的时间. 这个时间就是下次应该复习该卡片的时间. 计算这个时间时, 已经考虑了假期. 类似于一个缓存机制吧. 这样就不用每次动态计算卡片的 复习时间 了.

  • reviewDateHash: 是由当时计算 reviewDate 时用到的一些核心信息,组成的字符串. 因为缓存的 reviewDate,总是要在合适的时机,去缓存的.

  • 卡片reviewDateHash目前的策略如下:

// 用于辅助支持自定义假期. 周六日会被直接判定为假期.
const baseHolidayHash = hashCode(JSON.stringify(baseState.config['holiday']));

const cardReviewDateHash = `${baseHolidayHash}_${card.stage}_${intervalGap}_${card.lastStudy}`;

这样做可以保证,在下列场景下, 都会重新计算 复习时间:

  • 假期有变更.如: 新增了新的假期.
  • 复习间隔的配置有变动.
  • 卡片本身的状态有变动.比如: 卡片OK导致卡片 stage 来到了下一级.

计算复习时间时, 跳过假期和周六日:

也没啥神秘的.真的就是从上次复习的日期, 逐日遍历:

    while (days > 0) {
        result.setDate(result.getDate() + 1);
        if (!isWeekend(result) && !isHoliday(result)) {
            days--;
        }
    }

isHolidayisWeekend 的实现:

代码本身, 没啥难度. 网上也能搜到很多类似的例子. 但是有一个细节, 为了便于人手动配置, 需要将日期的特定格式来对比.为了避免歧义, 我统一用两位数字来表示 月和日,如: 2024-02-23.

function isHoliday(date) {
    const day = date.getDate();
    const month = date.getMonth() + 1;
    const year = date.getFullYear();

    // ref: https://stackoverflow.com/a/2998874
    const zeroPad = (num, places) => String(num).padStart(places, '0');

    const dateStr = `${year}-${zeroPad(month, 2)}-${zeroPad(day, 2)}`;
    return true == baseState.config['holiday'][dateStr];
}

function isWeekend(date) {
    const Sunday = 0;
    const Saturday = 6;
    const dayOfWeek = date.getDay()

    return dayOfWeek == Sunday || dayOfWeek == Saturday;
}

小感: 越来越感觉, 果断弃用第三方闪卡系统, 是一个很明智的决定.

当时自制闪卡系统的前因后果, 可以参考: 我为什么暂停一周,做了一个Anki替代品?

回头来看. 间隔复习本身的价值, 我是十分推崇的.但是已有的闪卡系统, 说实话, 不敢恭维.

他们, 可能比我更懂记忆原理, , 但是他们可能没有我更懂生活.