Algoritmo de repetição espaçada para memorização e aprendizado eficiente

Repetição Espaçada em Python: O Algoritmo Que Engana o Esquecimento (SM-2 Implementado do Zero)

Você Estuda Errado – e o Seu Cérebro Sabe Disso

Deixa eu te contar uma coisa que ninguém te conta na faculdade, em curso de programação, ou naquele tutorial de 47 minutos que você assiste a 2x de velocidade: você não tem problema de memória. Você tem problema de agendamento.

Eu passei três anos estudando para certificações técnicas acumulando cadernos que eu nunca mais abria. Sabe aquele caderno bonito que você compra cheio de motivação e na terceira página já desistiu? Exato. O problema nunca foi o conteúdo – foi o timing da revisão.

E é aqui que entra a repetição espaçada em Python: um algoritmo que calcula o momento exato em que você está prestes a esquecer algo e te mostra o conteúdo. Nem antes (desperdício de tempo), nem depois (já esqueceu). No ponto ótimo. A ciência chama de curva de esquecimento de Ebbinghaus. Eu chamo de "finalmente algo que funciona".

Neste artigo, vou implementar um sistema completo de repetição espaçada usando o algoritmo SM-2 – o mesmo motor por trás do Anki – em Python puro. Sem biblioteca externa, sem framework, sem desculpa. E no final, você vai ter um script que gerencia seu estudo de forma mais eficiente que 99% dos apps pagos por aí.

A Ciência Por Trás da Repetição Espaçada

Hermann Ebbinghaus descobriu em 1885 que o esquecimento segue uma curva exponencial. Você esquece 50% do que estudou nas primeiras 24 horas. Em uma semana, sobra menos de 20%. A menos que você revise.

Revisar antes de esquecer reforça a trilha neural e achata a curva. Cada revisão bem-timingada empurra o próximo esquecimento mais para frente:

  • 1ª revisão: 1 dia depois do estudo inicial
  • 2ª revisão: 3 dias depois
  • 3ª revisão: 7 dias depois
  • 4ª revisão: 16 dias depois
  • 5ª revisão: 35 dias depois

Viu o padrão? O intervalo entre revisões cresce. Daí o nome "espaçada". O SM-2 vai além: ele adapta os intervalos baseado em quanto você errou ou acertou.

Implementando o Algoritmo SM-2 do Zero em Python

O SM-2 (SuperMemo 2) usa três variáveis por cartão:

  • ef (ease factor): dificuldade do cartão (começa em 2.5)
  • interval: dias até a próxima revisão
  • repetitions: quantas vezes acertou consecutivamente

A fórmula é elegante e cabe em 15 linhas:

def sm2_review(ease_factor, interval, repetitions, quality):
    quality: 0-5 (0=blackout total, 5=lembrou perfeito)
    Retorna: (novo ease factor, novo intervalo, novas repetitions)

    if quality < 3:
        # Errou: reseta repeticoes, intervalo volta para 1 dia
        repetitions = 0
        interval = 1
    else:
        # Acertou: calcula proximo intervalo
        if repetitions == 0:
            interval = 1
        elif repetitions == 1:
            interval = 6
        else:
            interval = round(interval * ease_factor)
        repetitions += 1

    # Atualiza ease factor (nunca abaixo de 1.3)
    new_ease = ease_factor + (0.1 - (5 - quality) * (0.08 + (5 - quality) * 0.02))
    new_ease = max(1.3, new_ease)

    return (new_ease, interval, repetitions)

Simples. Perigosamente simples. E é isso que torna o SM-2 genial – ele funciona com quase nada de código porque a ciência está na matemática, não na complexidade.

Construindo o Sistema Completo: Agendamento + Persistência

Um algoritmo isolado não resolve nada. O que você precisa é um sistema: agendar revisões, persistir dados, e te dizer o que estudar hoje. Vou construir uma classe completa:

import json
import os
from datetime import datetime, timedelta
from pathlib import Path

