import random from collections import Counter from typing import Dict, Any, List, Tuple def recommend_numbers( draws: List[Tuple[int, List[int]]], *, recent_window: int = 200, recent_weight: float = 2.0, avoid_recent_k: int = 5, seed: int | None = None, ) -> Dict[str, Any]: """ 가벼운 통계 기반 추천: - 전체 빈도 + 최근(recent_window) 빈도에 가중치를 더한 가중 샘플링 - 최근 avoid_recent_k 회차에 나온 번호는 확률을 낮춤(완전 제외는 아님) """ if seed is not None: random.seed(seed) # 전체 빈도 all_nums = [n for _, nums in draws for n in nums] freq_all = Counter(all_nums) # 최근 빈도 recent = draws[-recent_window:] if len(draws) >= recent_window else draws recent_nums = [n for _, nums in recent for n in nums] freq_recent = Counter(recent_nums) # 최근 k회차 번호(패널티) last_k = draws[-avoid_recent_k:] if len(draws) >= avoid_recent_k else draws last_k_nums = set(n for _, nums in last_k for n in nums) # 가중치 구성 weights = {} for n in range(1, 46): w = freq_all[n] + recent_weight * freq_recent[n] if n in last_k_nums: w *= 0.6 # 최근에 너무 방금 나온 건 살짝 덜 뽑히게 weights[n] = max(w, 0.1) # 중복 없이 6개 뽑기(가중 샘플링) chosen = [] pool = list(range(1, 46)) for _ in range(6): total = sum(weights[n] for n in pool) r = random.random() * total acc = 0.0 for n in pool: acc += weights[n] if acc >= r: chosen.append(n) pool.remove(n) break chosen_sorted = sorted(chosen) explain = { "recent_window": recent_window, "recent_weight": recent_weight, "avoid_recent_k": avoid_recent_k, "top_all": [n for n, _ in freq_all.most_common(10)], "top_recent": [n for n, _ in freq_recent.most_common(10)], "last_k_draws": [d for d, _ in last_k], } return {"numbers": chosen_sorted, "explain": explain} def recommend_with_heatmap( draws: List[Tuple[int, List[int]]], past_recommendations: List[Dict[str, Any]], *, heatmap_window: int = 10, heatmap_weight: float = 1.5, recent_window: int = 200, recent_weight: float = 2.0, avoid_recent_k: int = 5, seed: int | None = None, ) -> Dict[str, Any]: """ 히트맵 기반 가중치 추천: - 과거 추천 번호들의 적중률을 분석하여 잘 맞춘 번호에 가중치 부여 - 기존 통계 기반 추천과 결합 Args: draws: 실제 당첨 번호 리스트 [(회차, [번호들]), ...] past_recommendations: 과거 추천 데이터 [{"numbers": [...], "correct_count": N, "based_on_draw": M}, ...] heatmap_window: 히트맵 분석할 최근 추천 개수 heatmap_weight: 히트맵 가중치 (높을수록 과거 적중 번호 선호) """ if seed is not None: random.seed(seed) # 1. 기존 통계 기반 가중치 계산 all_nums = [n for _, nums in draws for n in nums] freq_all = Counter(all_nums) recent = draws[-recent_window:] if len(draws) >= recent_window else draws recent_nums = [n for _, nums in recent for n in nums] freq_recent = Counter(recent_nums) last_k = draws[-avoid_recent_k:] if len(draws) >= avoid_recent_k else draws last_k_nums = set(n for _, nums in last_k for n in nums) # 2. 히트맵 생성: 과거 추천에서 적중한 번호들의 빈도 heatmap = Counter() recent_recs = past_recommendations[-heatmap_window:] if len(past_recommendations) >= heatmap_window else past_recommendations for rec in recent_recs: if rec.get("correct_count", 0) > 0: # 적중한 추천만 # 적중 개수에 비례해서 가중치 부여 (많이 맞춘 추천일수록 높은 가중) weight = rec["correct_count"] ** 1.5 # 제곱으로 강조 for num in rec["numbers"]: heatmap[num] += weight # 3. 최종 가중치 = 기존 통계 + 히트맵 weights = {} for n in range(1, 46): w = freq_all[n] + recent_weight * freq_recent[n] # 히트맵 가중치 추가 if n in heatmap: w += heatmap_weight * heatmap[n] # 최근 출현 번호 패널티 if n in last_k_nums: w *= 0.6 weights[n] = max(w, 0.1) # 4. 가중 샘플링으로 6개 선택 chosen = [] pool = list(range(1, 46)) for _ in range(6): total = sum(weights[n] for n in pool) r = random.random() * total acc = 0.0 for n in pool: acc += weights[n] if acc >= r: chosen.append(n) pool.remove(n) break chosen_sorted = sorted(chosen) # 5. 설명 데이터 explain = { "recent_window": recent_window, "recent_weight": recent_weight, "avoid_recent_k": avoid_recent_k, "heatmap_window": heatmap_window, "heatmap_weight": heatmap_weight, "top_all": [n for n, _ in freq_all.most_common(10)], "top_recent": [n for n, _ in freq_recent.most_common(10)], "top_heatmap": [n for n, _ in heatmap.most_common(10)], "last_k_draws": [d for d, _ in last_k], "analyzed_recommendations": len(recent_recs), } return {"numbers": chosen_sorted, "explain": explain}