Boa tarde Rafaela, tudo bem?
Após uma breve pesquisa cheguei nessa atualização:
import heapq
from typing import List, Dict, Optional
class Curso:
"""Representa um nó no grafo de rec. de cursos."""
def __init__(self, id_curso: str, nome: str, conversao: float, taxa_conclusao: float = 0.8):
self.id = id_curso
self.nome = nome
self.conversao = conversao # Métrica 1: Conversão histórica
self.taxa_conclusao = taxa_conclusao # Métrica 2: Quantos alunos terminam
# Método (heapq)
# Garante desempate correto na heap de menor custo
def __lt__(self, outro: 'Curso') -> bool:
return self.conversao > outro.conversao
def __repr__(self):
return f"{self.nome}"
class RecomendacaoTrilha:
"""Gerencia o grafo de con entre cursos e executa o algoritmo A*."""
def __init__(self):
# Inicializa o dicionário do grafo
self.grafo: Dict[Curso, List[Curso]] = {}
def adicionar_conexao(self, origem: Curso, destino: Curso):
"""
MELHORIA 1:
"""
self.grafo.setdefault(origem, []).append(destino)
def heuristica(self, curso: Curso) -> float:
"""
MELHORIA 2:
"""
score_combinado = (curso.conversao * 0.7) + (curso.taxa_conclusao * 0.3)
return -score_combinado
def a_star(self, inicio: Curso, objetivo: Curso) -> List[Curso]:
fila = []
# Estrutura na Heap: (f_score, g_score, nó_atual)
heapq.heappush(fila, (0 + self.heuristica(inicio), 0, inicio))
caminhos: Dict[Curso, Curso] = {}
custo_acumulado: Dict[Curso, int] = {inicio: 0}
encontrou_caminho = False
while fila:
_, g, atual = heapq.heappop(fila)
# Se alcançou o objetivo
if atual == objetivo:
encontrou_caminho = True
break
# Recupera os vizinhos
for vizinho in self.grafo.get(atual, []):
novo_custo_g = g + 1 # Cada transição de curso tem custo 1
# Caminho até o vizinho for inédito ou mais curto
if vizinho not in custo_acumulado or novo_custo_g < custo_acumulado[vizinho]:
custo_acumulado[vizinho] = novo_custo_g
# f(n) = g(n) + h(n)
f = novo_custo_g + self.heuristica(vizinho)
heapq.heappush(fila, (f, novo_custo_g, vizinho))
caminhos[vizinho] = atual
if not encontrou_caminho:
return []
return self._reconstruir_caminho(caminhos, inicio, objetivo)
def _reconstruir_caminho(self, caminhos: Dict[Curso, Curso], inicio: Curso, objetivo: Curso) -> List[Curso]:
"""Reconstrói a trilha do objetivo até o início e inverte o resultado."""
atual = objetivo
trilha = [atual]
while atual in caminhos:
atual = caminhos[atual]
trilha.append(atual)
trilha.reverse()
return trilha
# ====================
# TESTANDO A SOLUÇÃO
# ====================
# Instanciando cursos
c1 = Curso("C1", "Introdução ao Ubuntu", conversao=0.9, taxa_conclusao=0.95)
c2 = Curso("C2", "Configuração do WSL2", conversao=0.8, taxa_conclusao=0.85)
c3 = Curso("C3", "Automação com Shell Script", conversao=0.7, taxa_conclusao=0.75)
c4 = Curso("C4", "Criptografia com OpenSSL", conversao=0.6, taxa_conclusao=0.60)
c5 = Curso("C5", "Fundamentos de Cibersegurança", conversao=0.85, taxa_conclusao=0.90)
# Criando o sistema
sistema = RecomendacaoTrilha()
# Montando o grafo
sistema.adicionar_conexao(c1, c2)
sistema.adicionar_conexao(c1, c3)
sistema.adicionar_conexao(c2, c3)
sistema.adicionar_conexao(c2, c4)
sistema.adicionar_conexao(c3, c5)
sistema.adicionar_conexao(c5, c4)
# Executando a busca
trilha_recomendada = sistema.a_star(c1, c4)
print("--- Trilha de Aprendizado Recomendada (A*) ---")
for i, curso in enumerate(trilha_recomendada, start=1):
print(f"Passo {i}: {curso.nome}")
- Uso do setdefault() para montagem dinâmica do Grafo
Código antigo: O grafo era um dicionário engessado e definido manualmente.
Código novo: Uso do self.grafo.setdefault(origem, []).append(destino).
Vantagem: Permite alimentar o grafo dinamicamente a partir de consultas em BD ou arquivos JSON, sem precisar fazer validações manuais com if chave not in dicionario:.
- Heurística Enriquecida
Código antigo: Avaliava apenas a variável conversao.
Código novo: Adicionado uma segunda variável (taxa_conclusao) e calculamos uma média ponderada.
- Tipagem e Segurança de Código
Adicionamos Type Hints (Dict[Curso, List[Curso]]), facilitando a leitura e validação automática do código por IDEs (como VS Code ou PyCharm).
Obs.: Apaguei uma parte dos comentários para caber na resposta