164 lines
5.5 KiB
Python
164 lines
5.5 KiB
Python
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}
|
|
|