class SpacedRepetitionSystem:
    def __init__(self, data_file="srs_data.json"):
        self.data_file = Path(data_file)
        self.cards = self._load()

    def _load(self):
        if self.data_file.exists():
            with open(self.data_file) as f:
                return json.load(f)
        return {"cards": [], "stats": {"total_reviews": 0}}

    def _save(self):
        with open(self.data_file, "w", encoding="utf-8") as f:
            json.dump(self.cards, f, indent=2, ensure_ascii=False)

    def add_card(self, front, back, tags=None):
        card = {
            "id": len(self.cards["cards"]) + 1,
            "front": front,
            "back": back,
            "ease_factor": 2.5,
            "interval": 0,
            "repetitions": 0,
            "next_review": datetime.now().isoformat(),
            "tags": tags or [],
            "created": datetime.now().isoformat(),
            "review_history": []
        }
        self.cards["cards"].append(card)
        self._save()
        print(f"Cartao #{card['id']} adicionado: {front[:50]}...")
        return card["id"]

    def get_due_cards(self, limit=20):
        now = datetime.now()
        due = []
        for card in self.cards["cards"]:
            next_review = datetime.fromisoformat(card["next_review"])
            if next_review <= now:
                due.append(card)
                if len(due) >= limit:
                    break
        return due

    def review_card(self, card_id, quality):
        card = next((c for c in self.cards["cards"] if c["id"] == card_id), None)
        if not card:
            raise ValueError(f"Cartao {card_id} nao encontrado")

        card["ease_factor"], card["interval"], card["repetitions"] = sm2_review(
            card["ease_factor"], card["interval"], card["repetitions"], quality
        )

        next_date = datetime.now() + timedelta(days=card["interval"])
        card["next_review"] = next_date.isoformat()

        card["review_history"].append({
            "date": datetime.now().isoformat(),
            "quality": quality,
            "new_interval": card["interval"],
            "new_ease": card["ease_factor"]
        })

        self.cards["stats"]["total_reviews"] += 1
        self._save()

        status = "ACERTOU" if quality >= 3 else "ERROU"
        print(f"{status} Cartao #{card_id}: proximo em {card['interval']} dias (ef={card['ease_factor']:.2f})")

    def stats(self):
        cards = self.cards["cards"]
        now = datetime.now()
        due = sum(1 for c in cards if datetime.fromisoformat(c["next_review"]) <= now)
        learned = sum(1 for c in cards if c["repetitions"] > 0)
        return {
            "total_cards": len(cards),
            "due_today": due,
            "learned": learned,
            "total_reviews": self.cards["stats"]["total_reviews"]
        }

Como Usar no Dia a Dia

A parte boa? Você usa isso da mesma forma que qualquer CLI. Rápido, sem frescura:

# Inicializa o sistema
srs = SpacedRepetitionSystem()

# Adiciona cartoes (ex: estudo para certificacao)
srs.add_card(
    front="O que faz o AWS VPC Peering?",
    back="Conecta duas VPCs via rede privada da AWS, sem internet gateway ou VPN.",
    tags=["aws", "networking"]
)

srs.add_card(
    front="Qual a diferenca entre deepcopy e copy?",
    back="copy() copia a referencia de objetos internos; deepcopy() copia recursivamente tudo.",
    tags=["python", "memory"]
)

# Sessao de revisao - mostra o que esta vencido
due = srs.get_due_cards()
for card in due:
    print(f"\nP: {card['front']}")
    input("Enter para ver resposta...")
    print(f"R: {card['back']}")
    quality = int(input("Qualidade (0-5): "))
    srs.review_card(card["id"], quality)

Otimizando: Tags, Prioridades e Modo "Crise"

Um sistema de verdade precisa de filtros. Ninguém quer revisar 200 cartões na noite antes da prova. Então adicionei modo crise – que mostra só os cartões mais urgentes de uma tag específica:

