Last active
          June 2, 2025 12:23 
        
      - 
      
- 
        Save riceissa/1ead1b9881ffbb48793565ce69d7dbdd to your computer and use it in GitHub Desktop. 
Revisions
- 
        riceissa revised this gist Dec 15, 2023 . 1 changed file with 8 additions and 0 deletions.There are no files selected for viewingThis file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -12,6 +12,14 @@ responses throughout their history), leech tracking, checking if a card from the same notes has been reviewed already that day, delay in response (i.e. I assume all cards are reviewed exactly on the day they are due). Update (2023-12-15): Please note that the Anki review algorithm has possibly changed in many ways since the time when I wrote this program (although I believe that Anki still uses SM2 by default, so the basic concepts should still be the same as what is shown below). I have sadly not had the time or energy to keep up with the latest changes. In particular, Anki now supports FSRS instead of the SM2 algorithm (which is the algorithm below); FSRS is not covered at all below. """ 
- 
        riceissa revised this gist Jan 12, 2023 . 1 changed file with 3 additions and 3 deletions.There are no files selected for viewingThis file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -94,11 +94,11 @@ def schedule(card, response): elif card.status == 'relearning': if response == "again": card.steps_index = 0 return minutes_to_days(LAPSES_STEPS[0]) elif response == "good": card.steps_index += 1 if card.steps_index < len(LAPSES_STEPS): return minutes_to_days(LAPSES_STEPS[card.steps_index]) else: # we have re-graduated! card.status = 'learned' 
- 
        riceissa revised this gist Aug 11, 2021 . 1 changed file with 3 additions and 2 deletions.There are no files selected for viewingThis file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -74,7 +74,7 @@ def schedule(card, response): card.status = 'relearning' card.steps_index = 0 card.ease_factor = max(130, card.ease_factor - 20) card.interval = max(MINIMUM_INTERVAL, card.interval * NEW_INTERVAL/100) return minutes_to_days(LAPSES_STEPS[0]) elif response == "hard": card.ease_factor = max(130, card.ease_factor - 15) @@ -102,7 +102,8 @@ def schedule(card, response): else: # we have re-graduated! card.status = 'learned' # we don't modify the interval here because that was already done when # going from 'learned' to 'relearning' return card.interval else: raise ValueError("you can't press this button / we don't know how to deal with this case") 
- 
        riceissa revised this gist Aug 11, 2021 . 1 changed file with 2 additions and 4 deletions.There are no files selected for viewingThis file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -3,7 +3,7 @@ got from watching https://www.youtube.com/watch?v=lz60qTP2Gx0 and https://www.youtube.com/watch?v=1XaJjbCSXT0 and from reading https://faqs.ankiweb.net/what-spaced-repetition-algorithm.html There is also https://github.com/dae/anki/blob/master/anki/sched.py but I find it really hard to understand. @@ -74,9 +74,7 @@ def schedule(card, response): card.status = 'relearning' card.steps_index = 0 card.ease_factor = max(130, card.ease_factor - 20) card.interval = card.interval * NEW_INTERVAL/100 return minutes_to_days(LAPSES_STEPS[0]) elif response == "hard": card.ease_factor = max(130, card.ease_factor - 15) 
- 
        riceissa revised this gist Apr 17, 2020 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewingThis file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -71,7 +71,7 @@ def schedule(card, response): raise ValueError("you can't press this button / we don't know how to deal with this case") elif card.status == 'learned': if response == "again": card.status = 'relearning' card.steps_index = 0 card.ease_factor = max(130, card.ease_factor - 20) # the anki manual says "the current interval is multiplied by the 
- 
        riceissa created this gist Nov 22, 2019 .There are no files selected for viewingThis file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,136 @@ """ This is my understanding of the Anki scheduling algorithm, which I mostly got from watching https://www.youtube.com/watch?v=lz60qTP2Gx0 and https://www.youtube.com/watch?v=1XaJjbCSXT0 and from reading https://apps.ankiweb.net/docs/manual.html#what-spaced-repetition-algorithm-does-anki-use There is also https://github.com/dae/anki/blob/master/anki/sched.py but I find it really hard to understand. Things I don't bother to implement here: the random fudge factor (that Anki uses to decorrelate cards that were added on the same day and have the same responses throughout their history), leech tracking, checking if a card from the same notes has been reviewed already that day, delay in response (i.e. I assume all cards are reviewed exactly on the day they are due). """ # "New Cards" tab NEW_STEPS = [1, 10] # in minutes GRADUATING_INTERVAL = 1 # in days EASY_INTERVAL = 4 # in days STARTING_EASE = 250 # in percent # "Reviews" tab EASY_BONUS = 130 # in percent INTERVAL_MODIFIER = 100 # in percent MAXIMUM_INTERVAL = 36500 # in days # "Lapses" tab LAPSES_STEPS = [10] # in minutes NEW_INTERVAL = 70 # in percent MINIMUM_INTERVAL = 1 # in days class Card: def __init__(self): self.status = 'learning' # can be 'learning', 'learned', or 'relearning' self.steps_index = 0 self.ease_factor = STARTING_EASE self.interval = None def __repr__(self): return "Card[%s; steps_idx=%s; ease=%s; interval=%s]" % (self.status, self.steps_index, self.ease_factor, str(self.interval)) def schedule(card, response): '''response is one of "again", "hard", "good", or "easy" returns a result in days''' if card.status == 'learning': # for learning cards, there is no "hard" response possible if response == "again": card.steps_index = 0 return minutes_to_days(NEW_STEPS[card.steps_index]) elif response == "good": card.steps_index += 1 if card.steps_index < len(NEW_STEPS): return minutes_to_days(NEW_STEPS[card.steps_index]) else: # we have graduated! card.status = 'learned' card.interval = GRADUATING_INTERVAL return card.interval elif response == "easy": card.status = 'learned' card.interval = EASY_INTERVAL return EASY_INTERVAL else: raise ValueError("you can't press this button / we don't know how to deal with this case") elif card.status == 'learned': if response == "again": card.state = 'relearning' card.steps_index = 0 card.ease_factor = max(130, card.ease_factor - 20) # the anki manual says "the current interval is multiplied by the # value of new interval", but I have no idea what the "new # interval" is return minutes_to_days(LAPSES_STEPS[0]) elif response == "hard": card.ease_factor = max(130, card.ease_factor - 15) card.interval = card.interval * 1.2 * INTERVAL_MODIFIER/100 return min(MAXIMUM_INTERVAL, card.interval) elif response == "good": card.interval = (card.interval * card.ease_factor/100 * INTERVAL_MODIFIER/100) return min(MAXIMUM_INTERVAL, card.interval) elif response == "easy": card.ease_factor += 15 card.interval = (card.interval * card.ease_factor/100 * INTERVAL_MODIFIER/100 * EASY_BONUS/100) return min(MAXIMUM_INTERVAL, card.interval) else: raise ValueError("you can't press this button / we don't know how to deal with this case") elif card.status == 'relearning': if response == "again": card.steps_index = 0 return minutes_to_days(LAPSE_STEPS[0]) elif response == "good": card.steps_index += 1 if card.steps_index < len(LAPSE_STEPS): return minutes_to_days(LAPSE_STEPS[card.steps_index]) else: # we have re-graduated! card.status = 'learned' card.interval = max(MINIMUM_INTERVAL, card.interval * NEW_INTERVAL/100) return card.interval else: raise ValueError("you can't press this button / we don't know how to deal with this case") def minutes_to_days(minutes): return minutes / (60 * 24) def human_friendly_time(days): if not days: return days if days < 1: return str(round(days * 24 * 60, 2)) + " minutes" elif days < 30: return str(round(days, 2)) + " days" elif days < 365: return str(round(days / (365.25 / 12), 2)) + " months" else: return str(round(days / 365.25, 2)) + " years" card1 = Card() # responses = ["good", "good", "good", "again", "good", "good", "good"] responses = ["good"] * 10 for r in responses: print(str(card1) + " [%s]" % r, end="→ ") t = schedule(card1, r) print(human_friendly_time(t), card1)