我在做一个项目,其中间隔重复是必不可少的,但我不是这方面的专家,我害怕重新发明方轮。我的研究给了我两种不同的系统,即莱特纳系统和SM系列算法。我还没有决定哪个系统最适合我的项目。如果我采取SM的方向,我想我会尝试实现类似Anki使用的东西。
我最好的选择是使用现有的Java库。这可能很简单,我所需要的只是计算下一次重复的时间。
有人听说过这样的倡议吗?
我还没有看过Anki的实现,但是你见过这个吗?
基本上是这样的
public static void calcuateInterval(Card card) {
if (card.getEFactor() < 3) {
card.setCount(1);
}
int count = card.getCount();
int interval = 1;
if (count == 2) {
interval = 6;
} else if (count > 2) {
interval = Math.round(card.getInterval() * card.getEFactor());
}
card.setInterval(interval);
}
如果你真的想要Anki的算法,可以在Github上查看Android的Anki源代码。它是GPL,所以你可能需要购买许可证。
我确实在自己的抽认卡应用程序中重新发明了方轮。算法很简单:物品的重量是年龄分量、进度分量和努力分量的乘积。
年龄组件
公式为A(x) = Cn^x,其中- x为自物品最后一次测试以来的时间(以天为单位),
- C是当x为零时你想要的值,
- n是一个常量,基于你希望该值随着x的增加而增加的速度。
例如,如果您希望该值每五天翻一番,则n = e^(ln(2/C)/5)。
<<p> 进步组件/strong>公式为p (x) = Cn^-x,其中
- x是一个数字,对应于你在这个项目上的成功程度,
- C是当x为零时你想要的值,
- n是一个常量,基于你希望值随x增加而衰减的速度。
例如,如果您希望每五次连续成功将值减半,则n = e^(ln(1/2)/-5)。
<<p> 工作组件/strong>接受以下两个值之一:
- 10,如果你发现你最后一次召回的项目是"硬",或
- 1。
进度调整如下:
- 新条目从进度0开始。
- 如果你发现一个答案很容易,这个项目的进度增加1。
- 如果你发现一个答案很难,项目的进度将变为min(int(previous/2), previous - 1)。
- 如果你得到了一个错误的答案,项目的进度会降到最小(-1,前一个-1)。
是的,值可以变为负值。:)
应用程序通过从所有物品中随机选择来选择下一个要测试的物品,选择的概率与物品的重量直接变化。
算法中的具体数字是可调整的。我已经用我的当前值大约一年了,在积累和保留西班牙语、德语和拉丁语的词汇方面取得了巨大的成功。
(抱歉马铃薯质量的数学表达式。这里不允许使用LaTeX。)
Anki使用SM2算法。然而,该文章所描述的SM2有许多严重的缺陷。幸运的是,它们很容易修复。
对于这篇文章来说,解释如何做到这一点太长了,所以我在这里写了一篇博文。不需要使用开源库来完成此操作,因为实际实现非常简单。这是一个记录良好且易于理解的间隔重复算法。
特性- 介绍了有效学习大型甲板的子甲板(Super有用!)
- 直观的变量名和算法参数。完全
- 容易配置参数以适应不同用户的记忆能力。
- 计算下一张卡的成本很低。不需要对牌组中的每张牌进行计算。
https://github.com/Jakobovski/SaneMemo。
免责声明:我是SaneMemo的作者。
import random
import datetime
# The number of times needed for the user to get the card correct(EASY) consecutively before removing the card from
# the current sub_deck.
CONSECUTIVE_CORRECT_TO_REMOVE_FROM_SUBDECK_WHEN_KNOWN = 2
CONSECUTIVE_CORRECT_TO_REMOVE_FROM_SUBDECK_WHEN_WILL_FORGET = 3
# The number of cards in the sub-deck
SUBDECK_SIZE = 15
REMINDER_RATE = 1.6
class Deck(object):
def __init__(self):
self.cards = []
# Used to make sure we don't display the same card twice
self.last_card = None
def add_card(self, card):
self.cards.append(card)
def get_next_card(self):
self.cards = sorted(self.cards) # Sorted by next_practice_time
sub_deck = self.cards[0:min(SUBDECK_SIZE, len(self.cards))]
card = random.choice(sub_deck)
# In case card == last card lets select again. We don't want to show the same card two times in a row.
while card == self.last_card:
card = random.choice(sub_deck)
self.last_card = card
return card
class Card(object):
def __init__(self, front, back):
self.front = front
self.back = back
self.next_practice_time = datetime.utc.now()
self.consecutive_correct_answer = 0
self.last_time_easy = datetime.utc.now()
def update(self, performance_str):
""" Updates the card after the user has seen it and answered how difficult it was. The user can provide one of
three options: [I_KNOW, KNOW_BUT_WILL_FORGET, DONT_KNOW].
"""
if performance_str == "KNOW_IT":
self.consecutive_correct_answer += 1
if self.consecutive_correct_answer >= CONSECUTIVE_CORRECT_TO_REMOVE_FROM_SUBDECK_WHEN_KNOWN:
days_since_last_easy = (datetime.utc.now() - self.last_time_easy).days
days_to_next_review = (days_since_last_easy + 2) * REMINDER_RATE
self.next_practice_time = datetime.utc.now() + datetime.time(days=days_to_next_review)
self.last_time_easy = datetime.utc.now()
else:
self.next_practice_time = datetime.utc.now()
elif performance_str == "KNOW_BUT_WILL_FORGET":
self.consecutive_correct_answer += 1
if self.consecutive_correct_answer >= CONSECUTIVE_CORRECT_TO_REMOVE_FROM_SUBDECK_WHEN_WILL_FORGET:
self.next_practice_time = datetime.utc.now() + datetime.time(days=1)
else:
self.next_practice_time = datetime.utc.now()
elif performance_str == "DONT_KNOW":
self.consecutive_correct_answer = 0
self.next_practice_time = datetime.utc.now()
def __cmp__(self, other):
"""Comparator or sorting cards by next_practice_time"""
if hasattr(other, 'next_practice_time'):
return self.number.__cmp__(other.next_practice_time)