로또 번호 생성기

조합 1: 03 07 13 21 25 28 (핫 넘버: 1개)
조합 2: 04 19 20 21 38 45 (핫 넘버: 1개)
조합 3: 02 03 13 16 38 43 (핫 넘버: 1개)
조합 4: 10 13 14 31 33 35 (핫 넘버: 3개)
조합 5: 06 09 13 18 38 40 (핫 넘버: 3개)

* 무작위로 생성된 번호이며, 당첨을 보장하지 않습니다.

#!/opt/bitnami/venvs/lotto/bin/python3
import random
import pandas as pd
import pymysql
import os
from dotenv import load_dotenv
from collections import Counter

class LottoGenerator:
    def __init__(self):
        # 데이터베이스 연결 정보 로드
        load_dotenv()
        self.db_config = {
            'host': os.getenv('DB_HOST'),
            'port': int(os.getenv('DB_PORT', 3306)),
            'user': os.getenv('DB_USER'),
            'password': os.getenv('DB_PASSWORD'),
            'db': os.getenv('DB_NAME'),
            'charset': 'utf8mb4',
            'cursorclass': pymysql.cursors.DictCursor
        }
        self.lotto_data = None
        self.number_frequency = None
        self.pair_frequency = None
        
    def load_lotto_data(self):
        """데이터베이스에서 로또 당첨 번호 데이터 로드"""
        try:
            conn = pymysql.connect(**self.db_config)
            
            with conn.cursor() as cursor:
                sql = "SELECT count, `1`,`2`,`3`,`4`,`5`,`6` FROM lotto ORDER BY count ASC"
                cursor.execute(sql)
                rows = cursor.fetchall()
                
                self.lotto_data = pd.DataFrame(rows)
                print(f"데이터베이스에서 {len(self.lotto_data)}개의 로또 당첨 번호를 로드했습니다.")
                return True
                
        except Exception as e:
            print(f"데이터베이스 연결 오류: {str(e)}")
            return False
    
    def analyze_statistics(self):
        """당첨 번호 통계 분석"""
        if self.lotto_data is None:
            if not self.load_lotto_data():
                print("통계 분석을 위한 데이터 로드에 실패했습니다.")
                return False
        
        # 모든 당첨 번호를 하나의 리스트로 변환
        all_numbers = []
        for _, row in self.lotto_data.iterrows():
            all_numbers.extend([row['1'], row['2'], row['3'], row['4'], row['5'], row['6']])
        
        # 번호별 출현 빈도 계산
        self.number_frequency = Counter(all_numbers)
        
        # 번호 쌍(페어) 빈도 분석
        pairs = []
        for _, row in self.lotto_data.iterrows():
            numbers = [row['1'], row['2'], row['3'], row['4'], row['5'], row['6']]
            for i in range(len(numbers)):
                for j in range(i+1, len(numbers)):
                    pairs.append((min(numbers[i], numbers[j]), max(numbers[i], numbers[j])))
        
        self.pair_frequency = Counter(pairs)
        return True
    
    def calculate_weights(self):
        """번호별 가중치 계산"""
        if self.number_frequency is None:
            self.analyze_statistics()
        
        weights = {}
        max_freq = max(self.number_frequency.values())
        min_freq = min(self.number_frequency.values())
        
        for num in range(1, 46):
            freq = self.number_frequency.get(num, 0)
            # 빈도수에 따른 가중치 계산 (1-3 범위로 정규화)
            if max_freq > min_freq:
                weight = 1 + 2 * (freq - min_freq) / (max_freq - min_freq)
            else:
                weight = 1.0
            weights[num] = weight
        
        return weights
    
    def get_hot_numbers(self, count=10):
        """자주 나오는 번호 추출"""
        if self.number_frequency is None:
            self.analyze_statistics()
        
        return [num for num, _ in self.number_frequency.most_common(count)]
    
    def get_top_pairs(self, count=10):
        """자주 함께 나오는 번호 쌍 추출"""
        if self.pair_frequency is None:
            self.analyze_statistics()
        
        return [pair for pair, _ in self.pair_frequency.most_common(count)]
    
    def weighted_sample(self, population, weights, k):
        """가중치 기반 샘플링"""
        population_list = list(population)
        weights_list = [weights.get(i, 1.0) for i in population_list]
        return random.choices(population_list, weights=weights_list, k=k)
    
    def generate_numbers(self, games=5):
        """통계 기반 로또 번호 생성"""
        weights = self.calculate_weights()
        hot_numbers = self.get_hot_numbers()
        top_pairs = self.get_top_pairs()
        
        results = []
        for _ in range(games):
            numbers = []
            
            # 70% 확률로 인기 페어 중 하나를 우선 포함
            if top_pairs and random.random() < 0.7:
                pair = random.choice(top_pairs)
                numbers.extend(pair)
            
            # 나머지 번호 선택
            remaining = 6 - len(numbers)
            candidates = [n for n in range(1, 46) if n not in numbers]
            selected = self.weighted_sample(candidates, weights, remaining)
            numbers.extend(selected)
            
            # 중복 제거 및 부족한 숫자 추가
            numbers = list(set(numbers))
            while len(numbers) < 6:
                additional = self.weighted_sample(range(1, 46), weights, 1)[0]
                if additional not in numbers:
                    numbers.append(additional)
            
            # 정렬
            numbers.sort()
            results.append(numbers)
        
        return results, hot_numbers

def generate_lotto():
    generator = LottoGenerator()
    results, hot_numbers = generator.generate_numbers()
    
    print("\n통계 기반 로또 번호 추천")
    print("-" * 30)
    
    for i, numbers in enumerate(results, 1):
        hot_count = sum(1 for num in numbers if num in hot_numbers)
        print(f"조합 {i}: {' '.join(f'{num:02d}' for num in numbers)} (핫 넘버: {hot_count}개)")
    
    print("-" * 30)
    print(f"핫 넘버 (상위 10개): {', '.join(f'{num:02d}' for num in hot_numbers)}")

if __name__ == "__main__":
    generate_lotto()