def crisis_mode(self, tag, count=10):
    now = datetime.now()
    overdue = []
    for card in self.cards["cards"]:
        if tag not in card.get("tags", []):
            continue
        next_review = datetime.fromisoformat(card["next_review"])
        days_overdue = (now - next_review).days
        if days_overdue >= 0:
            overdue.append((days_overdue, card))
    overdue.sort(key=lambda x: x[0], reverse=True)
    return [card for _, card in overdue[:count]]

Visualizando Seus Dados – O Feedback que o Cérebro Precisa

Aqui vai uma verdade que ninguém conta: ver seu progresso importa tanto quanto estudar. O cérebro precisa de feedback visual para manter motivação. Então criei um gerador de relatório simples:

def generate_report(self):
    cards = self.cards["cards"]
    now = datetime.now()
    print("=" * 60)
    print("RELATORIO SRS - " + now.strftime("%d/%m/%Y"))
    print("=" * 60)
    print(f"Total de cartoes: {len(cards)}")
    print(f"Revisoes totais:  {self.cards['stats']['total_reviews']}")

    intervals = {
        "Hoje (1 dia)": 0,
        "Curto (2-7 dias)": 0,
        "Medio (8-30 dias)": 0,
        "Longo (31-90 dias)": 0,
        "Mestre (90+ dias)": 0
    }
    for card in cards:
        interval = card["interval"]
        if interval <= 1: intervals["Hoje (1 dia)"] += 1
        elif interval <= 7: intervals["Curto (2-7 dias)"] += 1
        elif interval <= 30: intervals["Medio (8-30 dias)"] += 1
        elif interval <= 90: intervals["Longo (31-90 dias)"] += 1
        else: intervals["Mestre (90+ dias)"] += 1

    print("\nDistribuicao por intervalo:")
    for label, count in intervals.items():
        bar = "#" * count
        print(f"  {label:20s} {count:3d} {bar}")

Por Que Funciona – A Neurociência Sem Bla-bla-bla

O que o SM-2 explora se chama efeito de espaçamento (spacing effect), documentado desde 1885. Quando você revisa algo quase esquecido, o cérebro trabalha mais para recuperar a informação. Esse esforço extra consolida a memória de forma muito mais profunda do que revisar algo que você ainda lembra bem.

É contraintuitivo. Parece que estudar com intervalos longos é pior. Mas a ciência mostra o oposto: a dificuldade no momento da recuperação é o que fortalece a memória. É como treino de musculação – sem carga, sem ganho.

Na prática, isso significa:

  • Estudar 30 minutos por dia > 3 horas no domingo (com SRS)
  • Revisar quando está difícil lembrar > revisar quando está fresco
  • Consistência por 6 meses > intensidade por 2 semanas

Erros que Cometi (e que Você Não Precisa Repetir)

Antes de chegar nesse modelo, eu cometi todos os erros clássicos:

  1. Cartões enormes – Tinha cartões com 3 parágrafos de frente. O cérebro não processa assim. Um conceito por cartão. Sem negociação.
  2. Sem tags – Quando a prova AWS chegou perto, eu precisava filtrar por serviço. Sem tags, impossível focar.
  3. Ignorar cartões difíceis – Os cartões com ease factor baixo eu evitava. Ironia: são exatamente os que você mais precisa revisar.
  4. Não revisar todo dia – O SRS só funciona se você revisar. Sem consistência, os intervalos acumulam e vira uma bola de neve.

Integrando com Sua Rotina de Estudo Existente

Você não precisa largar tudo e virar monk mode. O segredo é integrar o SRS no que você já faz:

  • Antes de estudar algo novo: rode get_due_cards() e revise o que está vencido (10 min)
  • Depois de estudar: adicione os conceitos novos como cartões (5 min)
  • Antes de dormir: cheque o relatório e planeje amanhã (2 min)

Total: 17 minutos por dia. Em 6 meses, você terá internalizado mais do que a maioria dos devs internalizam em 2 anos de "estudo" desorganizado.

O Próximo Nível: Automatizando a Criação de Cartões

O sistema fica ainda mais poderoso quando a criação de cartões é automática. Imagine um script que lê seus notes em Markdown e gera cartões automaticamente:

def cards_from_markdown(md_text):
    import re
    pattern = r"## (.+?)\n(.+?)(?=##|$)"
    matches = re.findall(pattern, md_text, re.DOTALL)
    return [(q.strip(), a.strip()) for q, a in matches]

with open("aws-vpc-notes.md") as f:
    notes = f.read()

for front, back in cards_from_markdown(notes):
    srs.add_card(front, back, tags=["aws", "auto-generated"])

Assim você estuda normalmente, toma notas, e o SRS gera as revisões automaticamente. É automação de aprendizado – o que, convenhamos, é bem meta.

Quer Ver Mais? Me Diz Qual Automação

Esse sistema de repetição espaçada é só o começo. Dá pra integrar com API REST, criar uma CLI completa com Typer, ou até fazer um bot Telegram que te manda os cartões do dia. O limite é sua necessidade.

Qual automação você quer ver no próximo post? Me conta nos comentários – eu construo, testo, erro, corrijo, e escrevo sobre o processo. Sem enrolação.

E se você curte essa vibe de ciência cognitiva aplicada a código, dá uma olhada na categoria Mente Binária – é lá que eu exploro a interseção entre como a gente pensa e como a gente programa.

Comparando com o Anki: Quando Vale a Pena Criar o Seu?

Eu sei o que você está pensando: "Olivetto, por que reinventar a roda se o Anki já existe?" Boa pergunta. O Anki é excelente – e eu uso ele. Mas tem três cenários onde criar seu próprio SRS faz sentido:

  1. Integração com seu workflow: Você quer que os cartões sejam gerados automaticamente a partir dos seus logs, código, ou documentação técnica. O Anki exige entrada manual.
  2. Controle total dos dados: Seus cartões ficam no seu JSON local, sem sync cloud, sem conta, sem telemetria. Zero vendor lock-in.
  3. Aprendizado real: Implementar o SM-2 do zero te ensina mais sobre o algoritmo do que 100 flashcards sobre ele. É a prova viva do método – aprender fazendo.

Não estou dizendo para abandonar o Anki. Estou dizendo que entender como ele funciona por dentro te transforma de usuário em criador. E essa diferença importa quando você quer adaptar a ferramenta à sua realidade, e não o contrário.

Extensoes Que Você Pode Adicionar

O sistema que construí aqui é funcional, mas é só o começo. Aqui vão extensões que eu planejo implementar e que você pode tentar também:

  • Algoritmo FSRS: O Free Spaced Repetition Scheduler é o sucessor moderno do SM-2. Usa regressão logística e modelos de memória mais sofisticados para prever esquecimento com mais precisão.
  • Sincronização via API REST: Expõe os cartões como endpoints e cria um frontend React. Agora você tem seu próprio Anki web app.
  • CLI com Typer: Transforma o sistema em ferramenta de linha de comando com subcomandos: srs add, srs review, srs stats, srs export.
  • Bot Telegram: Usa python-telegram-bot para enviar os cartões do dia automaticamente. Sem abrir terminal, sem lembrar de rodar o script – o bot aparece no seu chat com as perguntas.
  • Importação de Anki: Parse o arquivo .apkg do Anki (que é um SQLite zipado) e migra seus cartões existentes para o novo sistema.

Conclusão: Estude Melhor, Não Mais

O maior mito sobre aprendizado é que você precisa de mais tempo. Não precisa. Você precisa de melhor timing.

O algoritmo de repetição espaçada não é magia. É matemática aplicada à biologia do seu cérebro. E quando você implementa ele do zero em Python, acontece algo interessante: você não só aprende sobre o algoritmo – você vive o algoritmo. Cada cartão que adiciona, cada revisão que registra, cada ease factor que ajusta reforça o que você está tentando aprender.

O código completo está aí em cima. Copia, cola, roda. Adiciona seus cartões. Revisa todo dia. Em um mês você volta aqui e me conta quanto mudou.

Eu aposto que mudou tudo.

Posts Similares