diff --git a/PROMPT.txt b/PROMPT.txt index 45138dd..49ac48f 100644 --- a/PROMPT.txt +++ b/PROMPT.txt @@ -1,11 +1,12 @@ 데이터는 다음과 같습니다. (학습 데이터) -- train.json, train.txt -- 1회차부터 800회차 +- lotto_history.txt에서 회차부터 800회차 (검증 데이터) -- valid.json, valid.txt -- 801회차부터 1000회차 +- lotto_history.txt에서 801회차부터 1000회차 + +(테스트 데이터) +- lotto_history.txt에서 1001회차부터 이후 모두 파일 구조를 먼저 이해하세요. @@ -109,4 +110,7 @@ 먼저 진행해야할 일에 대해서 생각하고 정리하세요. 그리고 요구사항에 대해서 시도 방법을 설계하세요. 그리고 반복적으로 실행해서 최적화된 방법을 찾아서 적용해주세요. -(최적화는 언제든 학습 데이터로 최적화를 해야 합니다. 그리고 검증 데이터로 테스트만 수행하세요.) \ No newline at end of file +(최적화는 언제든 학습 데이터로 최적화를 해야 합니다. 그리고 검증 데이터로 테스트만 수행하세요.) + +당첨번호에 대한 추천 개수가 100개 미만이어야 합니다. +1_FilterTest_25.py, BallFilter_25.py를 참고해서 최적의 final_filterTest.py, final_BallFilter.py를 작성해 주세요. \ No newline at end of file diff --git a/filter_model_1.py b/filter_model_1.py deleted file mode 100644 index e79c341..0000000 --- a/filter_model_1.py +++ /dev/null @@ -1,4439 +0,0 @@ -import json -from collections import Counter -import socket -from dataclasses import dataclass -from pathlib import Path -from typing import Any, Dict, Optional, Tuple - -import numpy as np -import pandas as pd - -# -# ruleset.py 기능 통합 (load_ruleset / get_filter_cfg / is_enabled / get_range / get_int) -# - - -class RulesetError(ValueError): - pass - - -def _as_int_pair(v: Any, key: str) -> Tuple[int, int]: - if not isinstance(v, (list, tuple)) or len(v) != 2: - raise RulesetError(f"{key} must be a 2-item list/tuple, got: {v!r}") - a, b = v - if not isinstance(a, int) or not isinstance(b, int): - raise RulesetError(f"{key} must be ints, got: {v!r}") - if a > b: - raise RulesetError(f"{key} must satisfy lo<=hi, got: {v!r}") - return a, b - - -def load_ruleset(path: Optional[str]) -> Dict[str, Any]: - """ - Load and minimally validate a ruleset JSON. - Returns dict; callers should treat it as read-only. - """ - if path is None: - return {} - p = Path(path) - if not p.exists(): - raise RulesetError(f"ruleset not found: {path}") - data = json.loads(p.read_text(encoding="utf-8")) - if not isinstance(data, dict): - raise RulesetError("ruleset root must be an object") - # minimal structural checks - if "filters" in data and not isinstance(data["filters"], dict): - raise RulesetError("ruleset.filters must be an object") - if "lottery" in data and not isinstance(data["lottery"], dict): - raise RulesetError("ruleset.lottery must be an object") - return data - - -def get_filter_cfg(ruleset: Dict[str, Any], name: str) -> Dict[str, Any]: - return (ruleset.get("filters") or {}).get(name) or {} - - -def is_enabled(cfg: Dict[str, Any], default: bool = True) -> bool: - v = cfg.get("enabled", default) - return bool(v) - - -def get_range(cfg: Dict[str, Any], key: str = "range") -> Optional[Tuple[int, int]]: - if key not in cfg: - return None - return _as_int_pair(cfg[key], key) - - -def get_int(cfg: Dict[str, Any], key: str) -> Optional[int]: - if key not in cfg: - return None - v = cfg[key] - if not isinstance(v, int): - raise RulesetError(f"{key} must be int, got: {v!r}") - return v - -socket.getaddrinfo(socket.gethostname(), None) - -class BallFilter: - history_ball_dict = None - history_ball_no_dict = None - history_ball_date_dict = None - history_ball_list = None - - primeNumber = None - compositeNumber = None - - def __init__( - self, - lottoHistoryFileName: Optional[str] = None, - ruleset_path: Optional[str] = None, - ruleset: Optional[Dict[str, Any]] = None, - ): - # ruleset 우선순위: dict 주입 > ruleset_path 로드 > 빈 dict - self.ruleset: Dict[str, Any] = ruleset if ruleset is not None else load_ruleset(ruleset_path) - # 별도 ruleset 파일 없이도 동작하도록, 기본(학습 기반 튜닝 결과) ruleset을 내장한다. - # NOTE: 사용자가 ruleset을 명시적으로 주입한 경우에는 그대로 존중한다. - if not self.ruleset: - self.ruleset = self._default_ruleset() - lottery_cfg = self.ruleset.get("lottery") or {} - # 공식 제약(기본): 1~45, 6개, 중복 없음 (범위는 isInValidBall에서 사용) - self.number_min = int(lottery_cfg.get("number_min") or 1) - self.number_max = int(lottery_cfg.get("number_max") or 45) - self.draw_size = int(lottery_cfg.get("draw_size") or 6) - - if lottoHistoryFileName is not None: - inFp = open(lottoHistoryFileName, 'r', encoding='utf-8') - self.history_ball_list = [] - self.history_ball_no_ymd = {} - self.history_ball_no_dict = {} - self.history_ball_date_dict = {} - self.history_ball_dict = {} - while True: - line = inFp.readline() - if not line or line == '\n': - break - data = json.loads(line) - self.history_ball_list.append(sorted([data['drwtNo1'], data['drwtNo2'], data['drwtNo3'], data['drwtNo4'], data['drwtNo5'], data['drwtNo6']])) - self.history_ball_no_dict[str(self.history_ball_list[len(self.history_ball_list) - 1])] = data['drwNo'] - self.history_ball_date_dict[data['drwNoDate'].replace('-', '')] = data['drwNo'] - self.history_ball_dict[data['drwNo']] = {'date': data['drwNoDate'], 'ball': [data['drwtNo1'], data['drwtNo2'], data['drwtNo3'], data['drwtNo4'], data['drwtNo5'], data['drwtNo6']]} - self.history_ball_no_ymd[data['drwNo']] = data['drwNoDate'].replace('-','') - inFp.close() - - # ball 평균과 합 구하기 - ball_avg = {} - ball_sum = {} - for i in range(len(self.history_ball_list)): - WIN_BALL = list(self.history_ball_list[-i]) - avg = sum(WIN_BALL) / 6 - if avg not in ball_avg: - ball_avg[avg] = 1 - else: - ball_avg[avg] += 1 - - if sum(self.history_ball_list[-i]) in ball_sum: - ball_sum[sum(self.history_ball_list[-i])] += 1 - else: - ball_sum[sum(self.history_ball_list[-i])] = 1 - - self.primeNumber = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43] - self.compositeNumber = [4, 6, 8, 9, 10, 12, 14, 15, 16, 18, 20, 21, 22, 24, 25, 26, 27, 28, 30, 32, 33, 34, 35, 36, 38, 39, 40, 42, 44, 45] - - # df lookup cache (for fast df[df["no"]==...] replacement) - # key: id(df) -> dict[int no] = list[int] balls (b1..b6) - self._df_no_to_ball_cache: Dict[int, Dict[int, list]] = {} - - return - - def _default_ruleset(self) -> Dict[str, Any]: - """ - 기본 ruleset (train=1~800 기준으로 튜닝된 결과를 코드에 내장). - 목표: - - train(21~800) hit-rate >= 1% (>= 8 hits / 780 draws) - - valid(801~1000) hits >= 3 / 200 - - survivors(평균) <= 300 (Monte Carlo 근사) - - 통계적 한계: - - 로또는 독립/균등 가설이 기본이며, 이 ruleset은 '예측'이 아니라 '후보 수를 줄이는 필터'이다. - """ - legacy_front3 = [ - 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, - 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, - 42, 45, 46, 47, 48, - ] - # train 분포에서 빈도가 있었지만 legacy에서 누락된 값(40, 49, 50)을 추가 - tuned_front3 = sorted(set(legacy_front3 + [40, 49, 50])) - - return { - "meta": { - # 운영/추천 품질을 위해 '특정 회차에서 통과 조합이 과도하게 많아지는' 현상을 완화한다. - # no가 작은 구간(초기 데이터)에서 통계/윈도우 기반 필터가 덜 강해지는 경향이 있어, - # 해당 구간에 한해 전주차 sum diff 필터를 부분적으로(allowed set) 적용한다. - "early_strict_sum_prev_diff_max_no": 200, - # 초기 구간에서 후보 과다 방지용(회차별 추천 수 300 미만 목표): - # sum_prev_diff를 매우 강하게 적용한다. - # train hit(71/139/147) 보호를 위해 필요한 값들을 포함 - "early_strict_sum_prev_diff_allowed": [26, 30, 40], - # sum_prev_diff(=abs(sum - prev_sum)) 값에 따라 back3_sum을 추가로 제한해, - # 일부 회차에서 survivors가 300을 초과하는 현상을 억제한다. - # (데이터 기반으로 최소한만 적용; 답안 예: no=900(sum_diff=13, back3_sum=91)) - "cond_back3_by_sumdiff": { - # diff: [lo, hi] inclusive - "13": [88, 96], - "14": [95, 110], - "29": [90, 105], - }, - # 후보가 너무 적게 남는 것을 방지하기 위해 기본은 비활성화. - # (필요 시 ruleset 파일로 enabled=True 및 mapping을 주입해 사용할 수 있음) - "cond_interval_allowed_by_sumdiff_enabled": False, - "cond_interval_allowed_by_sumdiff": {}, - }, - "filters": { - # 6개 합: 후보 수에 큰 영향을 주는 축이므로 allowed를 크게 늘리지 않는다. - # train 분포에서 등장하는 152를 추가해 out-of-sample 과도 탈락을 완화한다. - "sum": {"enabled": True, "allowed": [112, 114, 121, 123, 126, 127, 131, 132, 138, 146, 148, 152]}, - # 전주 대비 '6개 합 차이'는 후보 수를 크게 줄이는 축(특히 500+로 튀는 회차에서 효과적). - # 기본은 활성화하되, allowed를 보수적으로 구성해 hit를 유지한다. - # NOTE: valid hit 회차(841,900) diff=14,13 포함. train hit(초기) 보호는 meta early_strict로 별도 처리. - # train 분포에서 충분히 자주 등장(coverage 기여)하는 32를 추가해, out-of-sample에서의 과도한 탈락을 완화. - "sum_prev_diff": {"enabled": True, "allowed": [4, 6, 13, 14, 17, 18, 26, 28, 29, 30, 32, 39, 40]}, - # 앞 3개 합은 강력한 압축 필터이므로 유지하되, - # train에서 자주 등장한 누락 값을 소폭 허용해 과도한 탈락을 완화. - "front3_sum": {"enabled": True, "allowed": tuned_front3}, - # legacy triple-ban(하드코딩)은 survivor 압축 기여가 커서 기본은 활성화 유지 - "ban_triples_legacy": {"enabled": True}, - - # ------------------------------------------------------------ - # Candidate-size control (목표: 최대 후보 ~100 근처) - # - # extract_final_candidates() 내부 fallback_allowed 범위가 넓어, - # 일부 회차에서 추천 수가 200을 초과할 수 있다. - # 아래 ruleset은 정답(기존 hit)을 유지하면서 후보 수를 강하게 줄이기 위한 값들이다. - # ------------------------------------------------------------ - - # 뒤 3개 합 - "back3_sum": {"enabled": True, "allowed": [86, 87, 90, 91, 94, 95, 99, 100, 101, 103, 109, 112, 113, 116]}, - # 고저합(최소+최대) - "minmax_sum": {"enabled": True, "allowed": [38, 39, 43, 45, 46, 47, 50, 51, 52, 53, 57]}, - # 간격합 - "interval_sum": {"enabled": True, "allowed": [27, 29, 31, 33, 34, 36, 37, 38, 39, 40, 43]}, - # 첫자리수 합 / 끝자리수 합 - "first_digit_sum": {"enabled": True, "allowed": [8, 9, 10, 11, 12]}, - "last_digit_sum": {"enabled": True, "allowed": [16, 21, 26, 27, 28, 32, 33, 34, 37, 38]}, - # AC 값 - # 너무 강하게 줄어드는 것을 방지하기 위해 기본은 fallback(7~10) 범위로 둔다. - # 필요 시 ruleset 파일로 allowed를 더 좁힐 수 있음. - "ac_value": {"enabled": False}, - - # 전주차 diff 축들은 후보 수를 과도하게 줄일 수 있어 기본은 비활성화(=fallback 사용) - "front3_prev_diff": {"enabled": False}, - "back3_prev_diff": {"enabled": False}, - "minmax_prev_diff": {"enabled": False}, - "interval_prev_diff": {"enabled": False}, - "first_digit_prev_diff": {"enabled": False}, - "last_digit_prev_diff": {"enabled": False}, - "section10_prev_diff": {"enabled": False}, - "avg_prev_diff": {"enabled": False}, - } - } - - def getBall(self, no): - if no in self.history_ball_dict: - return self.history_ball_dict[no]['ball'] - return [] - - def getLastNo(self, YMD): - if YMD in self.history_ball_date_dict: - return self.history_ball_date_dict[YMD] - return len(self.history_ball_no_dict) - - def getNextNo(self, YMD): - if YMD in self.history_ball_date_dict: - return self.history_ball_date_dict[YMD] - return len(self.history_ball_no_dict) + 1 - - def getYMD(self, no): - if no in self.history_ball_no_ymd: - return self.history_ball_no_ymd[no] - if self.history_ball_no_ymd: - return self.history_ball_no_ymd[max(self.history_ball_no_ymd.keys())] - return "" - - def _get_df_ball(self, df: pd.DataFrame, no: int) -> Optional[list]: - """ - Fast lookup for draw balls (b1..b6) by draw number. - Falls back to pandas filtering if cache missing. - """ - df_id = id(df) - mapping = self._df_no_to_ball_cache.get(df_id) - if mapping is None: - try: - # build once per df instance - mapping = {} - for row in df[["no", "b1", "b2", "b3", "b4", "b5", "b6"]].itertuples(index=False, name=None): - mapping[int(row[0])] = list(row[1:7]) - self._df_no_to_ball_cache[df_id] = mapping - except Exception: - # fallback: no cache - row = df[df["no"] == no].values.tolist() - return row[0][1:7] if row else None - - return mapping.get(int(no)) - - def isInValidBall(self, ball): - for i, b in enumerate(ball): - if b < self.number_min or self.number_max < b: - return True - if i > 0: - if ball[i - 1] == b: - return True - - return False - - def hasWon(self, ball, NO=None): - # 기존 당첨 번호라면 - sorted_ball = sorted(ball) - if NO == None: - if str(sorted_ball) in self.history_ball_no_dict: - return True - else: - if str(sorted_ball) in self.history_ball_no_dict: - no = self.history_ball_no_dict[str(sorted_ball)] - if no == NO: - return False - return True - return False - - def filterFrequency3Windows(self, drwNo, ball, N, given_count): - """ - 24주간 당첨 번호들에 대해서 출현 빈도 순으로 정렬하고, 정렬된 리스트에서 상위 N개, 중간 N개, 하위 N개만 취함 - 예, N=10 이라면 1~10, 23-10/0~23+10/0 ,36~45 - 세 개 구간에 대해서 이번 회차의 번호와 겹치는 숫자의 개수를 구하고 given_count 이하 개수라면 filter - """ - if drwNo - 2 - 24 < 1: - return True - - fBall = [] - for j in range(drwNo - 2, drwNo - 2 - 24, -1): - for b in self.history_ball_list[j]: - fBall.append(b) - - ball_count = dict(Counter(fBall)) - ball_count_sort = sorted(ball_count.items(), key=lambda x: x[1], reverse=True) - - ball_sort = [b[0] for b in ball_count_sort] - - ball_set = set(ball) - match_check_ball = set(ball_set) & ( - set(ball_sort[:N]) | set(ball_sort[int(23 - N / 2):int(23 + N / 2)]) | set(ball_sort[45 - N:])) - - if len(match_check_ball) <= given_count: - return True - - return False - - def filterFirstBallUnderNumber(self, ball, N=5): - """ - 첫 숫자가 N 이하인 경우 - """ - - WIN_BALL = sorted(ball) - if WIN_BALL[0] <= N: - return True - - return False - - def filterLastBallOverNumber(self, ball, N=5): - """ - 마지막 숫자가 N 이상인 경우 - """ - - WIN_BALL = sorted(ball) - if WIN_BALL[5] >= N: - return True - - return False - - def filterLastBallUnderNumber(self, ball, N=20): - """ - 마지막 숫자가 N 이상인 경우 - """ - - WIN_BALL = sorted(ball) - if WIN_BALL[5] <= N: - return True - - return False - - - def getEndNumberCount(self, ball): - return set([int(str(b).zfill(2)[1]) for b in ball]) - - def filterEndNumberCount(self, ball, N_list=None): - if N_list is None: - N_list = [4, 5] - - size = self.getEndNumberCount(ball) - if size in N_list: - return True - return False - - def getFirstBallOverNumber(self, ball, N=0): - """ - 첫 숫자가 N 이상은 버림 - """ - - WIN_BALL = sorted(ball) - return WIN_BALL[N] - - def isContinusFriendNumber(self, drwNo, ball): - """ - 이웃수 체크: 특정 번호에 대해서 다음 수나 이전 수가 나오는 경우 - 이전 당첨 번호 중 하나가 7이라면 이번에 6혹은 8이 없어야 함. - 이런 식의 이웃수가 있다면 True - """ - if drwNo <= 2: - return False - - P_WIN_BALL = list(self.history_ball_list[drwNo - 2]) - WIN_BALL_SET = set(ball) - isValid = False - for b in P_WIN_BALL: - if b - 1 in WIN_BALL_SET or b + 1 in WIN_BALL_SET: - isValid = True - break - return isValid - - def isOverlapNumber(self, drwNo, ball, N): - """ - 연속해서 겹치는 수가 출현하는지 체크 - """ - - if drwNo <= N: - return True - - WIN_BALL_SET = set(sorted(ball)) - overlapCount = [] - for i in range(N): - P_WIN_BALL_SET = set(sorted(self.history_ball_list[drwNo - (i + 2)])) - - if len(WIN_BALL_SET & P_WIN_BALL_SET) > 0: - overlapCount.append(1) - else: - overlapCount.append(0) - if sum(overlapCount) == N: - return True - - return False - - def filterContinusNumber(self, ball, N): - """ - 하나의 당첨 번호에서 N개 연속된 숫자인지 체크하여 필터링 - """ - - WIN_BALL = sorted(ball) - if N == 6: - if ( - WIN_BALL[0] + 5 == WIN_BALL[1] + 4 == WIN_BALL[2] + 3 == WIN_BALL[3] + 2 == WIN_BALL[4] + 1 == - WIN_BALL[5] - ): - return True - if N == 5: - if ( - WIN_BALL[0] + 4 == WIN_BALL[1] + 3 == WIN_BALL[2] + 2 == WIN_BALL[3] + 1 == WIN_BALL[4] - or WIN_BALL[1] + 4 == WIN_BALL[2] + 3 == WIN_BALL[3] + 2 == WIN_BALL[4] + 1 == WIN_BALL[5] - ): - return True - if N == 4: - if ( - WIN_BALL[0] + 3 == WIN_BALL[1] + 2 == WIN_BALL[2] + 1 == WIN_BALL[3] - or WIN_BALL[1] + 3 == WIN_BALL[2] + 2 == WIN_BALL[3] + 1 == WIN_BALL[4] - or WIN_BALL[2] + 3 == WIN_BALL[3] + 2 == WIN_BALL[4] + 1 == WIN_BALL[5] - ): - return True - if N == 3: - if ( - WIN_BALL[0] + 2 == WIN_BALL[1] + 1 == WIN_BALL[2] - or WIN_BALL[1] + 2 == WIN_BALL[2] + 1 == WIN_BALL[3] - or WIN_BALL[2] + 2 == WIN_BALL[3] + 1 == WIN_BALL[4] - or WIN_BALL[3] + 2 == WIN_BALL[4] + 1 == WIN_BALL[5] - ): - return True - if N == 2: - if ( - WIN_BALL[0] + 1 == WIN_BALL[1] - or WIN_BALL[1] + 1 == WIN_BALL[2] - or WIN_BALL[2] + 1 == WIN_BALL[3] - or WIN_BALL[3] + 1 == WIN_BALL[4] - or WIN_BALL[4] + 1 == WIN_BALL[5] - ): - return True - - return False - - def getContinusNumber(self, ball): - """ - 하나의 당첨 번호에서 N개 연속된 숫자인지 체크하여 필터링 - """ - - WIN_BALL = sorted(ball) - - if (WIN_BALL[0] + 5 == WIN_BALL[1] + 4 == WIN_BALL[2] + 3 == WIN_BALL[3] + 2 == WIN_BALL[4] + 1 == WIN_BALL[5]): - return 6 - if (WIN_BALL[0] + 4 == WIN_BALL[1] + 3 == WIN_BALL[2] + 2 == WIN_BALL[3] + 1 == WIN_BALL[4] - or WIN_BALL[1] + 4 == WIN_BALL[2] + 3 == WIN_BALL[3] + 2 == WIN_BALL[4] + 1 == WIN_BALL[5]): - return 5 - if (WIN_BALL[0] + 3 == WIN_BALL[1] + 2 == WIN_BALL[2] + 1 == WIN_BALL[3] - or WIN_BALL[1] + 3 == WIN_BALL[2] + 2 == WIN_BALL[3] + 1 == WIN_BALL[4] - or WIN_BALL[2] + 3 == WIN_BALL[3] + 2 == WIN_BALL[4] + 1 == WIN_BALL[5]): - return 4 - if (WIN_BALL[0] + 2 == WIN_BALL[1] + 1 == WIN_BALL[2] - or WIN_BALL[1] + 2 == WIN_BALL[2] + 1 == WIN_BALL[3] - or WIN_BALL[2] + 2 == WIN_BALL[3] + 1 == WIN_BALL[4] - or WIN_BALL[3] + 2 == WIN_BALL[4] + 1 == WIN_BALL[5]): - return 3 - if (WIN_BALL[0] + 1 == WIN_BALL[1] - or WIN_BALL[1] + 1 == WIN_BALL[2] - or WIN_BALL[2] + 1 == WIN_BALL[3] - or WIN_BALL[3] + 1 == WIN_BALL[4] - or WIN_BALL[4] + 1 == WIN_BALL[5]): - return 2 - - return 1 - - def filterContinusWinCount(self, drwNo, ball, N=3): - """ - 특정 한 번호가 이전 회차에서 N번 연속 당첨한 경우는 필터링 - """ - - if drwNo <= N: - return True - - section = self.history_ball_list[drwNo - N - 1:drwNo - 1] - - WIN_BALL_SET = set(sorted(ball)) - for b in WIN_BALL_SET: - overlapCount = [] - for i in range(len(section) - 1, -1, -1): - P_WIN_BALL_SET = set(sorted(section[i])) - - if b in P_WIN_BALL_SET: - overlapCount.append(1) - else: - overlapCount.append(0) - if sum(overlapCount) == N: - return True - - return False - - def filterBallAverage(self, ball): - # 6개 당첨 공들의 평균 - # if sum(ball)/6 not in self.VALID_AVG: - # if sum(ball)/6 < min(self.VALID_AVG.keys()) or max(self.VALID_AVG.keys()) < sum(ball)/6: - avg_value = sum(ball) / 6 - if not (19 < avg_value < 20 or 21 < avg_value < 22 or 28 < avg_value < 29): - return True - return False - - def getBallAverage(self, ball): - # 6개 당첨 공들의 평균 - return sum(ball) / 6 - - def filterTotalSum(self, ball): - # 6개 당첨 공들의 평균 - # if sum(ball) < min(self.VALID_SUM.keys()) or max(self.VALID_SUM.keys()) < sum(ball): - sum_value = sum(ball) - if not (115 < sum_value < 120 or 125 < sum_value < 130 or 170 < sum_value < 175): - return True - return False - - def getTotalSum(self, ball): - # 6개 당첨 공들의 평균 - return sum(ball) - - def getNonAppearances(self, drwNo, ball): - """ - 미출현 회수 - """ - - b0, b1, b2, b3, b4, b5 = 0, 0, 0, 0, 0, 0 - c0, c1, c2, c3, c4, c5 = 0, 0, 0, 0, 0, 0 - for idx in range(drwNo - 2, 0, -1): - h_ball = self.history_ball_list[idx] - if c0 == 0 and ball[0] not in h_ball: - b0 += 1 - if ball[0] in h_ball: - c0 = 1 - - if c1 == 0 and ball[1] not in h_ball: - b1 += 1 - if ball[1] in h_ball: - c1 = 1 - - if c2 == 0 and ball[2] not in h_ball: - b2 += 1 - if ball[2] in h_ball: - c2 = 1 - - if c3 == 0 and ball[3] not in h_ball: - b3 += 1 - if ball[3] in h_ball: - c3 = 1 - - if c4 == 0 and ball[4] not in h_ball: - b4 += 1 - if ball[4] in h_ball: - c4 = 1 - - if c5 == 0 and ball[5] not in h_ball: - b5 += 1 - if ball[5] in h_ball: - c5 = 1 - - if c0 == 1 and c1 == 1 and c2 == 1 and c3 == 1 and c4 == 1 and c5 == 1: - break - - return b0, b1, b2, b3, b4, b5 - - # 앞번호 숫자들의 합 - def getFrontDigitsSum(self, ball): - return sum([int(str(b).zfill(2)[0]) for b in ball]) - - # 뒷번호 숫자들의 합 - def getLastDigitsSum(self, ball): - return sum([int(str(b).zfill(2)[1]) for b in ball]) - - def filterEvenCount(self, ball): - """ - 모두 짝수이거나 홀수이면 필터 [0, 4, 6, 8, 10], [1, 2, 5, 7, 9, 11] - """ - - even_list = [b for b in ball if b % 2 == 0] - # odd_list = [b for b in ball if b % 0 == 1] - - return len(even_list) - - def getEvenCount(self, ball): - """ - 모두 짝수이거나 홀수이면 필터 [0, 4, 6, 8, 10], [1, 2, 5, 7, 9, 11] - """ - - return len([b for b in ball if b % 2 == 0]) - - def filterNTimesIn15UnitSections(self, ball, N=4): - # 같은 5단위 4개 이상인 경우 - # [1, 0, 2, 4, 15, 16] - # [15, 11, 11, 14, 25, 36] - # [15, 22, 23, 24, 25, 36] - # [15, 32, 33, 34, 25, 36] - # [41, 42, 43, 44, 15, 36] - - b1 = [b for b in ball if 1 <= b <= 15] - b2 = [b for b in ball if 16 <= b <= 30] - b3 = [b for b in ball if 31 <= b <= 45] - - if len(b1) >= N or len(b2) >= N or len(b3) >= N: - return True - - return False - - def filterNTimesIn10UnitSections(self, ball, N=4): - # 같은 10단위 4개 이상인 경우 - - b1 = [b for b in ball if 1 <= b <= 10] - b2 = [b for b in ball if 11 <= b <= 20] - b3 = [b for b in ball if 21 <= b <= 30] - b4 = [b for b in ball if 31 <= b <= 40] - b5 = [b for b in ball if 41 <= b <= 45] - - if len(b1) >= N or len(b2) >= N or len(b3) >= N or len(b4) >= N or len(b5) >= N: - return True - - return False - - def filterNTimesIn9UnitSections(self, ball, N=4): - # 같은 9단위 4개 이상인 경우 - # [1, 0, 2, 4, 15, 16] - # [15, 11, 11, 14, 25, 36] - # [15, 22, 23, 24, 25, 36] - # [15, 32, 33, 34, 25, 36] - # [41, 42, 43, 44, 15, 36] - - b1 = [b for b in ball if 1 <= b <= 9] - b2 = [b for b in ball if 10 <= b <= 18] - b3 = [b for b in ball if 19 <= b <= 27] - b4 = [b for b in ball if 28 <= b <= 36] - b5 = [b for b in ball if 37 <= b <= 45] - - if len(b1) >= N or len(b2) >= N or len(b3) >= N or len(b4) >= N or len(b5) >= N: - return True - - return False - - def filterNTimesIn7UnitSections(self, ball, N=4): - # 같은 5단위 4개 이상인 경우 - - b1 = [b for b in ball if 1 <= b <= 7] - b2 = [b for b in ball if 6 <= b <= 14] - b3 = [b for b in ball if 11 <= b <= 21] - b4 = [b for b in ball if 16 <= b <= 28] - b5 = [b for b in ball if 21 <= b <= 25] - b6 = [b for b in ball if 26 <= b <= 30] - b7 = [b for b in ball if 31 <= b <= 35] - b8 = [b for b in ball if 36 <= b <= 40] - b9 = [b for b in ball if 41 <= b <= 45] - - if ( - len(b1) >= N or len(b2) >= N or len(b3) >= N or len(b4) >= N or len(b5) >= N - or len(b6) >= N or len(b7) >= N or len(b8) >= N or len(b9) >= N - ): - return True - - return False - - def filterNTimesIn5UnitSections(self, ball, N=4): - # 같은 5단위 4개 이상인 경우 - - b1 = [b for b in ball if 1 <= b <= 5] - b2 = [b for b in ball if 6 <= b <= 10] - b3 = [b for b in ball if 11 <= b <= 15] - b4 = [b for b in ball if 16 <= b <= 20] - b5 = [b for b in ball if 21 <= b <= 25] - b6 = [b for b in ball if 26 <= b <= 30] - b7 = [b for b in ball if 31 <= b <= 35] - b8 = [b for b in ball if 36 <= b <= 40] - b9 = [b for b in ball if 41 <= b <= 45] - - if ( - len(b1) >= N or len(b2) >= N or len(b3) >= N or len(b4) >= N or len(b5) >= N - or len(b6) >= N or len(b7) >= N or len(b8) >= N or len(b9) >= N - ): - return True - - return False - - def filterGivenData(self, ball): - if not (ball[0] < 5 and ball[1] < 10 and 37 < ball[5]): - return True - - return False - - def filterPreviousNumber(self, ball, no): - previous_ball = self.getBall(no-1) - pb_set = set(previous_ball) - - if ( - ball[0] not in pb_set and ball[0] - 1 not in pb_set and ball[0] + 1 not in pb_set and - ball[1] not in pb_set and ball[1] - 1 not in pb_set and ball[1] + 1 not in pb_set and - ball[2] not in pb_set and ball[2] - 1 not in pb_set and ball[2] + 1 not in pb_set and - ball[3] not in pb_set and ball[3] - 1 not in pb_set and ball[3] + 1 not in pb_set and - ball[4] not in pb_set and ball[4] - 1 not in pb_set and ball[4] + 1 not in pb_set and - ball[5] not in pb_set and ball[5] - 1 not in pb_set and ball[5] + 1 not in pb_set - ): - return True - return False - - def getACValue(self, ball): - ac = set() - for i in range(5, -1, -1): - for j in range(i-1, -1, -1): - ac.add( ball[i] - ball[j]) - return len(ac) - (6-1) - - def getNumberOfAppearancesInSection10(self, ball): - section = set() - for b in ball: - v = int(b/10) - if v not in section: - section.add(v) - return len(section) - - def get_ball_interval(self, ball): - interval_sum = 0 - for i in range(1, len(ball)): - interval_sum += (ball[i] - ball[i-1]) - return interval_sum - - def getFirstLetterSumBall(self, ball): - acc = [str(b)[0] for b in ball if len(str(b))==2] - acc = [int(b) for b in acc] - return sum(acc) - - def getLastLetterSumBall(self, ball): - acc = [str(b)[1] for b in ball if len(str(b)) == 2] + [str(b) for b in ball if len(str(b)) == 1] - acc = [int(b) for b in acc] - return sum(acc) - - def getWeeksFrequency(self, answer, df=None, no=None, week=20): - if df is None: - # fallback to history if caller didn't provide df (build with 'no' column) - if self.history_ball_list is None: - return 0 - rows = [] - for idx, balls in enumerate(self.history_ball_list, start=1): - rows.append([idx] + list(balls) + [0]) - df = pd.DataFrame(rows, columns=["no", "b1", "b2", "b3", "b4", "b5", "b6", "bn"]) - - dic = {} - ball = [] - for w in range(1, week+1): - pb = self._get_df_ball(df, no - w) - if pb is None: - continue - ball += pb - - for b in ball: - if b not in dic: - dic[b] = 1 - else: - dic[b] += 1 - - exist_ball = set() - for b in answer: - if b in dic: - exist_ball.add(b) - - return len(exist_ball) - - def filterOverseas(self, ball, no): - if no in self.oversea_history_ball: - oversea_balls = self.oversea_history_ball[no] - match = [] - for b in ball: - if b in oversea_balls: - match.append(1) - if len(match) < 3: - return True - return False - - def filterAllPreivous7(self, ball, no): - pb_set = set() - for i in range(no-1, no-8, -1): - pb = self.getBall(i) - for b in pb: - if b not in pb_set: - pb_set.add(b) - if len(set(ball) & pb_set) == 6: - return True - return False - - def checkFilter_JapanMethod(self, df, week=26): - # https://xn--961bo7bg3gjne.com/menu_103.php - - all_balls = {} - pos = len(df) - 1 - try_num = 0 - for i in range(pos, pos - week, -1): - ball = [df['b1'].iloc[i], df['b2'].iloc[i], df['b3'].iloc[i], df['b4'].iloc[i], df['b5'].iloc[i], df['b6'].iloc[i]] - for b in ball: - if b not in all_balls: - all_balls[b] = 1 - else: - all_balls[b] += 1 - try_num += 1 - - all_balls_sorted = sorted(all_balls.items(), key=lambda x: x[1], reverse=True) - return set([bf[0] for bf in all_balls_sorted if bf[1] in [2,3]]) - - def filterPatternInPaper1(self, ball, log=False): - # https://www.9dantv.com/mobile/jump.do - # 모서리패턴 - filter_set = {1,2,8,9,6,7,13,14,29,30,36,37,43,44,45,34,35,41,42} - if len(set(ball) & filter_set) == 6: - return "용지영역: #1" - # 좌상 삼각 패턴 - # NOTE: 기존 코드에 18.19(float) 오타가 있었음 → (18, 19)로 교정 - filter_set = {1,2,3,4,5,6,7,8,9,10,11,12,13,16,17,18,19,22,23,24,25,29,30,31,36,37,43} - if len(set(ball) & filter_set) == 6: - return "용지영역: #0" - # 좌하 삼각 패턴 - filter_set = {1,8,9,15,16,17,22,23,24,25,29,30,31,32,33,36,37,38,39,40,41,43,44,45} - if len(set(ball) & filter_set) == 6: - return "용지영역: #2" - # 우상 삼각패턴 - filter_set = {1,2,3,4,5,6,7,9,10,11,12,13,14,17,18,19,20,21,25,26,27,28,33,34,35,41,42} - if len(set(ball) & filter_set) == 6: - return "용지영역: #4" - # 우하 삼각패턴 - filter_set = {7,13,14,19,20,21,25,26,27,28,31,32,33,34,35,37,38,39,40,41,42,43,44,45} - if len(set(ball) & filter_set) == 6: - return "용지영역: #5" - # 우하 삼각패턴 - filter_set = {7,13,14,19,20,21,25,26,27,28,31,32,33,34,35,37,38,39,40,41,42,43,44,45} - if len(set(ball) & filter_set) == 6: - return "용지영역: #6" - return None - - def filterPatternInPaper2(self, ball, log=False): - # 퐁당퐁당 패턴 #1 - # NOTE: 기존 코드에 441 오타가 있었음 → 44로 교정, 중복 제거 - filter_set = {1,2,4,5,8,9,11,12,15,16,18,19,22,23,25,26,29,30,32,33,36,37,39,40,43,44} - if len(set(ball) & filter_set) == 6: - return "용지영역: #7" - # 퐁당퐁당 패턴 #0 - filter_set = {3,10,17,24,31,38,45,4,11,18,25,32,39,6,13,20,27,34,41,7,14,21,28,35,42} - if len(set(ball) & filter_set) == 6: - return "용지영역: #8" - - # 좌우 2줄 패턴 - filter_set = {1,8,15,22,29,36,43,2,9,16,23,30,37,44,6,13,20,27,34,41,7,14,21,28,35,42} - if len(set(ball) & filter_set) == 6: - return "용지영역: #9" - return None - - def filterPatternInPaper3(self, ball, log=False): - # 가로 라인 1,0,2 - filter_set = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21} - if len(set(ball) & filter_set) == 6: - return "용지영역: #10" - # 가로 라인 0,2,4 - filter_set = {8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28} - if len(set(ball) & filter_set) == 6: - return "용지영역: #11" - # 가로 라인 2,4,5 - filter_set = {15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35} - if len(set(ball) & filter_set) == 6: - return "용지영역: #11" - # 가로 라인 4,5,6 - filter_set = {22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42} - if len(set(ball) & filter_set) == 6: - return "용지영역: #11" - # 가로 라인 5,6,7 - filter_set = {29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45} - if len(set(ball) & filter_set) == 6: - return "용지영역: #14" - return None - - def filterPatternInPaper4(self, ball, log=False): - # 가로 1라인부터 연속 6줄 #1 - filter_set = {1,2,3,4,5,6,7} - if len(set(ball) & filter_set) == 6: - return "용지영역: #15" - # 가로 1라인부터 연속 6줄 #0 - filter_set = {8,9,10,11,12,13,14} - if len(set(ball) & filter_set) == 6: - return "용지영역: #16" - # 가로 1라인부터 연속 6줄 #2 - filter_set = {15,16,17,18,19,20,21} - if len(set(ball) & filter_set) == 6: - return "용지영역: #17" - # 가로 1라인부터 연속 6줄 #4 - filter_set = {22,23,24,25,26,27,28} - if len(set(ball) & filter_set) == 6: - return "용지영역: #18" - # 가로 1라인부터 연속 6줄 #5 - filter_set = {29,30,31,32,33,34,35} - if len(set(ball) & filter_set) == 6: - return "용지영역: #19" - # 가로 1라인부터 연속 6줄 #6 - filter_set = {36,37,38,39,40,41,42} - if len(set(ball) & filter_set) == 6: - return "용지영역: #20" - # 가로 2라인부터 연속 6줄 - filter_set = {36,37,38,39,40,41,42} - if len(set(ball) & filter_set) == 6: - return "용지영역: #21" - return None - - def filterPatternInPaper5(self, ball, log=False): - # 세로 라인 1,0,2 - filter_set = {1,8,15,22,29,36,43,2,9,16,23,30,37,44,3,10,17,24,31,38,45} - if len(set(ball) & filter_set) == 6: - return "용지영역: #22" - # 세로 라인 0,2,4 - filter_set = {2,9,16,23,30,37,44,3,10,17,24,31,38,45,4,11,18,25,32,39} - if len(set(ball) & filter_set) == 6: - return "용지영역: #23" - # 세로 라인 2,4,5 - filter_set = {3,10,17,24,31,38,45,4,11,18,25,32,39,5,12,19,26,33,40} - if len(set(ball) & filter_set) == 6: - return "용지영역: #24" - # 세로 라인 4,5,6 - filter_set = {4,11,18,25,32,39,5,12,19,26,33,40,6,13,20,27,34,41} - if len(set(ball) & filter_set) == 6: - return "용지영역: #25" - # 세로 라인 5,6,7 - filter_set = {5,12,19,26,33,40,6,13,20,27,34,41,7,14,21,28,35,42} - if len(set(ball) & filter_set) == 6: - return "용지영역: #26" - return None - - def filterPatternInPaper6(self, ball, log=False): - # 세로 1라인부터 연속 6줄 - filter_set = {1,8,15,22,29,36,43} - if len(set(ball) & filter_set) == 6: - return "용지영역: #27" - # 세로 2라인부터 연속 6줄 - filter_set = {2,9,16,23,30,37,44} - if len(set(ball) & filter_set) == 6: - return "용지영역: #28" - # 세로 3라인부터 연속 6줄 - filter_set = {3,10,17,24,31,38,45} - if len(set(ball) & filter_set) == 6: - return "용지영역: #29" - # 세로 4라인부터 연속 6줄 - filter_set = {4,11,18,25,32,39} - if len(set(ball) & filter_set) == 6: - return "용지영역: #30" - # 세로 5라인부터 연속 6줄 - filter_set = {5,12,19,26,33,40} - if len(set(ball) & filter_set) == 6: - return "용지영역: #31" - # 세로 6라인부터 연속 6줄 - filter_set = {6,13,20,27,34,41} - if len(set(ball) & filter_set) == 6: - return "용지영역: #32" - # 세로 7라인부터 연속 6줄 - filter_set = {7,14,21,28,35,42} - if len(set(ball) & filter_set) == 6: - return "용지영역: #33" - return None - - def getHigLowRate(self, ball): - low = [] - high = [] - for b in ball: - if b < 23: - low.append(b) - if 23 < b: - high.append(b) - return len(low), len(high) - - def filterOneDigitPattern(self, ball): - # 끝자리(0~9) 유니크 개수 - digit = set() - for b in ball: - digit.add(b % 10) - return len(digit) - - def filterPairBall(self, ball): - set_ball = set(ball) - if len(set_ball & {8, 12}) == 2: return 1 - if len(set_ball & {6, 29}) == 2: return 2 - if len(set_ball & {6, 33}) == 2: return 3 - if len(set_ball & {8, 26}) == 2: return 4 - if len(set_ball & {11, 40}) == 2: return 5 - if len(set_ball & {26, 32}) == 2: return 6 - if len(set_ball & {5, 40}) == 2: return 7 - if len(set_ball & {8, 9}) == 2: return 8 - if len(set_ball & {19, 29}) == 2: return 9 - if len(set_ball & {21, 28}) == 2: return 10 - if len(set_ball & {23, 41}) == 2: return 11 - if len(set_ball & {24, 43}) == 2: return 12 - if len(set_ball & {1, 22}) == 2: return 13 - if len(set_ball & {3, 25}) == 2: return 14 - if len(set_ball & {3, 28}) == 2: return 15 - if len(set_ball & {4, 30}) == 2: return 16 - if len(set_ball & {9, 19}) == 2: return 17 - if len(set_ball & {9, 45}) == 2: return 18 - if len(set_ball & {11, 34}) == 2: return 19 - if len(set_ball & {16, 22}) == 2: return 20 - if len(set_ball & {22, 29}) == 2: return 21 - if len(set_ball & {24, 28}) == 2: return 22 - if len(set_ball & {28, 31}) == 2: return 23 - if len(set_ball & {37, 44}) == 2: return 24 - if len(set_ball & {1, 31}) == 2: return 25 - if len(set_ball & {2, 23}) == 2: return 26 - if len(set_ball & {2, 35}) == 2: return 27 - if len(set_ball & {5, 10}) == 2: return 28 - if len(set_ball & {5, 33}) == 2: return 29 - if len(set_ball & {6, 23}) == 2: return 30 - if len(set_ball & {7, 32}) == 2: return 31 - if len(set_ball & {9, 20}) == 2: return 32 - if len(set_ball & {9, 44}) == 2: return 33 - if len(set_ball & {16, 32}) == 2: return 34 - if len(set_ball & {22, 45}) == 2: return 35 - if len(set_ball & {25, 30}) == 2: return 36 - if len(set_ball & {25, 41}) == 2: return 37 - if len(set_ball & {25, 42}) == 2: return 38 - if len(set_ball & {28, 29}) == 2: return 39 - if len(set_ball & {28, 32}) == 2: return 40 - if len(set_ball & {28, 33}) == 2: return 41 - if len(set_ball & {29, 30}) == 2: return 42 - if len(set_ball & {1, 30}) == 2: return 43 - if len(set_ball & {2, 9}) == 2: return 44 - if len(set_ball & {2, 38}) == 2: return 45 - if len(set_ball & {3, 21}) == 2: return 46 - if len(set_ball & {4, 21}) == 2: return 47 - if len(set_ball & {4, 36}) == 2: return 48 - if len(set_ball & {5, 8}) == 2: return 49 - if len(set_ball & {5, 28}) == 2: return 50 - if len(set_ball & {5, 36}) == 2: return 51 - if len(set_ball & {5, 38}) == 2: return 52 - if len(set_ball & {6, 8}) == 2: return 53 - if len(set_ball & {6, 9}) == 2: return 54 - if len(set_ball & {6, 45}) == 2: return 55 - if len(set_ball & {7, 21}) == 2: return 56 - if len(set_ball & {8, 10}) == 2: return 57 - if len(set_ball & {8, 22}) == 2: return 58 - if len(set_ball & {8, 41}) == 2: return 59 - if len(set_ball & {9, 22}) == 2: return 60 - if len(set_ball & {9, 32}) == 2: return 61 - if len(set_ball & {9, 42}) == 2: return 62 - if len(set_ball & {10, 17}) == 2: return 63 - if len(set_ball & {10, 26}) == 2: return 64 - if len(set_ball & {10, 30}) == 2: return 65 - if len(set_ball & {10, 45}) == 2: return 66 - if len(set_ball & {14, 29}) == 2: return 67 - if len(set_ball & {15, 31}) == 2: return 68 - if len(set_ball & {15, 32}) == 2: return 69 - if len(set_ball & {18, 41}) == 2: return 70 - if len(set_ball & {19, 37}) == 2: return 71 - if len(set_ball & {20, 29}) == 2: return 72 - if len(set_ball & {22, 41}) == 2: return 73 - if len(set_ball & {23, 32}) == 2: return 74 - if len(set_ball & {23, 33}) == 2: return 75 - if len(set_ball & {24, 26}) == 2: return 76 - if len(set_ball & {24, 31}) == 2: return 77 - if len(set_ball & {28, 35}) == 2: return 78 - if len(set_ball & {29, 41}) == 2: return 79 - if len(set_ball & {32, 35}) == 2: return 80 - if len(set_ball & {32, 38}) == 2: return 81 - if len(set_ball & {35, 41}) == 2: return 82 - if len(set_ball & {39, 42}) == 2: return 83 - return None - - - def filterTriplePairBall(self, ball): - set_ball = set(ball) - if len(set_ball & {1, 2, 32}) == 3: return 1 - if len(set_ball & {1, 2, 41}) == 3: return 2 - if len(set_ball & {1, 2, 43}) == 3: return 3 - if len(set_ball & {1, 3, 5}) == 3: return 4 - if len(set_ball & {1, 3, 13}) == 3: return 5 - if len(set_ball & {1, 3, 19}) == 3: return 6 - if len(set_ball & {1, 3, 38}) == 3: return 7 - if len(set_ball & {1, 4, 7}) == 3: return 8 - if len(set_ball & {1, 4, 11}) == 3: return 9 - if len(set_ball & {1, 4, 21}) == 3: return 10 - if len(set_ball & {1, 4, 22}) == 3: return 11 - if len(set_ball & {1, 4, 24}) == 3: return 12 - if len(set_ball & {1, 4, 25}) == 3: return 13 - if len(set_ball & {1, 4, 27}) == 3: return 14 - if len(set_ball & {1, 4, 30}) == 3: return 15 - if len(set_ball & {1, 4, 32}) == 3: return 16 - if len(set_ball & {1, 4, 36}) == 3: return 17 - if len(set_ball & {1, 4, 44}) == 3: return 18 - if len(set_ball & {1, 5, 7}) == 3: return 19 - if len(set_ball & {1, 5, 8}) == 3: return 20 - if len(set_ball & {1, 5, 15}) == 3: return 21 - if len(set_ball & {1, 5, 17}) == 3: return 22 - if len(set_ball & {1, 5, 22}) == 3: return 23 - if len(set_ball & {1, 5, 33}) == 3: return 24 - if len(set_ball & {1, 5, 43}) == 3: return 25 - if len(set_ball & {1, 5, 45}) == 3: return 26 - if len(set_ball & {1, 6, 7}) == 3: return 27 - if len(set_ball & {1, 6, 8}) == 3: return 28 - if len(set_ball & {1, 6, 10}) == 3: return 29 - if len(set_ball & {1, 6, 18}) == 3: return 30 - if len(set_ball & {1, 6, 21}) == 3: return 31 - if len(set_ball & {1, 6, 23}) == 3: return 32 - if len(set_ball & {1, 6, 26}) == 3: return 33 - if len(set_ball & {1, 6, 29}) == 3: return 34 - if len(set_ball & {1, 6, 30}) == 3: return 35 - if len(set_ball & {1, 6, 32}) == 3: return 36 - if len(set_ball & {1, 6, 35}) == 3: return 37 - if len(set_ball & {1, 6, 43}) == 3: return 38 - if len(set_ball & {1, 6, 44}) == 3: return 39 - if len(set_ball & {1, 7, 13}) == 3: return 40 - if len(set_ball & {1, 7, 17}) == 3: return 41 - if len(set_ball & {1, 7, 25}) == 3: return 42 - if len(set_ball & {1, 7, 28}) == 3: return 43 - if len(set_ball & {1, 7, 29}) == 3: return 44 - if len(set_ball & {1, 7, 31}) == 3: return 45 - if len(set_ball & {1, 7, 39}) == 3: return 46 - if len(set_ball & {1, 7, 43}) == 3: return 47 - if len(set_ball & {1, 7, 44}) == 3: return 48 - if len(set_ball & {1, 7, 45}) == 3: return 49 - if len(set_ball & {1, 8, 16}) == 3: return 50 - if len(set_ball & {1, 8, 20}) == 3: return 51 - if len(set_ball & {1, 8, 25}) == 3: return 52 - if len(set_ball & {1, 8, 30}) == 3: return 53 - if len(set_ball & {1, 8, 40}) == 3: return 54 - if len(set_ball & {1, 8, 41}) == 3: return 55 - if len(set_ball & {1, 9, 15}) == 3: return 56 - if len(set_ball & {1, 9, 22}) == 3: return 57 - if len(set_ball & {1, 9, 34}) == 3: return 58 - if len(set_ball & {1, 9, 37}) == 3: return 59 - if len(set_ball & {1, 9, 44}) == 3: return 60 - if len(set_ball & {1, 10, 11}) == 3: return 61 - if len(set_ball & {1, 10, 30}) == 3: return 62 - if len(set_ball & {1, 10, 34}) == 3: return 63 - if len(set_ball & {1, 10, 39}) == 3: return 64 - if len(set_ball & {1, 11, 19}) == 3: return 65 - if len(set_ball & {1, 11, 20}) == 3: return 66 - if len(set_ball & {1, 11, 29}) == 3: return 67 - if len(set_ball & {1, 11, 31}) == 3: return 68 - if len(set_ball & {1, 11, 33}) == 3: return 69 - if len(set_ball & {1, 11, 41}) == 3: return 70 - if len(set_ball & {1, 11, 43}) == 3: return 71 - if len(set_ball & {1, 12, 25}) == 3: return 72 - if len(set_ball & {1, 12, 30}) == 3: return 73 - if len(set_ball & {1, 12, 31}) == 3: return 74 - if len(set_ball & {1, 13, 15}) == 3: return 75 - if len(set_ball & {1, 13, 23}) == 3: return 76 - if len(set_ball & {1, 13, 27}) == 3: return 77 - if len(set_ball & {1, 13, 30}) == 3: return 78 - if len(set_ball & {1, 13, 31}) == 3: return 79 - if len(set_ball & {1, 13, 41}) == 3: return 80 - if len(set_ball & {1, 14, 17}) == 3: return 81 - if len(set_ball & {1, 14, 19}) == 3: return 82 - if len(set_ball & {1, 14, 22}) == 3: return 83 - if len(set_ball & {1, 14, 23}) == 3: return 84 - if len(set_ball & {1, 14, 25}) == 3: return 85 - if len(set_ball & {1, 14, 30}) == 3: return 86 - if len(set_ball & {1, 14, 31}) == 3: return 87 - if len(set_ball & {1, 14, 36}) == 3: return 88 - if len(set_ball & {1, 14, 38}) == 3: return 89 - if len(set_ball & {1, 14, 45}) == 3: return 90 - if len(set_ball & {1, 15, 21}) == 3: return 91 - if len(set_ball & {1, 15, 27}) == 3: return 92 - if len(set_ball & {1, 15, 29}) == 3: return 93 - if len(set_ball & {1, 15, 30}) == 3: return 94 - if len(set_ball & {1, 15, 31}) == 3: return 95 - if len(set_ball & {1, 15, 33}) == 3: return 96 - if len(set_ball & {1, 15, 43}) == 3: return 97 - if len(set_ball & {1, 16, 27}) == 3: return 98 - if len(set_ball & {1, 16, 31}) == 3: return 99 - if len(set_ball & {1, 17, 18}) == 3: return 100 - if len(set_ball & {1, 17, 38}) == 3: return 101 - if len(set_ball & {1, 18, 21}) == 3: return 102 - if len(set_ball & {1, 18, 25}) == 3: return 103 - if len(set_ball & {1, 18, 35}) == 3: return 104 - if len(set_ball & {1, 18, 39}) == 3: return 105 - if len(set_ball & {1, 19, 21}) == 3: return 106 - if len(set_ball & {1, 19, 22}) == 3: return 107 - if len(set_ball & {1, 19, 25}) == 3: return 108 - if len(set_ball & {1, 19, 29}) == 3: return 109 - if len(set_ball & {1, 19, 31}) == 3: return 110 - if len(set_ball & {1, 19, 32}) == 3: return 111 - if len(set_ball & {1, 19, 33}) == 3: return 112 - if len(set_ball & {1, 19, 37}) == 3: return 113 - if len(set_ball & {1, 19, 45}) == 3: return 114 - if len(set_ball & {1, 20, 21}) == 3: return 115 - if len(set_ball & {1, 20, 38}) == 3: return 116 - if len(set_ball & {1, 21, 28}) == 3: return 117 - if len(set_ball & {1, 21, 43}) == 3: return 118 - if len(set_ball & {1, 22, 23}) == 3: return 119 - if len(set_ball & {1, 22, 24}) == 3: return 120 - if len(set_ball & {1, 22, 26}) == 3: return 121 - if len(set_ball & {1, 22, 27}) == 3: return 122 - if len(set_ball & {1, 22, 29}) == 3: return 123 - if len(set_ball & {1, 22, 30}) == 3: return 124 - if len(set_ball & {1, 22, 34}) == 3: return 125 - if len(set_ball & {1, 22, 35}) == 3: return 126 - if len(set_ball & {1, 22, 36}) == 3: return 127 - if len(set_ball & {1, 22, 41}) == 3: return 128 - if len(set_ball & {1, 22, 43}) == 3: return 129 - if len(set_ball & {1, 22, 44}) == 3: return 130 - if len(set_ball & {1, 23, 32}) == 3: return 131 - if len(set_ball & {1, 23, 36}) == 3: return 132 - if len(set_ball & {1, 24, 37}) == 3: return 133 - if len(set_ball & {1, 25, 27}) == 3: return 134 - if len(set_ball & {1, 25, 30}) == 3: return 135 - if len(set_ball & {1, 25, 31}) == 3: return 136 - if len(set_ball & {1, 25, 32}) == 3: return 137 - if len(set_ball & {1, 25, 33}) == 3: return 138 - if len(set_ball & {1, 25, 42}) == 3: return 139 - if len(set_ball & {1, 25, 43}) == 3: return 140 - if len(set_ball & {1, 26, 39}) == 3: return 141 - if len(set_ball & {1, 26, 45}) == 3: return 142 - if len(set_ball & {1, 27, 31}) == 3: return 143 - if len(set_ball & {1, 27, 38}) == 3: return 144 - if len(set_ball & {1, 27, 44}) == 3: return 145 - if len(set_ball & {1, 28, 33}) == 3: return 146 - if len(set_ball & {1, 28, 38}) == 3: return 147 - if len(set_ball & {1, 28, 39}) == 3: return 148 - if len(set_ball & {1, 29, 30}) == 3: return 149 - if len(set_ball & {1, 29, 41}) == 3: return 150 - if len(set_ball & {1, 30, 31}) == 3: return 151 - if len(set_ball & {1, 30, 37}) == 3: return 152 - if len(set_ball & {1, 30, 40}) == 3: return 153 - if len(set_ball & {1, 30, 44}) == 3: return 154 - if len(set_ball & {1, 30, 45}) == 3: return 155 - if len(set_ball & {1, 31, 32}) == 3: return 156 - if len(set_ball & {1, 31, 33}) == 3: return 157 - if len(set_ball & {1, 31, 35}) == 3: return 158 - if len(set_ball & {1, 31, 36}) == 3: return 159 - if len(set_ball & {1, 31, 37}) == 3: return 160 - if len(set_ball & {1, 31, 38}) == 3: return 161 - if len(set_ball & {1, 31, 39}) == 3: return 162 - if len(set_ball & {1, 31, 41}) == 3: return 163 - if len(set_ball & {1, 31, 45}) == 3: return 164 - if len(set_ball & {1, 32, 38}) == 3: return 165 - if len(set_ball & {1, 32, 39}) == 3: return 166 - if len(set_ball & {1, 32, 43}) == 3: return 167 - if len(set_ball & {1, 32, 44}) == 3: return 168 - if len(set_ball & {1, 33, 38}) == 3: return 169 - if len(set_ball & {1, 33, 41}) == 3: return 170 - if len(set_ball & {1, 33, 44}) == 3: return 171 - if len(set_ball & {1, 35, 36}) == 3: return 172 - if len(set_ball & {1, 36, 43}) == 3: return 173 - if len(set_ball & {1, 37, 44}) == 3: return 174 - if len(set_ball & {1, 38, 42}) == 3: return 175 - if len(set_ball & {1, 38, 43}) == 3: return 176 - if len(set_ball & {1, 38, 44}) == 3: return 177 - if len(set_ball & {1, 39, 41}) == 3: return 178 - if len(set_ball & {1, 39, 42}) == 3: return 179 - if len(set_ball & {1, 41, 45}) == 3: return 180 - if len(set_ball & {2, 3, 8}) == 3: return 181 - if len(set_ball & {2, 3, 10}) == 3: return 182 - if len(set_ball & {2, 3, 18}) == 3: return 183 - if len(set_ball & {2, 3, 21}) == 3: return 184 - if len(set_ball & {2, 3, 28}) == 3: return 185 - if len(set_ball & {2, 3, 29}) == 3: return 186 - if len(set_ball & {2, 3, 31}) == 3: return 187 - if len(set_ball & {2, 3, 32}) == 3: return 188 - if len(set_ball & {2, 3, 35}) == 3: return 189 - if len(set_ball & {2, 3, 41}) == 3: return 190 - if len(set_ball & {2, 3, 45}) == 3: return 191 - if len(set_ball & {2, 4, 7}) == 3: return 192 - if len(set_ball & {2, 4, 9}) == 3: return 193 - if len(set_ball & {2, 4, 10}) == 3: return 194 - if len(set_ball & {2, 4, 12}) == 3: return 195 - if len(set_ball & {2, 4, 13}) == 3: return 196 - if len(set_ball & {2, 4, 14}) == 3: return 197 - if len(set_ball & {2, 4, 18}) == 3: return 198 - if len(set_ball & {2, 4, 22}) == 3: return 199 - if len(set_ball & {2, 4, 40}) == 3: return 200 - if len(set_ball & {2, 4, 41}) == 3: return 201 - if len(set_ball & {2, 4, 42}) == 3: return 202 - if len(set_ball & {2, 4, 45}) == 3: return 203 - if len(set_ball & {2, 5, 9}) == 3: return 204 - if len(set_ball & {2, 5, 21}) == 3: return 205 - if len(set_ball & {2, 5, 25}) == 3: return 206 - if len(set_ball & {2, 5, 26}) == 3: return 207 - if len(set_ball & {2, 5, 30}) == 3: return 208 - if len(set_ball & {2, 5, 35}) == 3: return 209 - if len(set_ball & {2, 5, 37}) == 3: return 210 - if len(set_ball & {2, 5, 38}) == 3: return 211 - if len(set_ball & {2, 5, 41}) == 3: return 212 - if len(set_ball & {2, 5, 42}) == 3: return 213 - if len(set_ball & {2, 5, 43}) == 3: return 214 - if len(set_ball & {2, 6, 10}) == 3: return 215 - if len(set_ball & {2, 6, 15}) == 3: return 216 - if len(set_ball & {2, 6, 23}) == 3: return 217 - if len(set_ball & {2, 6, 24}) == 3: return 218 - if len(set_ball & {2, 6, 32}) == 3: return 219 - if len(set_ball & {2, 6, 35}) == 3: return 220 - if len(set_ball & {2, 6, 38}) == 3: return 221 - if len(set_ball & {2, 6, 41}) == 3: return 222 - if len(set_ball & {2, 7, 11}) == 3: return 223 - if len(set_ball & {2, 7, 23}) == 3: return 224 - if len(set_ball & {2, 7, 31}) == 3: return 225 - if len(set_ball & {2, 7, 32}) == 3: return 226 - if len(set_ball & {2, 7, 35}) == 3: return 227 - if len(set_ball & {2, 7, 37}) == 3: return 228 - if len(set_ball & {2, 8, 10}) == 3: return 229 - if len(set_ball & {2, 8, 12}) == 3: return 230 - if len(set_ball & {2, 8, 16}) == 3: return 231 - if len(set_ball & {2, 8, 40}) == 3: return 232 - if len(set_ball & {2, 9, 11}) == 3: return 233 - if len(set_ball & {2, 9, 13}) == 3: return 234 - if len(set_ball & {2, 9, 18}) == 3: return 235 - if len(set_ball & {2, 9, 20}) == 3: return 236 - if len(set_ball & {2, 9, 21}) == 3: return 237 - if len(set_ball & {2, 9, 27}) == 3: return 238 - if len(set_ball & {2, 9, 29}) == 3: return 239 - if len(set_ball & {2, 9, 30}) == 3: return 240 - if len(set_ball & {2, 9, 32}) == 3: return 241 - if len(set_ball & {2, 9, 36}) == 3: return 242 - if len(set_ball & {2, 9, 39}) == 3: return 243 - if len(set_ball & {2, 10, 17}) == 3: return 244 - if len(set_ball & {2, 10, 20}) == 3: return 245 - if len(set_ball & {2, 10, 21}) == 3: return 246 - if len(set_ball & {2, 10, 23}) == 3: return 247 - if len(set_ball & {2, 10, 24}) == 3: return 248 - if len(set_ball & {2, 10, 27}) == 3: return 249 - if len(set_ball & {2, 10, 28}) == 3: return 250 - if len(set_ball & {2, 10, 30}) == 3: return 251 - if len(set_ball & {2, 10, 41}) == 3: return 252 - if len(set_ball & {2, 10, 43}) == 3: return 253 - if len(set_ball & {2, 11, 20}) == 3: return 254 - if len(set_ball & {2, 11, 38}) == 3: return 255 - if len(set_ball & {2, 11, 40}) == 3: return 256 - if len(set_ball & {2, 12, 13}) == 3: return 257 - if len(set_ball & {2, 12, 16}) == 3: return 258 - if len(set_ball & {2, 12, 18}) == 3: return 259 - if len(set_ball & {2, 12, 25}) == 3: return 260 - if len(set_ball & {2, 12, 29}) == 3: return 261 - if len(set_ball & {2, 12, 32}) == 3: return 262 - if len(set_ball & {2, 12, 35}) == 3: return 263 - if len(set_ball & {2, 12, 36}) == 3: return 264 - if len(set_ball & {2, 13, 21}) == 3: return 265 - if len(set_ball & {2, 13, 23}) == 3: return 266 - if len(set_ball & {2, 13, 24}) == 3: return 267 - if len(set_ball & {2, 13, 26}) == 3: return 268 - if len(set_ball & {2, 13, 35}) == 3: return 269 - if len(set_ball & {2, 13, 39}) == 3: return 270 - if len(set_ball & {2, 14, 18}) == 3: return 271 - if len(set_ball & {2, 14, 19}) == 3: return 272 - if len(set_ball & {2, 14, 20}) == 3: return 273 - if len(set_ball & {2, 14, 26}) == 3: return 274 - if len(set_ball & {2, 14, 34}) == 3: return 275 - if len(set_ball & {2, 14, 35}) == 3: return 276 - if len(set_ball & {2, 14, 37}) == 3: return 277 - if len(set_ball & {2, 14, 43}) == 3: return 278 - if len(set_ball & {2, 15, 17}) == 3: return 279 - if len(set_ball & {2, 15, 26}) == 3: return 280 - if len(set_ball & {2, 15, 32}) == 3: return 281 - if len(set_ball & {2, 15, 35}) == 3: return 282 - if len(set_ball & {2, 15, 39}) == 3: return 283 - if len(set_ball & {2, 16, 18}) == 3: return 284 - if len(set_ball & {2, 16, 23}) == 3: return 285 - if len(set_ball & {2, 16, 43}) == 3: return 286 - if len(set_ball & {2, 17, 23}) == 3: return 287 - if len(set_ball & {2, 17, 25}) == 3: return 288 - if len(set_ball & {2, 17, 35}) == 3: return 289 - if len(set_ball & {2, 17, 44}) == 3: return 290 - if len(set_ball & {2, 18, 22}) == 3: return 291 - if len(set_ball & {2, 18, 35}) == 3: return 292 - if len(set_ball & {2, 18, 41}) == 3: return 293 - if len(set_ball & {2, 19, 21}) == 3: return 294 - if len(set_ball & {2, 19, 30}) == 3: return 295 - if len(set_ball & {2, 19, 40}) == 3: return 296 - if len(set_ball & {2, 20, 22}) == 3: return 297 - if len(set_ball & {2, 20, 23}) == 3: return 298 - if len(set_ball & {2, 20, 26}) == 3: return 299 - if len(set_ball & {2, 20, 28}) == 3: return 300 - if len(set_ball & {2, 20, 32}) == 3: return 301 - if len(set_ball & {2, 20, 36}) == 3: return 302 - if len(set_ball & {2, 21, 23}) == 3: return 303 - if len(set_ball & {2, 21, 24}) == 3: return 304 - if len(set_ball & {2, 21, 31}) == 3: return 305 - if len(set_ball & {2, 21, 32}) == 3: return 306 - if len(set_ball & {2, 21, 37}) == 3: return 307 - if len(set_ball & {2, 21, 40}) == 3: return 308 - if len(set_ball & {2, 22, 26}) == 3: return 309 - if len(set_ball & {2, 22, 35}) == 3: return 310 - if len(set_ball & {2, 22, 43}) == 3: return 311 - if len(set_ball & {2, 23, 24}) == 3: return 312 - if len(set_ball & {2, 23, 28}) == 3: return 313 - if len(set_ball & {2, 23, 30}) == 3: return 314 - if len(set_ball & {2, 23, 32}) == 3: return 315 - if len(set_ball & {2, 23, 33}) == 3: return 316 - if len(set_ball & {2, 23, 35}) == 3: return 317 - if len(set_ball & {2, 23, 36}) == 3: return 318 - if len(set_ball & {2, 23, 39}) == 3: return 319 - if len(set_ball & {2, 23, 42}) == 3: return 320 - if len(set_ball & {2, 23, 45}) == 3: return 321 - if len(set_ball & {2, 24, 25}) == 3: return 322 - if len(set_ball & {2, 24, 26}) == 3: return 323 - if len(set_ball & {2, 24, 38}) == 3: return 324 - if len(set_ball & {2, 25, 35}) == 3: return 325 - if len(set_ball & {2, 26, 28}) == 3: return 326 - if len(set_ball & {2, 26, 31}) == 3: return 327 - if len(set_ball & {2, 26, 32}) == 3: return 328 - if len(set_ball & {2, 26, 35}) == 3: return 329 - if len(set_ball & {2, 26, 38}) == 3: return 330 - if len(set_ball & {2, 26, 39}) == 3: return 331 - if len(set_ball & {2, 26, 41}) == 3: return 332 - if len(set_ball & {2, 26, 42}) == 3: return 333 - if len(set_ball & {2, 27, 29}) == 3: return 334 - if len(set_ball & {2, 27, 31}) == 3: return 335 - if len(set_ball & {2, 27, 34}) == 3: return 336 - if len(set_ball & {2, 27, 45}) == 3: return 337 - if len(set_ball & {2, 28, 40}) == 3: return 338 - if len(set_ball & {2, 28, 41}) == 3: return 339 - if len(set_ball & {2, 29, 33}) == 3: return 340 - if len(set_ball & {2, 29, 35}) == 3: return 341 - if len(set_ball & {2, 29, 37}) == 3: return 342 - if len(set_ball & {2, 29, 41}) == 3: return 343 - if len(set_ball & {2, 29, 42}) == 3: return 344 - if len(set_ball & {2, 30, 37}) == 3: return 345 - if len(set_ball & {2, 30, 44}) == 3: return 346 - if len(set_ball & {2, 31, 36}) == 3: return 347 - if len(set_ball & {2, 31, 44}) == 3: return 348 - if len(set_ball & {2, 32, 37}) == 3: return 349 - if len(set_ball & {2, 32, 38}) == 3: return 350 - if len(set_ball & {2, 32, 40}) == 3: return 351 - if len(set_ball & {2, 32, 41}) == 3: return 352 - if len(set_ball & {2, 33, 38}) == 3: return 353 - if len(set_ball & {2, 34, 36}) == 3: return 354 - if len(set_ball & {2, 34, 39}) == 3: return 355 - if len(set_ball & {2, 35, 38}) == 3: return 356 - if len(set_ball & {2, 35, 44}) == 3: return 357 - if len(set_ball & {2, 35, 45}) == 3: return 358 - if len(set_ball & {2, 36, 38}) == 3: return 359 - if len(set_ball & {2, 36, 40}) == 3: return 360 - if len(set_ball & {2, 36, 43}) == 3: return 361 - if len(set_ball & {2, 36, 44}) == 3: return 362 - if len(set_ball & {2, 36, 45}) == 3: return 363 - if len(set_ball & {2, 37, 38}) == 3: return 364 - if len(set_ball & {2, 37, 42}) == 3: return 365 - if len(set_ball & {2, 37, 44}) == 3: return 366 - if len(set_ball & {2, 38, 41}) == 3: return 367 - if len(set_ball & {2, 38, 43}) == 3: return 368 - if len(set_ball & {2, 38, 44}) == 3: return 369 - if len(set_ball & {2, 39, 40}) == 3: return 370 - if len(set_ball & {2, 40, 45}) == 3: return 371 - if len(set_ball & {2, 42, 43}) == 3: return 372 - if len(set_ball & {2, 44, 45}) == 3: return 373 - if len(set_ball & {3, 4, 8}) == 3: return 374 - if len(set_ball & {3, 4, 13}) == 3: return 375 - if len(set_ball & {3, 4, 18}) == 3: return 376 - if len(set_ball & {3, 4, 21}) == 3: return 377 - if len(set_ball & {3, 4, 26}) == 3: return 378 - if len(set_ball & {3, 4, 35}) == 3: return 379 - if len(set_ball & {3, 4, 39}) == 3: return 380 - if len(set_ball & {3, 5, 9}) == 3: return 381 - if len(set_ball & {3, 5, 15}) == 3: return 382 - if len(set_ball & {3, 5, 16}) == 3: return 383 - if len(set_ball & {3, 5, 18}) == 3: return 384 - if len(set_ball & {3, 5, 23}) == 3: return 385 - if len(set_ball & {3, 5, 25}) == 3: return 386 - if len(set_ball & {3, 5, 28}) == 3: return 387 - if len(set_ball & {3, 5, 36}) == 3: return 388 - if len(set_ball & {3, 5, 40}) == 3: return 389 - if len(set_ball & {3, 5, 41}) == 3: return 390 - if len(set_ball & {3, 5, 45}) == 3: return 391 - if len(set_ball & {3, 6, 8}) == 3: return 392 - if len(set_ball & {3, 6, 11}) == 3: return 393 - if len(set_ball & {3, 6, 15}) == 3: return 394 - if len(set_ball & {3, 6, 16}) == 3: return 395 - if len(set_ball & {3, 6, 25}) == 3: return 396 - if len(set_ball & {3, 6, 26}) == 3: return 397 - if len(set_ball & {3, 6, 29}) == 3: return 398 - if len(set_ball & {3, 6, 31}) == 3: return 399 - if len(set_ball & {3, 6, 32}) == 3: return 400 - if len(set_ball & {3, 6, 33}) == 3: return 401 - if len(set_ball & {3, 6, 40}) == 3: return 402 - if len(set_ball & {3, 6, 42}) == 3: return 403 - if len(set_ball & {3, 6, 43}) == 3: return 404 - if len(set_ball & {3, 6, 45}) == 3: return 405 - if len(set_ball & {3, 7, 19}) == 3: return 406 - if len(set_ball & {3, 7, 28}) == 3: return 407 - if len(set_ball & {3, 7, 30}) == 3: return 408 - if len(set_ball & {3, 7, 35}) == 3: return 409 - if len(set_ball & {3, 7, 45}) == 3: return 410 - if len(set_ball & {3, 8, 10}) == 3: return 411 - if len(set_ball & {3, 8, 14}) == 3: return 412 - if len(set_ball & {3, 8, 25}) == 3: return 413 - if len(set_ball & {3, 8, 26}) == 3: return 414 - if len(set_ball & {3, 8, 28}) == 3: return 415 - if len(set_ball & {3, 8, 33}) == 3: return 416 - if len(set_ball & {3, 8, 37}) == 3: return 417 - if len(set_ball & {3, 9, 15}) == 3: return 418 - if len(set_ball & {3, 9, 16}) == 3: return 419 - if len(set_ball & {3, 9, 20}) == 3: return 420 - if len(set_ball & {3, 9, 21}) == 3: return 421 - if len(set_ball & {3, 9, 26}) == 3: return 422 - if len(set_ball & {3, 9, 31}) == 3: return 423 - if len(set_ball & {3, 9, 38}) == 3: return 424 - if len(set_ball & {3, 9, 39}) == 3: return 425 - if len(set_ball & {3, 9, 41}) == 3: return 426 - if len(set_ball & {3, 9, 44}) == 3: return 427 - if len(set_ball & {3, 10, 12}) == 3: return 428 - if len(set_ball & {3, 10, 18}) == 3: return 429 - if len(set_ball & {3, 10, 21}) == 3: return 430 - if len(set_ball & {3, 10, 41}) == 3: return 431 - if len(set_ball & {3, 11, 23}) == 3: return 432 - if len(set_ball & {3, 11, 25}) == 3: return 433 - if len(set_ball & {3, 11, 28}) == 3: return 434 - if len(set_ball & {3, 11, 29}) == 3: return 435 - if len(set_ball & {3, 11, 40}) == 3: return 436 - if len(set_ball & {3, 12, 17}) == 3: return 437 - if len(set_ball & {3, 12, 28}) == 3: return 438 - if len(set_ball & {3, 12, 29}) == 3: return 439 - if len(set_ball & {3, 12, 37}) == 3: return 440 - if len(set_ball & {3, 12, 44}) == 3: return 441 - if len(set_ball & {3, 14, 19}) == 3: return 442 - if len(set_ball & {3, 14, 29}) == 3: return 443 - if len(set_ball & {3, 14, 39}) == 3: return 444 - if len(set_ball & {3, 15, 16}) == 3: return 445 - if len(set_ball & {3, 15, 17}) == 3: return 446 - if len(set_ball & {3, 15, 18}) == 3: return 447 - if len(set_ball & {3, 15, 19}) == 3: return 448 - if len(set_ball & {3, 15, 21}) == 3: return 449 - if len(set_ball & {3, 15, 23}) == 3: return 450 - if len(set_ball & {3, 15, 26}) == 3: return 451 - if len(set_ball & {3, 15, 31}) == 3: return 452 - if len(set_ball & {3, 15, 39}) == 3: return 453 - if len(set_ball & {3, 15, 42}) == 3: return 454 - if len(set_ball & {3, 16, 25}) == 3: return 455 - if len(set_ball & {3, 16, 33}) == 3: return 456 - if len(set_ball & {3, 16, 41}) == 3: return 457 - if len(set_ball & {3, 16, 42}) == 3: return 458 - if len(set_ball & {3, 16, 45}) == 3: return 459 - if len(set_ball & {3, 17, 25}) == 3: return 460 - if len(set_ball & {3, 17, 26}) == 3: return 461 - if len(set_ball & {3, 17, 29}) == 3: return 462 - if len(set_ball & {3, 17, 33}) == 3: return 463 - if len(set_ball & {3, 17, 40}) == 3: return 464 - if len(set_ball & {3, 17, 42}) == 3: return 465 - if len(set_ball & {3, 17, 43}) == 3: return 466 - if len(set_ball & {3, 18, 21}) == 3: return 467 - if len(set_ball & {3, 18, 24}) == 3: return 468 - if len(set_ball & {3, 18, 25}) == 3: return 469 - if len(set_ball & {3, 18, 38}) == 3: return 470 - if len(set_ball & {3, 18, 39}) == 3: return 471 - if len(set_ball & {3, 18, 44}) == 3: return 472 - if len(set_ball & {3, 19, 26}) == 3: return 473 - if len(set_ball & {3, 19, 29}) == 3: return 474 - if len(set_ball & {3, 19, 33}) == 3: return 475 - if len(set_ball & {3, 19, 34}) == 3: return 476 - if len(set_ball & {3, 19, 40}) == 3: return 477 - if len(set_ball & {3, 19, 44}) == 3: return 478 - if len(set_ball & {3, 20, 29}) == 3: return 479 - if len(set_ball & {3, 20, 30}) == 3: return 480 - if len(set_ball & {3, 21, 24}) == 3: return 481 - if len(set_ball & {3, 21, 27}) == 3: return 482 - if len(set_ball & {3, 21, 28}) == 3: return 483 - if len(set_ball & {3, 21, 32}) == 3: return 484 - if len(set_ball & {3, 21, 34}) == 3: return 485 - if len(set_ball & {3, 21, 36}) == 3: return 486 - if len(set_ball & {3, 21, 40}) == 3: return 487 - if len(set_ball & {3, 21, 43}) == 3: return 488 - if len(set_ball & {3, 22, 34}) == 3: return 489 - if len(set_ball & {3, 23, 25}) == 3: return 490 - if len(set_ball & {3, 23, 30}) == 3: return 491 - if len(set_ball & {3, 23, 33}) == 3: return 492 - if len(set_ball & {3, 24, 28}) == 3: return 493 - if len(set_ball & {3, 24, 40}) == 3: return 494 - if len(set_ball & {3, 25, 26}) == 3: return 495 - if len(set_ball & {3, 25, 27}) == 3: return 496 - if len(set_ball & {3, 25, 28}) == 3: return 497 - if len(set_ball & {3, 25, 30}) == 3: return 498 - if len(set_ball & {3, 25, 31}) == 3: return 499 - if len(set_ball & {3, 25, 34}) == 3: return 500 - if len(set_ball & {3, 25, 35}) == 3: return 501 - if len(set_ball & {3, 25, 38}) == 3: return 502 - if len(set_ball & {3, 25, 39}) == 3: return 503 - if len(set_ball & {3, 25, 40}) == 3: return 504 - if len(set_ball & {3, 25, 41}) == 3: return 505 - if len(set_ball & {3, 25, 42}) == 3: return 506 - if len(set_ball & {3, 26, 28}) == 3: return 507 - if len(set_ball & {3, 26, 30}) == 3: return 508 - if len(set_ball & {3, 26, 32}) == 3: return 509 - if len(set_ball & {3, 26, 36}) == 3: return 510 - if len(set_ball & {3, 26, 39}) == 3: return 511 - if len(set_ball & {3, 26, 40}) == 3: return 512 - if len(set_ball & {3, 26, 45}) == 3: return 513 - if len(set_ball & {3, 27, 33}) == 3: return 514 - if len(set_ball & {3, 27, 34}) == 3: return 515 - if len(set_ball & {3, 27, 36}) == 3: return 516 - if len(set_ball & {3, 28, 29}) == 3: return 517 - if len(set_ball & {3, 28, 31}) == 3: return 518 - if len(set_ball & {3, 28, 33}) == 3: return 519 - if len(set_ball & {3, 28, 35}) == 3: return 520 - if len(set_ball & {3, 28, 36}) == 3: return 521 - if len(set_ball & {3, 28, 37}) == 3: return 522 - if len(set_ball & {3, 28, 41}) == 3: return 523 - if len(set_ball & {3, 29, 30}) == 3: return 524 - if len(set_ball & {3, 29, 31}) == 3: return 525 - if len(set_ball & {3, 29, 33}) == 3: return 526 - if len(set_ball & {3, 29, 34}) == 3: return 527 - if len(set_ball & {3, 30, 35}) == 3: return 528 - if len(set_ball & {3, 30, 42}) == 3: return 529 - if len(set_ball & {3, 30, 44}) == 3: return 530 - if len(set_ball & {3, 31, 33}) == 3: return 531 - if len(set_ball & {3, 31, 36}) == 3: return 532 - if len(set_ball & {3, 31, 45}) == 3: return 533 - if len(set_ball & {3, 32, 38}) == 3: return 534 - if len(set_ball & {3, 32, 39}) == 3: return 535 - if len(set_ball & {3, 33, 40}) == 3: return 536 - if len(set_ball & {3, 33, 44}) == 3: return 537 - if len(set_ball & {3, 34, 40}) == 3: return 538 - if len(set_ball & {3, 35, 38}) == 3: return 539 - if len(set_ball & {3, 35, 39}) == 3: return 540 - if len(set_ball & {3, 35, 41}) == 3: return 541 - if len(set_ball & {3, 35, 42}) == 3: return 542 - if len(set_ball & {3, 36, 43}) == 3: return 543 - if len(set_ball & {3, 36, 44}) == 3: return 544 - if len(set_ball & {3, 37, 40}) == 3: return 545 - if len(set_ball & {3, 38, 41}) == 3: return 546 - if len(set_ball & {3, 39, 40}) == 3: return 547 - if len(set_ball & {3, 44, 45}) == 3: return 548 - if len(set_ball & {4, 5, 10}) == 3: return 549 - if len(set_ball & {4, 5, 19}) == 3: return 550 - if len(set_ball & {4, 5, 28}) == 3: return 551 - if len(set_ball & {4, 5, 30}) == 3: return 552 - if len(set_ball & {4, 5, 33}) == 3: return 553 - if len(set_ball & {4, 5, 34}) == 3: return 554 - if len(set_ball & {4, 5, 36}) == 3: return 555 - if len(set_ball & {4, 5, 40}) == 3: return 556 - if len(set_ball & {4, 5, 44}) == 3: return 557 - if len(set_ball & {4, 6, 7}) == 3: return 558 - if len(set_ball & {4, 6, 16}) == 3: return 559 - if len(set_ball & {4, 6, 18}) == 3: return 560 - if len(set_ball & {4, 6, 22}) == 3: return 561 - if len(set_ball & {4, 6, 23}) == 3: return 562 - if len(set_ball & {4, 6, 24}) == 3: return 563 - if len(set_ball & {4, 6, 27}) == 3: return 564 - if len(set_ball & {4, 6, 29}) == 3: return 565 - if len(set_ball & {4, 6, 34}) == 3: return 566 - if len(set_ball & {4, 6, 35}) == 3: return 567 - if len(set_ball & {4, 6, 36}) == 3: return 568 - if len(set_ball & {4, 6, 38}) == 3: return 569 - if len(set_ball & {4, 6, 45}) == 3: return 570 - if len(set_ball & {4, 7, 8}) == 3: return 571 - if len(set_ball & {4, 7, 9}) == 3: return 572 - if len(set_ball & {4, 7, 21}) == 3: return 573 - if len(set_ball & {4, 7, 27}) == 3: return 574 - if len(set_ball & {4, 7, 28}) == 3: return 575 - if len(set_ball & {4, 7, 30}) == 3: return 576 - if len(set_ball & {4, 7, 34}) == 3: return 577 - if len(set_ball & {4, 7, 36}) == 3: return 578 - if len(set_ball & {4, 7, 37}) == 3: return 579 - if len(set_ball & {4, 7, 43}) == 3: return 580 - if len(set_ball & {4, 8, 12}) == 3: return 581 - if len(set_ball & {4, 8, 14}) == 3: return 582 - if len(set_ball & {4, 8, 22}) == 3: return 583 - if len(set_ball & {4, 8, 26}) == 3: return 584 - if len(set_ball & {4, 8, 28}) == 3: return 585 - if len(set_ball & {4, 8, 35}) == 3: return 586 - if len(set_ball & {4, 9, 12}) == 3: return 587 - if len(set_ball & {4, 9, 15}) == 3: return 588 - if len(set_ball & {4, 9, 20}) == 3: return 589 - if len(set_ball & {4, 9, 35}) == 3: return 590 - if len(set_ball & {4, 9, 41}) == 3: return 591 - if len(set_ball & {4, 9, 43}) == 3: return 592 - if len(set_ball & {4, 10, 39}) == 3: return 593 - if len(set_ball & {4, 10, 43}) == 3: return 594 - if len(set_ball & {4, 11, 15}) == 3: return 595 - if len(set_ball & {4, 11, 16}) == 3: return 596 - if len(set_ball & {4, 11, 19}) == 3: return 597 - if len(set_ball & {4, 11, 25}) == 3: return 598 - if len(set_ball & {4, 11, 30}) == 3: return 599 - if len(set_ball & {4, 11, 33}) == 3: return 600 - if len(set_ball & {4, 11, 34}) == 3: return 601 - if len(set_ball & {4, 11, 36}) == 3: return 602 - if len(set_ball & {4, 11, 40}) == 3: return 603 - if len(set_ball & {4, 11, 44}) == 3: return 604 - if len(set_ball & {4, 12, 13}) == 3: return 605 - if len(set_ball & {4, 12, 15}) == 3: return 606 - if len(set_ball & {4, 12, 17}) == 3: return 607 - if len(set_ball & {4, 12, 19}) == 3: return 608 - if len(set_ball & {4, 12, 21}) == 3: return 609 - if len(set_ball & {4, 12, 26}) == 3: return 610 - if len(set_ball & {4, 12, 29}) == 3: return 611 - if len(set_ball & {4, 12, 30}) == 3: return 612 - if len(set_ball & {4, 12, 31}) == 3: return 613 - if len(set_ball & {4, 12, 36}) == 3: return 614 - if len(set_ball & {4, 12, 39}) == 3: return 615 - if len(set_ball & {4, 12, 40}) == 3: return 616 - if len(set_ball & {4, 12, 44}) == 3: return 617 - if len(set_ball & {4, 13, 15}) == 3: return 618 - if len(set_ball & {4, 13, 16}) == 3: return 619 - if len(set_ball & {4, 13, 24}) == 3: return 620 - if len(set_ball & {4, 13, 25}) == 3: return 621 - if len(set_ball & {4, 13, 30}) == 3: return 622 - if len(set_ball & {4, 13, 35}) == 3: return 623 - if len(set_ball & {4, 14, 17}) == 3: return 624 - if len(set_ball & {4, 14, 27}) == 3: return 625 - if len(set_ball & {4, 14, 30}) == 3: return 626 - if len(set_ball & {4, 14, 34}) == 3: return 627 - if len(set_ball & {4, 14, 36}) == 3: return 628 - if len(set_ball & {4, 14, 38}) == 3: return 629 - if len(set_ball & {4, 14, 39}) == 3: return 630 - if len(set_ball & {4, 15, 19}) == 3: return 631 - if len(set_ball & {4, 15, 30}) == 3: return 632 - if len(set_ball & {4, 15, 32}) == 3: return 633 - if len(set_ball & {4, 15, 44}) == 3: return 634 - if len(set_ball & {4, 15, 45}) == 3: return 635 - if len(set_ball & {4, 16, 32}) == 3: return 636 - if len(set_ball & {4, 16, 45}) == 3: return 637 - if len(set_ball & {4, 17, 21}) == 3: return 638 - if len(set_ball & {4, 17, 23}) == 3: return 639 - if len(set_ball & {4, 17, 24}) == 3: return 640 - if len(set_ball & {4, 17, 25}) == 3: return 641 - if len(set_ball & {4, 17, 29}) == 3: return 642 - if len(set_ball & {4, 17, 35}) == 3: return 643 - if len(set_ball & {4, 17, 41}) == 3: return 644 - if len(set_ball & {4, 17, 45}) == 3: return 645 - if len(set_ball & {4, 18, 28}) == 3: return 646 - if len(set_ball & {4, 18, 35}) == 3: return 647 - if len(set_ball & {4, 18, 36}) == 3: return 648 - if len(set_ball & {4, 19, 23}) == 3: return 649 - if len(set_ball & {4, 19, 28}) == 3: return 650 - if len(set_ball & {4, 19, 36}) == 3: return 651 - if len(set_ball & {4, 19, 37}) == 3: return 652 - if len(set_ball & {4, 20, 42}) == 3: return 653 - if len(set_ball & {4, 21, 27}) == 3: return 654 - if len(set_ball & {4, 21, 28}) == 3: return 655 - if len(set_ball & {4, 21, 30}) == 3: return 656 - if len(set_ball & {4, 21, 31}) == 3: return 657 - if len(set_ball & {4, 21, 35}) == 3: return 658 - if len(set_ball & {4, 22, 23}) == 3: return 659 - if len(set_ball & {4, 22, 25}) == 3: return 660 - if len(set_ball & {4, 22, 26}) == 3: return 661 - if len(set_ball & {4, 22, 29}) == 3: return 662 - if len(set_ball & {4, 22, 30}) == 3: return 663 - if len(set_ball & {4, 22, 31}) == 3: return 664 - if len(set_ball & {4, 22, 32}) == 3: return 665 - if len(set_ball & {4, 22, 35}) == 3: return 666 - if len(set_ball & {4, 22, 36}) == 3: return 667 - if len(set_ball & {4, 22, 39}) == 3: return 668 - if len(set_ball & {4, 22, 45}) == 3: return 669 - if len(set_ball & {4, 23, 24}) == 3: return 670 - if len(set_ball & {4, 23, 27}) == 3: return 671 - if len(set_ball & {4, 23, 36}) == 3: return 672 - if len(set_ball & {4, 24, 29}) == 3: return 673 - if len(set_ball & {4, 24, 30}) == 3: return 674 - if len(set_ball & {4, 24, 31}) == 3: return 675 - if len(set_ball & {4, 24, 39}) == 3: return 676 - if len(set_ball & {4, 24, 43}) == 3: return 677 - if len(set_ball & {4, 25, 28}) == 3: return 678 - if len(set_ball & {4, 25, 30}) == 3: return 679 - if len(set_ball & {4, 25, 38}) == 3: return 680 - if len(set_ball & {4, 25, 39}) == 3: return 681 - if len(set_ball & {4, 25, 44}) == 3: return 682 - if len(set_ball & {4, 26, 39}) == 3: return 683 - if len(set_ball & {4, 26, 45}) == 3: return 684 - if len(set_ball & {4, 27, 29}) == 3: return 685 - if len(set_ball & {4, 27, 31}) == 3: return 686 - if len(set_ball & {4, 27, 33}) == 3: return 687 - if len(set_ball & {4, 27, 36}) == 3: return 688 - if len(set_ball & {4, 28, 41}) == 3: return 689 - if len(set_ball & {4, 29, 30}) == 3: return 690 - if len(set_ball & {4, 29, 44}) == 3: return 691 - if len(set_ball & {4, 30, 38}) == 3: return 692 - if len(set_ball & {4, 30, 39}) == 3: return 693 - if len(set_ball & {4, 30, 44}) == 3: return 694 - if len(set_ball & {4, 30, 45}) == 3: return 695 - if len(set_ball & {4, 31, 32}) == 3: return 696 - if len(set_ball & {4, 31, 38}) == 3: return 697 - if len(set_ball & {4, 32, 35}) == 3: return 698 - if len(set_ball & {4, 32, 45}) == 3: return 699 - if len(set_ball & {4, 34, 36}) == 3: return 700 - if len(set_ball & {4, 34, 42}) == 3: return 701 - if len(set_ball & {4, 34, 45}) == 3: return 702 - if len(set_ball & {4, 35, 38}) == 3: return 703 - if len(set_ball & {4, 35, 39}) == 3: return 704 - if len(set_ball & {4, 35, 41}) == 3: return 705 - if len(set_ball & {4, 35, 44}) == 3: return 706 - if len(set_ball & {4, 36, 38}) == 3: return 707 - if len(set_ball & {4, 36, 42}) == 3: return 708 - if len(set_ball & {4, 36, 44}) == 3: return 709 - if len(set_ball & {4, 37, 44}) == 3: return 710 - if len(set_ball & {4, 38, 42}) == 3: return 711 - if len(set_ball & {4, 40, 44}) == 3: return 712 - if len(set_ball & {4, 41, 44}) == 3: return 713 - if len(set_ball & {4, 42, 44}) == 3: return 714 - if len(set_ball & {4, 44, 45}) == 3: return 715 - if len(set_ball & {5, 6, 7}) == 3: return 716 - if len(set_ball & {5, 6, 10}) == 3: return 717 - if len(set_ball & {5, 6, 23}) == 3: return 718 - if len(set_ball & {5, 6, 30}) == 3: return 719 - if len(set_ball & {5, 6, 33}) == 3: return 720 - if len(set_ball & {5, 6, 34}) == 3: return 721 - if len(set_ball & {5, 6, 35}) == 3: return 722 - if len(set_ball & {5, 6, 36}) == 3: return 723 - if len(set_ball & {5, 6, 40}) == 3: return 724 - if len(set_ball & {5, 7, 10}) == 3: return 725 - if len(set_ball & {5, 7, 17}) == 3: return 726 - if len(set_ball & {5, 7, 19}) == 3: return 727 - if len(set_ball & {5, 7, 23}) == 3: return 728 - if len(set_ball & {5, 7, 24}) == 3: return 729 - if len(set_ball & {5, 7, 27}) == 3: return 730 - if len(set_ball & {5, 7, 31}) == 3: return 731 - if len(set_ball & {5, 7, 36}) == 3: return 732 - if len(set_ball & {5, 7, 38}) == 3: return 733 - if len(set_ball & {5, 8, 9}) == 3: return 734 - if len(set_ball & {5, 8, 10}) == 3: return 735 - if len(set_ball & {5, 8, 12}) == 3: return 736 - if len(set_ball & {5, 8, 13}) == 3: return 737 - if len(set_ball & {5, 8, 20}) == 3: return 738 - if len(set_ball & {5, 8, 24}) == 3: return 739 - if len(set_ball & {5, 8, 25}) == 3: return 740 - if len(set_ball & {5, 8, 31}) == 3: return 741 - if len(set_ball & {5, 8, 32}) == 3: return 742 - if len(set_ball & {5, 8, 34}) == 3: return 743 - if len(set_ball & {5, 8, 36}) == 3: return 744 - if len(set_ball & {5, 8, 37}) == 3: return 745 - if len(set_ball & {5, 8, 40}) == 3: return 746 - if len(set_ball & {5, 8, 41}) == 3: return 747 - if len(set_ball & {5, 8, 45}) == 3: return 748 - if len(set_ball & {5, 9, 10}) == 3: return 749 - if len(set_ball & {5, 9, 18}) == 3: return 750 - if len(set_ball & {5, 9, 24}) == 3: return 751 - if len(set_ball & {5, 9, 28}) == 3: return 752 - if len(set_ball & {5, 9, 33}) == 3: return 753 - if len(set_ball & {5, 9, 42}) == 3: return 754 - if len(set_ball & {5, 9, 44}) == 3: return 755 - if len(set_ball & {5, 10, 11}) == 3: return 756 - if len(set_ball & {5, 10, 14}) == 3: return 757 - if len(set_ball & {5, 10, 15}) == 3: return 758 - if len(set_ball & {5, 10, 23}) == 3: return 759 - if len(set_ball & {5, 10, 25}) == 3: return 760 - if len(set_ball & {5, 10, 26}) == 3: return 761 - if len(set_ball & {5, 10, 28}) == 3: return 762 - if len(set_ball & {5, 10, 33}) == 3: return 763 - if len(set_ball & {5, 10, 38}) == 3: return 764 - if len(set_ball & {5, 10, 40}) == 3: return 765 - if len(set_ball & {5, 10, 42}) == 3: return 766 - if len(set_ball & {5, 11, 25}) == 3: return 767 - if len(set_ball & {5, 11, 28}) == 3: return 768 - if len(set_ball & {5, 11, 40}) == 3: return 769 - if len(set_ball & {5, 12, 15}) == 3: return 770 - if len(set_ball & {5, 12, 36}) == 3: return 771 - if len(set_ball & {5, 12, 40}) == 3: return 772 - if len(set_ball & {5, 13, 15}) == 3: return 773 - if len(set_ball & {5, 13, 30}) == 3: return 774 - if len(set_ball & {5, 13, 38}) == 3: return 775 - if len(set_ball & {5, 15, 17}) == 3: return 776 - if len(set_ball & {5, 15, 24}) == 3: return 777 - if len(set_ball & {5, 15, 28}) == 3: return 778 - if len(set_ball & {5, 15, 29}) == 3: return 779 - if len(set_ball & {5, 15, 32}) == 3: return 780 - if len(set_ball & {5, 15, 33}) == 3: return 781 - if len(set_ball & {5, 15, 38}) == 3: return 782 - if len(set_ball & {5, 15, 40}) == 3: return 783 - if len(set_ball & {5, 15, 41}) == 3: return 784 - if len(set_ball & {5, 15, 44}) == 3: return 785 - if len(set_ball & {5, 16, 19}) == 3: return 786 - if len(set_ball & {5, 16, 25}) == 3: return 787 - if len(set_ball & {5, 16, 33}) == 3: return 788 - if len(set_ball & {5, 16, 36}) == 3: return 789 - if len(set_ball & {5, 16, 39}) == 3: return 790 - if len(set_ball & {5, 16, 43}) == 3: return 791 - if len(set_ball & {5, 16, 44}) == 3: return 792 - if len(set_ball & {5, 17, 19}) == 3: return 793 - if len(set_ball & {5, 17, 37}) == 3: return 794 - if len(set_ball & {5, 17, 45}) == 3: return 795 - if len(set_ball & {5, 18, 24}) == 3: return 796 - if len(set_ball & {5, 18, 26}) == 3: return 797 - if len(set_ball & {5, 18, 27}) == 3: return 798 - if len(set_ball & {5, 18, 29}) == 3: return 799 - if len(set_ball & {5, 18, 39}) == 3: return 800 - if len(set_ball & {5, 18, 44}) == 3: return 801 - if len(set_ball & {5, 19, 29}) == 3: return 802 - if len(set_ball & {5, 19, 32}) == 3: return 803 - if len(set_ball & {5, 19, 33}) == 3: return 804 - if len(set_ball & {5, 19, 35}) == 3: return 805 - if len(set_ball & {5, 19, 37}) == 3: return 806 - if len(set_ball & {5, 19, 40}) == 3: return 807 - if len(set_ball & {5, 20, 29}) == 3: return 808 - if len(set_ball & {5, 20, 32}) == 3: return 809 - if len(set_ball & {5, 20, 38}) == 3: return 810 - if len(set_ball & {5, 21, 28}) == 3: return 811 - if len(set_ball & {5, 21, 31}) == 3: return 812 - if len(set_ball & {5, 21, 32}) == 3: return 813 - if len(set_ball & {5, 22, 24}) == 3: return 814 - if len(set_ball & {5, 22, 27}) == 3: return 815 - if len(set_ball & {5, 22, 30}) == 3: return 816 - if len(set_ball & {5, 22, 40}) == 3: return 817 - if len(set_ball & {5, 23, 29}) == 3: return 818 - if len(set_ball & {5, 23, 31}) == 3: return 819 - if len(set_ball & {5, 23, 32}) == 3: return 820 - if len(set_ball & {5, 23, 37}) == 3: return 821 - if len(set_ball & {5, 23, 39}) == 3: return 822 - if len(set_ball & {5, 23, 41}) == 3: return 823 - if len(set_ball & {5, 23, 42}) == 3: return 824 - if len(set_ball & {5, 23, 44}) == 3: return 825 - if len(set_ball & {5, 24, 26}) == 3: return 826 - if len(set_ball & {5, 24, 28}) == 3: return 827 - if len(set_ball & {5, 24, 31}) == 3: return 828 - if len(set_ball & {5, 24, 36}) == 3: return 829 - if len(set_ball & {5, 24, 38}) == 3: return 830 - if len(set_ball & {5, 24, 41}) == 3: return 831 - if len(set_ball & {5, 24, 43}) == 3: return 832 - if len(set_ball & {5, 24, 45}) == 3: return 833 - if len(set_ball & {5, 25, 33}) == 3: return 834 - if len(set_ball & {5, 25, 35}) == 3: return 835 - if len(set_ball & {5, 25, 42}) == 3: return 836 - if len(set_ball & {5, 26, 28}) == 3: return 837 - if len(set_ball & {5, 26, 32}) == 3: return 838 - if len(set_ball & {5, 26, 33}) == 3: return 839 - if len(set_ball & {5, 26, 36}) == 3: return 840 - if len(set_ball & {5, 26, 37}) == 3: return 841 - if len(set_ball & {5, 26, 40}) == 3: return 842 - if len(set_ball & {5, 28, 35}) == 3: return 843 - if len(set_ball & {5, 28, 38}) == 3: return 844 - if len(set_ball & {5, 28, 40}) == 3: return 845 - if len(set_ball & {5, 29, 38}) == 3: return 846 - if len(set_ball & {5, 30, 32}) == 3: return 847 - if len(set_ball & {5, 30, 37}) == 3: return 848 - if len(set_ball & {5, 30, 40}) == 3: return 849 - if len(set_ball & {5, 31, 33}) == 3: return 850 - if len(set_ball & {5, 31, 37}) == 3: return 851 - if len(set_ball & {5, 31, 38}) == 3: return 852 - if len(set_ball & {5, 32, 36}) == 3: return 853 - if len(set_ball & {5, 32, 38}) == 3: return 854 - if len(set_ball & {5, 33, 34}) == 3: return 855 - if len(set_ball & {5, 33, 35}) == 3: return 856 - if len(set_ball & {5, 33, 36}) == 3: return 857 - if len(set_ball & {5, 33, 37}) == 3: return 858 - if len(set_ball & {5, 33, 41}) == 3: return 859 - if len(set_ball & {5, 33, 43}) == 3: return 860 - if len(set_ball & {5, 33, 45}) == 3: return 861 - if len(set_ball & {5, 35, 36}) == 3: return 862 - if len(set_ball & {5, 35, 37}) == 3: return 863 - if len(set_ball & {5, 35, 39}) == 3: return 864 - if len(set_ball & {5, 35, 41}) == 3: return 865 - if len(set_ball & {5, 36, 37}) == 3: return 866 - if len(set_ball & {5, 36, 38}) == 3: return 867 - if len(set_ball & {5, 36, 39}) == 3: return 868 - if len(set_ball & {5, 36, 40}) == 3: return 869 - if len(set_ball & {5, 36, 41}) == 3: return 870 - if len(set_ball & {5, 36, 45}) == 3: return 871 - if len(set_ball & {5, 37, 43}) == 3: return 872 - if len(set_ball & {5, 37, 44}) == 3: return 873 - if len(set_ball & {5, 38, 40}) == 3: return 874 - if len(set_ball & {5, 38, 43}) == 3: return 875 - if len(set_ball & {5, 39, 41}) == 3: return 876 - if len(set_ball & {5, 39, 42}) == 3: return 877 - if len(set_ball & {5, 39, 44}) == 3: return 878 - if len(set_ball & {5, 40, 42}) == 3: return 879 - if len(set_ball & {5, 40, 43}) == 3: return 880 - if len(set_ball & {5, 40, 44}) == 3: return 881 - if len(set_ball & {5, 41, 42}) == 3: return 882 - if len(set_ball & {5, 41, 44}) == 3: return 883 - if len(set_ball & {5, 43, 44}) == 3: return 884 - if len(set_ball & {6, 7, 8}) == 3: return 885 - if len(set_ball & {6, 7, 23}) == 3: return 886 - if len(set_ball & {6, 7, 27}) == 3: return 887 - if len(set_ball & {6, 7, 29}) == 3: return 888 - if len(set_ball & {6, 8, 9}) == 3: return 889 - if len(set_ball & {6, 8, 10}) == 3: return 890 - if len(set_ball & {6, 8, 12}) == 3: return 891 - if len(set_ball & {6, 8, 15}) == 3: return 892 - if len(set_ball & {6, 8, 19}) == 3: return 893 - if len(set_ball & {6, 8, 20}) == 3: return 894 - if len(set_ball & {6, 8, 24}) == 3: return 895 - if len(set_ball & {6, 8, 25}) == 3: return 896 - if len(set_ball & {6, 8, 27}) == 3: return 897 - if len(set_ball & {6, 8, 29}) == 3: return 898 - if len(set_ball & {6, 8, 32}) == 3: return 899 - if len(set_ball & {6, 8, 34}) == 3: return 900 - if len(set_ball & {6, 8, 41}) == 3: return 901 - if len(set_ball & {6, 8, 44}) == 3: return 902 - if len(set_ball & {6, 9, 12}) == 3: return 903 - if len(set_ball & {6, 9, 13}) == 3: return 904 - if len(set_ball & {6, 9, 14}) == 3: return 905 - if len(set_ball & {6, 9, 20}) == 3: return 906 - if len(set_ball & {6, 9, 26}) == 3: return 907 - if len(set_ball & {6, 9, 27}) == 3: return 908 - if len(set_ball & {6, 9, 29}) == 3: return 909 - if len(set_ball & {6, 9, 34}) == 3: return 910 - if len(set_ball & {6, 9, 36}) == 3: return 911 - if len(set_ball & {6, 9, 38}) == 3: return 912 - if len(set_ball & {6, 9, 42}) == 3: return 913 - if len(set_ball & {6, 9, 43}) == 3: return 914 - if len(set_ball & {6, 9, 44}) == 3: return 915 - if len(set_ball & {6, 9, 45}) == 3: return 916 - if len(set_ball & {6, 10, 13}) == 3: return 917 - if len(set_ball & {6, 10, 23}) == 3: return 918 - if len(set_ball & {6, 10, 24}) == 3: return 919 - if len(set_ball & {6, 10, 27}) == 3: return 920 - if len(set_ball & {6, 10, 33}) == 3: return 921 - if len(set_ball & {6, 10, 45}) == 3: return 922 - if len(set_ball & {6, 11, 12}) == 3: return 923 - if len(set_ball & {6, 11, 35}) == 3: return 924 - if len(set_ball & {6, 12, 16}) == 3: return 925 - if len(set_ball & {6, 13, 18}) == 3: return 926 - if len(set_ball & {6, 13, 19}) == 3: return 927 - if len(set_ball & {6, 13, 26}) == 3: return 928 - if len(set_ball & {6, 13, 33}) == 3: return 929 - if len(set_ball & {6, 13, 34}) == 3: return 930 - if len(set_ball & {6, 13, 45}) == 3: return 931 - if len(set_ball & {6, 14, 29}) == 3: return 932 - if len(set_ball & {6, 14, 32}) == 3: return 933 - if len(set_ball & {6, 14, 33}) == 3: return 934 - if len(set_ball & {6, 14, 45}) == 3: return 935 - if len(set_ball & {6, 15, 27}) == 3: return 936 - if len(set_ball & {6, 15, 29}) == 3: return 937 - if len(set_ball & {6, 15, 45}) == 3: return 938 - if len(set_ball & {6, 16, 22}) == 3: return 939 - if len(set_ball & {6, 16, 26}) == 3: return 940 - if len(set_ball & {6, 16, 35}) == 3: return 941 - if len(set_ball & {6, 16, 36}) == 3: return 942 - if len(set_ball & {6, 16, 44}) == 3: return 943 - if len(set_ball & {6, 17, 24}) == 3: return 944 - if len(set_ball & {6, 17, 25}) == 3: return 945 - if len(set_ball & {6, 17, 36}) == 3: return 946 - if len(set_ball & {6, 17, 41}) == 3: return 947 - if len(set_ball & {6, 17, 42}) == 3: return 948 - if len(set_ball & {6, 18, 20}) == 3: return 949 - if len(set_ball & {6, 18, 23}) == 3: return 950 - if len(set_ball & {6, 18, 27}) == 3: return 951 - if len(set_ball & {6, 18, 41}) == 3: return 952 - if len(set_ball & {6, 18, 44}) == 3: return 953 - if len(set_ball & {6, 19, 22}) == 3: return 954 - if len(set_ball & {6, 19, 27}) == 3: return 955 - if len(set_ball & {6, 19, 29}) == 3: return 956 - if len(set_ball & {6, 19, 37}) == 3: return 957 - if len(set_ball & {6, 20, 22}) == 3: return 958 - if len(set_ball & {6, 20, 25}) == 3: return 959 - if len(set_ball & {6, 20, 34}) == 3: return 960 - if len(set_ball & {6, 20, 35}) == 3: return 961 - if len(set_ball & {6, 20, 43}) == 3: return 962 - if len(set_ball & {6, 20, 45}) == 3: return 963 - if len(set_ball & {6, 21, 24}) == 3: return 964 - if len(set_ball & {6, 21, 25}) == 3: return 965 - if len(set_ball & {6, 21, 28}) == 3: return 966 - if len(set_ball & {6, 21, 44}) == 3: return 967 - if len(set_ball & {6, 22, 27}) == 3: return 968 - if len(set_ball & {6, 22, 33}) == 3: return 969 - if len(set_ball & {6, 22, 42}) == 3: return 970 - if len(set_ball & {6, 23, 26}) == 3: return 971 - if len(set_ball & {6, 23, 27}) == 3: return 972 - if len(set_ball & {6, 23, 29}) == 3: return 973 - if len(set_ball & {6, 23, 33}) == 3: return 974 - if len(set_ball & {6, 23, 41}) == 3: return 975 - if len(set_ball & {6, 23, 43}) == 3: return 976 - if len(set_ball & {6, 23, 44}) == 3: return 977 - if len(set_ball & {6, 23, 45}) == 3: return 978 - if len(set_ball & {6, 24, 26}) == 3: return 979 - if len(set_ball & {6, 24, 29}) == 3: return 980 - if len(set_ball & {6, 24, 31}) == 3: return 981 - if len(set_ball & {6, 24, 33}) == 3: return 982 - if len(set_ball & {6, 24, 43}) == 3: return 983 - if len(set_ball & {6, 24, 45}) == 3: return 984 - if len(set_ball & {6, 25, 27}) == 3: return 985 - if len(set_ball & {6, 25, 29}) == 3: return 986 - if len(set_ball & {6, 25, 30}) == 3: return 987 - if len(set_ball & {6, 25, 36}) == 3: return 988 - if len(set_ball & {6, 25, 39}) == 3: return 989 - if len(set_ball & {6, 25, 41}) == 3: return 990 - if len(set_ball & {6, 25, 42}) == 3: return 991 - if len(set_ball & {6, 25, 45}) == 3: return 992 - if len(set_ball & {6, 26, 31}) == 3: return 993 - if len(set_ball & {6, 26, 32}) == 3: return 994 - if len(set_ball & {6, 26, 35}) == 3: return 995 - if len(set_ball & {6, 26, 42}) == 3: return 996 - if len(set_ball & {6, 27, 29}) == 3: return 997 - if len(set_ball & {6, 27, 30}) == 3: return 998 - if len(set_ball & {6, 27, 33}) == 3: return 999 - if len(set_ball & {6, 27, 34}) == 3: return 1000 - if len(set_ball & {6, 27, 36}) == 3: return 1001 - if len(set_ball & {6, 27, 45}) == 3: return 1002 - if len(set_ball & {6, 28, 29}) == 3: return 1003 - if len(set_ball & {6, 28, 31}) == 3: return 1004 - if len(set_ball & {6, 28, 37}) == 3: return 1005 - if len(set_ball & {6, 28, 43}) == 3: return 1006 - if len(set_ball & {6, 29, 31}) == 3: return 1007 - if len(set_ball & {6, 29, 32}) == 3: return 1008 - if len(set_ball & {6, 29, 33}) == 3: return 1009 - if len(set_ball & {6, 29, 34}) == 3: return 1010 - if len(set_ball & {6, 29, 35}) == 3: return 1011 - if len(set_ball & {6, 29, 40}) == 3: return 1012 - if len(set_ball & {6, 29, 44}) == 3: return 1013 - if len(set_ball & {6, 30, 33}) == 3: return 1014 - if len(set_ball & {6, 30, 36}) == 3: return 1015 - if len(set_ball & {6, 30, 42}) == 3: return 1016 - if len(set_ball & {6, 30, 44}) == 3: return 1017 - if len(set_ball & {6, 30, 45}) == 3: return 1018 - if len(set_ball & {6, 31, 42}) == 3: return 1019 - if len(set_ball & {6, 32, 33}) == 3: return 1020 - if len(set_ball & {6, 32, 41}) == 3: return 1021 - if len(set_ball & {6, 32, 42}) == 3: return 1022 - if len(set_ball & {6, 32, 43}) == 3: return 1023 - if len(set_ball & {6, 32, 45}) == 3: return 1024 - if len(set_ball & {6, 33, 35}) == 3: return 1025 - if len(set_ball & {6, 33, 36}) == 3: return 1026 - if len(set_ball & {6, 33, 37}) == 3: return 1027 - if len(set_ball & {6, 33, 41}) == 3: return 1028 - if len(set_ball & {6, 33, 42}) == 3: return 1029 - if len(set_ball & {6, 33, 43}) == 3: return 1030 - if len(set_ball & {6, 33, 45}) == 3: return 1031 - if len(set_ball & {6, 34, 36}) == 3: return 1032 - if len(set_ball & {6, 34, 43}) == 3: return 1033 - if len(set_ball & {6, 36, 40}) == 3: return 1034 - if len(set_ball & {6, 37, 42}) == 3: return 1035 - if len(set_ball & {6, 37, 44}) == 3: return 1036 - if len(set_ball & {6, 38, 42}) == 3: return 1037 - if len(set_ball & {6, 39, 42}) == 3: return 1038 - if len(set_ball & {6, 40, 44}) == 3: return 1039 - if len(set_ball & {6, 41, 42}) == 3: return 1040 - if len(set_ball & {6, 41, 44}) == 3: return 1041 - if len(set_ball & {6, 44, 45}) == 3: return 1042 - if len(set_ball & {7, 8, 12}) == 3: return 1043 - if len(set_ball & {7, 8, 25}) == 3: return 1044 - if len(set_ball & {7, 8, 26}) == 3: return 1045 - if len(set_ball & {7, 8, 28}) == 3: return 1046 - if len(set_ball & {7, 8, 35}) == 3: return 1047 - if len(set_ball & {7, 8, 40}) == 3: return 1048 - if len(set_ball & {7, 9, 16}) == 3: return 1049 - if len(set_ball & {7, 9, 21}) == 3: return 1050 - if len(set_ball & {7, 9, 30}) == 3: return 1051 - if len(set_ball & {7, 9, 40}) == 3: return 1052 - if len(set_ball & {7, 9, 41}) == 3: return 1053 - if len(set_ball & {7, 9, 44}) == 3: return 1054 - if len(set_ball & {7, 9, 45}) == 3: return 1055 - if len(set_ball & {7, 10, 11}) == 3: return 1056 - if len(set_ball & {7, 10, 14}) == 3: return 1057 - if len(set_ball & {7, 10, 18}) == 3: return 1058 - if len(set_ball & {7, 10, 20}) == 3: return 1059 - if len(set_ball & {7, 10, 24}) == 3: return 1060 - if len(set_ball & {7, 10, 27}) == 3: return 1061 - if len(set_ball & {7, 10, 30}) == 3: return 1062 - if len(set_ball & {7, 10, 32}) == 3: return 1063 - if len(set_ball & {7, 10, 37}) == 3: return 1064 - if len(set_ball & {7, 10, 39}) == 3: return 1065 - if len(set_ball & {7, 10, 43}) == 3: return 1066 - if len(set_ball & {7, 10, 45}) == 3: return 1067 - if len(set_ball & {7, 11, 14}) == 3: return 1068 - if len(set_ball & {7, 11, 15}) == 3: return 1069 - if len(set_ball & {7, 11, 19}) == 3: return 1070 - if len(set_ball & {7, 11, 25}) == 3: return 1071 - if len(set_ball & {7, 11, 30}) == 3: return 1072 - if len(set_ball & {7, 11, 34}) == 3: return 1073 - if len(set_ball & {7, 11, 36}) == 3: return 1074 - if len(set_ball & {7, 11, 39}) == 3: return 1075 - if len(set_ball & {7, 11, 40}) == 3: return 1076 - if len(set_ball & {7, 12, 17}) == 3: return 1077 - if len(set_ball & {7, 12, 20}) == 3: return 1078 - if len(set_ball & {7, 12, 30}) == 3: return 1079 - if len(set_ball & {7, 12, 44}) == 3: return 1080 - if len(set_ball & {7, 13, 14}) == 3: return 1081 - if len(set_ball & {7, 13, 22}) == 3: return 1082 - if len(set_ball & {7, 13, 23}) == 3: return 1083 - if len(set_ball & {7, 13, 32}) == 3: return 1084 - if len(set_ball & {7, 13, 34}) == 3: return 1085 - if len(set_ball & {7, 14, 18}) == 3: return 1086 - if len(set_ball & {7, 14, 19}) == 3: return 1087 - if len(set_ball & {7, 14, 21}) == 3: return 1088 - if len(set_ball & {7, 14, 25}) == 3: return 1089 - if len(set_ball & {7, 14, 27}) == 3: return 1090 - if len(set_ball & {7, 14, 29}) == 3: return 1091 - if len(set_ball & {7, 14, 30}) == 3: return 1092 - if len(set_ball & {7, 14, 41}) == 3: return 1093 - if len(set_ball & {7, 14, 43}) == 3: return 1094 - if len(set_ball & {7, 14, 45}) == 3: return 1095 - if len(set_ball & {7, 15, 17}) == 3: return 1096 - if len(set_ball & {7, 15, 29}) == 3: return 1097 - if len(set_ball & {7, 15, 35}) == 3: return 1098 - if len(set_ball & {7, 15, 41}) == 3: return 1099 - if len(set_ball & {7, 16, 22}) == 3: return 1100 - if len(set_ball & {7, 16, 30}) == 3: return 1101 - if len(set_ball & {7, 16, 32}) == 3: return 1102 - if len(set_ball & {7, 16, 39}) == 3: return 1103 - if len(set_ball & {7, 16, 43}) == 3: return 1104 - if len(set_ball & {7, 17, 21}) == 3: return 1105 - if len(set_ball & {7, 17, 25}) == 3: return 1106 - if len(set_ball & {7, 17, 27}) == 3: return 1107 - if len(set_ball & {7, 17, 31}) == 3: return 1108 - if len(set_ball & {7, 17, 34}) == 3: return 1109 - if len(set_ball & {7, 17, 37}) == 3: return 1110 - if len(set_ball & {7, 17, 41}) == 3: return 1111 - if len(set_ball & {7, 17, 42}) == 3: return 1112 - if len(set_ball & {7, 17, 43}) == 3: return 1113 - if len(set_ball & {7, 19, 20}) == 3: return 1114 - if len(set_ball & {7, 19, 34}) == 3: return 1115 - if len(set_ball & {7, 21, 22}) == 3: return 1116 - if len(set_ball & {7, 21, 25}) == 3: return 1117 - if len(set_ball & {7, 21, 26}) == 3: return 1118 - if len(set_ball & {7, 21, 28}) == 3: return 1119 - if len(set_ball & {7, 21, 37}) == 3: return 1120 - if len(set_ball & {7, 21, 40}) == 3: return 1121 - if len(set_ball & {7, 21, 42}) == 3: return 1122 - if len(set_ball & {7, 21, 45}) == 3: return 1123 - if len(set_ball & {7, 22, 30}) == 3: return 1124 - if len(set_ball & {7, 22, 45}) == 3: return 1125 - if len(set_ball & {7, 23, 25}) == 3: return 1126 - if len(set_ball & {7, 23, 30}) == 3: return 1127 - if len(set_ball & {7, 23, 31}) == 3: return 1128 - if len(set_ball & {7, 23, 38}) == 3: return 1129 - if len(set_ball & {7, 23, 40}) == 3: return 1130 - if len(set_ball & {7, 23, 41}) == 3: return 1131 - if len(set_ball & {7, 24, 26}) == 3: return 1132 - if len(set_ball & {7, 24, 32}) == 3: return 1133 - if len(set_ball & {7, 25, 27}) == 3: return 1134 - if len(set_ball & {7, 25, 30}) == 3: return 1135 - if len(set_ball & {7, 25, 31}) == 3: return 1136 - if len(set_ball & {7, 25, 32}) == 3: return 1137 - if len(set_ball & {7, 25, 41}) == 3: return 1138 - if len(set_ball & {7, 26, 31}) == 3: return 1139 - if len(set_ball & {7, 26, 32}) == 3: return 1140 - if len(set_ball & {7, 26, 41}) == 3: return 1141 - if len(set_ball & {7, 27, 28}) == 3: return 1142 - if len(set_ball & {7, 27, 31}) == 3: return 1143 - if len(set_ball & {7, 27, 32}) == 3: return 1144 - if len(set_ball & {7, 27, 34}) == 3: return 1145 - if len(set_ball & {7, 28, 31}) == 3: return 1146 - if len(set_ball & {7, 28, 32}) == 3: return 1147 - if len(set_ball & {7, 28, 34}) == 3: return 1148 - if len(set_ball & {7, 29, 37}) == 3: return 1149 - if len(set_ball & {7, 29, 41}) == 3: return 1150 - if len(set_ball & {7, 29, 45}) == 3: return 1151 - if len(set_ball & {7, 30, 31}) == 3: return 1152 - if len(set_ball & {7, 30, 32}) == 3: return 1153 - if len(set_ball & {7, 30, 42}) == 3: return 1154 - if len(set_ball & {7, 31, 32}) == 3: return 1155 - if len(set_ball & {7, 31, 42}) == 3: return 1156 - if len(set_ball & {7, 31, 43}) == 3: return 1157 - if len(set_ball & {7, 31, 44}) == 3: return 1158 - if len(set_ball & {7, 31, 45}) == 3: return 1159 - if len(set_ball & {7, 32, 38}) == 3: return 1160 - if len(set_ball & {7, 33, 42}) == 3: return 1161 - if len(set_ball & {7, 34, 43}) == 3: return 1162 - if len(set_ball & {7, 34, 44}) == 3: return 1163 - if len(set_ball & {7, 35, 41}) == 3: return 1164 - if len(set_ball & {7, 35, 42}) == 3: return 1165 - if len(set_ball & {7, 35, 43}) == 3: return 1166 - if len(set_ball & {7, 35, 44}) == 3: return 1167 - if len(set_ball & {7, 35, 45}) == 3: return 1168 - if len(set_ball & {7, 36, 44}) == 3: return 1169 - if len(set_ball & {7, 36, 45}) == 3: return 1170 - if len(set_ball & {7, 38, 42}) == 3: return 1171 - if len(set_ball & {7, 38, 43}) == 3: return 1172 - if len(set_ball & {7, 38, 45}) == 3: return 1173 - if len(set_ball & {7, 40, 42}) == 3: return 1174 - if len(set_ball & {7, 40, 45}) == 3: return 1175 - if len(set_ball & {7, 42, 43}) == 3: return 1176 - if len(set_ball & {7, 42, 44}) == 3: return 1177 - if len(set_ball & {7, 43, 45}) == 3: return 1178 - if len(set_ball & {8, 9, 11}) == 3: return 1179 - if len(set_ball & {8, 9, 13}) == 3: return 1180 - if len(set_ball & {8, 9, 14}) == 3: return 1181 - if len(set_ball & {8, 9, 15}) == 3: return 1182 - if len(set_ball & {8, 9, 23}) == 3: return 1183 - if len(set_ball & {8, 9, 26}) == 3: return 1184 - if len(set_ball & {8, 9, 30}) == 3: return 1185 - if len(set_ball & {8, 9, 31}) == 3: return 1186 - if len(set_ball & {8, 9, 34}) == 3: return 1187 - if len(set_ball & {8, 9, 35}) == 3: return 1188 - if len(set_ball & {8, 9, 36}) == 3: return 1189 - if len(set_ball & {8, 9, 37}) == 3: return 1190 - if len(set_ball & {8, 9, 38}) == 3: return 1191 - if len(set_ball & {8, 9, 39}) == 3: return 1192 - if len(set_ball & {8, 9, 41}) == 3: return 1193 - if len(set_ball & {8, 9, 42}) == 3: return 1194 - if len(set_ball & {8, 9, 43}) == 3: return 1195 - if len(set_ball & {8, 9, 45}) == 3: return 1196 - if len(set_ball & {8, 10, 17}) == 3: return 1197 - if len(set_ball & {8, 10, 22}) == 3: return 1198 - if len(set_ball & {8, 10, 25}) == 3: return 1199 - if len(set_ball & {8, 10, 26}) == 3: return 1200 - if len(set_ball & {8, 10, 29}) == 3: return 1201 - if len(set_ball & {8, 10, 39}) == 3: return 1202 - if len(set_ball & {8, 11, 20}) == 3: return 1203 - if len(set_ball & {8, 11, 23}) == 3: return 1204 - if len(set_ball & {8, 11, 24}) == 3: return 1205 - if len(set_ball & {8, 11, 27}) == 3: return 1206 - if len(set_ball & {8, 11, 29}) == 3: return 1207 - if len(set_ball & {8, 11, 31}) == 3: return 1208 - if len(set_ball & {8, 11, 32}) == 3: return 1209 - if len(set_ball & {8, 11, 34}) == 3: return 1210 - if len(set_ball & {8, 11, 35}) == 3: return 1211 - if len(set_ball & {8, 11, 40}) == 3: return 1212 - if len(set_ball & {8, 11, 42}) == 3: return 1213 - if len(set_ball & {8, 12, 14}) == 3: return 1214 - if len(set_ball & {8, 12, 15}) == 3: return 1215 - if len(set_ball & {8, 12, 16}) == 3: return 1216 - if len(set_ball & {8, 12, 17}) == 3: return 1217 - if len(set_ball & {8, 12, 18}) == 3: return 1218 - if len(set_ball & {8, 12, 20}) == 3: return 1219 - if len(set_ball & {8, 12, 22}) == 3: return 1220 - if len(set_ball & {8, 12, 23}) == 3: return 1221 - if len(set_ball & {8, 12, 25}) == 3: return 1222 - if len(set_ball & {8, 12, 26}) == 3: return 1223 - if len(set_ball & {8, 12, 27}) == 3: return 1224 - if len(set_ball & {8, 12, 28}) == 3: return 1225 - if len(set_ball & {8, 12, 30}) == 3: return 1226 - if len(set_ball & {8, 12, 32}) == 3: return 1227 - if len(set_ball & {8, 12, 34}) == 3: return 1228 - if len(set_ball & {8, 12, 37}) == 3: return 1229 - if len(set_ball & {8, 12, 38}) == 3: return 1230 - if len(set_ball & {8, 12, 39}) == 3: return 1231 - if len(set_ball & {8, 12, 40}) == 3: return 1232 - if len(set_ball & {8, 12, 41}) == 3: return 1233 - if len(set_ball & {8, 12, 45}) == 3: return 1234 - if len(set_ball & {8, 13, 17}) == 3: return 1235 - if len(set_ball & {8, 13, 21}) == 3: return 1236 - if len(set_ball & {8, 13, 41}) == 3: return 1237 - if len(set_ball & {8, 14, 20}) == 3: return 1238 - if len(set_ball & {8, 14, 24}) == 3: return 1239 - if len(set_ball & {8, 14, 26}) == 3: return 1240 - if len(set_ball & {8, 14, 41}) == 3: return 1241 - if len(set_ball & {8, 14, 42}) == 3: return 1242 - if len(set_ball & {8, 14, 43}) == 3: return 1243 - if len(set_ball & {8, 15, 24}) == 3: return 1244 - if len(set_ball & {8, 15, 26}) == 3: return 1245 - if len(set_ball & {8, 15, 32}) == 3: return 1246 - if len(set_ball & {8, 15, 36}) == 3: return 1247 - if len(set_ball & {8, 15, 40}) == 3: return 1248 - if len(set_ball & {8, 15, 42}) == 3: return 1249 - if len(set_ball & {8, 16, 20}) == 3: return 1250 - if len(set_ball & {8, 16, 22}) == 3: return 1251 - if len(set_ball & {8, 16, 23}) == 3: return 1252 - if len(set_ball & {8, 16, 24}) == 3: return 1253 - if len(set_ball & {8, 16, 27}) == 3: return 1254 - if len(set_ball & {8, 16, 28}) == 3: return 1255 - if len(set_ball & {8, 16, 33}) == 3: return 1256 - if len(set_ball & {8, 16, 35}) == 3: return 1257 - if len(set_ball & {8, 16, 39}) == 3: return 1258 - if len(set_ball & {8, 16, 40}) == 3: return 1259 - if len(set_ball & {8, 17, 25}) == 3: return 1260 - if len(set_ball & {8, 17, 28}) == 3: return 1261 - if len(set_ball & {8, 17, 41}) == 3: return 1262 - if len(set_ball & {8, 18, 26}) == 3: return 1263 - if len(set_ball & {8, 18, 36}) == 3: return 1264 - if len(set_ball & {8, 18, 41}) == 3: return 1265 - if len(set_ball & {8, 19, 23}) == 3: return 1266 - if len(set_ball & {8, 19, 24}) == 3: return 1267 - if len(set_ball & {8, 19, 26}) == 3: return 1268 - if len(set_ball & {8, 19, 29}) == 3: return 1269 - if len(set_ball & {8, 20, 24}) == 3: return 1270 - if len(set_ball & {8, 20, 26}) == 3: return 1271 - if len(set_ball & {8, 20, 28}) == 3: return 1272 - if len(set_ball & {8, 20, 31}) == 3: return 1273 - if len(set_ball & {8, 20, 32}) == 3: return 1274 - if len(set_ball & {8, 20, 40}) == 3: return 1275 - if len(set_ball & {8, 21, 26}) == 3: return 1276 - if len(set_ball & {8, 21, 32}) == 3: return 1277 - if len(set_ball & {8, 21, 41}) == 3: return 1278 - if len(set_ball & {8, 21, 42}) == 3: return 1279 - if len(set_ball & {8, 21, 43}) == 3: return 1280 - if len(set_ball & {8, 22, 27}) == 3: return 1281 - if len(set_ball & {8, 22, 29}) == 3: return 1282 - if len(set_ball & {8, 22, 30}) == 3: return 1283 - if len(set_ball & {8, 22, 34}) == 3: return 1284 - if len(set_ball & {8, 22, 37}) == 3: return 1285 - if len(set_ball & {8, 22, 40}) == 3: return 1286 - if len(set_ball & {8, 22, 43}) == 3: return 1287 - if len(set_ball & {8, 22, 44}) == 3: return 1288 - if len(set_ball & {8, 22, 45}) == 3: return 1289 - if len(set_ball & {8, 23, 28}) == 3: return 1290 - if len(set_ball & {8, 23, 29}) == 3: return 1291 - if len(set_ball & {8, 23, 30}) == 3: return 1292 - if len(set_ball & {8, 23, 32}) == 3: return 1293 - if len(set_ball & {8, 23, 34}) == 3: return 1294 - if len(set_ball & {8, 23, 37}) == 3: return 1295 - if len(set_ball & {8, 24, 25}) == 3: return 1296 - if len(set_ball & {8, 24, 26}) == 3: return 1297 - if len(set_ball & {8, 24, 30}) == 3: return 1298 - if len(set_ball & {8, 24, 32}) == 3: return 1299 - if len(set_ball & {8, 25, 26}) == 3: return 1300 - if len(set_ball & {8, 26, 33}) == 3: return 1301 - if len(set_ball & {8, 26, 35}) == 3: return 1302 - if len(set_ball & {8, 26, 39}) == 3: return 1303 - if len(set_ball & {8, 26, 40}) == 3: return 1304 - if len(set_ball & {8, 26, 41}) == 3: return 1305 - if len(set_ball & {8, 26, 42}) == 3: return 1306 - if len(set_ball & {8, 27, 28}) == 3: return 1307 - if len(set_ball & {8, 28, 41}) == 3: return 1308 - if len(set_ball & {8, 28, 44}) == 3: return 1309 - if len(set_ball & {8, 29, 37}) == 3: return 1310 - if len(set_ball & {8, 29, 39}) == 3: return 1311 - if len(set_ball & {8, 29, 41}) == 3: return 1312 - if len(set_ball & {8, 30, 36}) == 3: return 1313 - if len(set_ball & {8, 31, 32}) == 3: return 1314 - if len(set_ball & {8, 31, 37}) == 3: return 1315 - if len(set_ball & {8, 31, 39}) == 3: return 1316 - if len(set_ball & {8, 31, 40}) == 3: return 1317 - if len(set_ball & {8, 32, 38}) == 3: return 1318 - if len(set_ball & {8, 32, 40}) == 3: return 1319 - if len(set_ball & {8, 32, 41}) == 3: return 1320 - if len(set_ball & {8, 32, 44}) == 3: return 1321 - if len(set_ball & {8, 33, 43}) == 3: return 1322 - if len(set_ball & {8, 34, 35}) == 3: return 1323 - if len(set_ball & {8, 34, 38}) == 3: return 1324 - if len(set_ball & {8, 34, 42}) == 3: return 1325 - if len(set_ball & {8, 37, 38}) == 3: return 1326 - if len(set_ball & {8, 37, 42}) == 3: return 1327 - if len(set_ball & {8, 37, 44}) == 3: return 1328 - if len(set_ball & {8, 38, 43}) == 3: return 1329 - if len(set_ball & {8, 40, 41}) == 3: return 1330 - if len(set_ball & {8, 40, 42}) == 3: return 1331 - if len(set_ball & {8, 41, 42}) == 3: return 1332 - if len(set_ball & {8, 42, 44}) == 3: return 1333 - if len(set_ball & {8, 42, 45}) == 3: return 1334 - if len(set_ball & {9, 10, 17}) == 3: return 1335 - if len(set_ball & {9, 10, 18}) == 3: return 1336 - if len(set_ball & {9, 10, 19}) == 3: return 1337 - if len(set_ball & {9, 10, 20}) == 3: return 1338 - if len(set_ball & {9, 10, 23}) == 3: return 1339 - if len(set_ball & {9, 10, 42}) == 3: return 1340 - if len(set_ball & {9, 10, 43}) == 3: return 1341 - if len(set_ball & {9, 11, 25}) == 3: return 1342 - if len(set_ball & {9, 11, 29}) == 3: return 1343 - if len(set_ball & {9, 11, 33}) == 3: return 1344 - if len(set_ball & {9, 11, 34}) == 3: return 1345 - if len(set_ball & {9, 11, 40}) == 3: return 1346 - if len(set_ball & {9, 11, 45}) == 3: return 1347 - if len(set_ball & {9, 12, 17}) == 3: return 1348 - if len(set_ball & {9, 12, 18}) == 3: return 1349 - if len(set_ball & {9, 12, 22}) == 3: return 1350 - if len(set_ball & {9, 12, 32}) == 3: return 1351 - if len(set_ball & {9, 12, 33}) == 3: return 1352 - if len(set_ball & {9, 12, 42}) == 3: return 1353 - if len(set_ball & {9, 13, 14}) == 3: return 1354 - if len(set_ball & {9, 13, 16}) == 3: return 1355 - if len(set_ball & {9, 13, 17}) == 3: return 1356 - if len(set_ball & {9, 13, 22}) == 3: return 1357 - if len(set_ball & {9, 13, 23}) == 3: return 1358 - if len(set_ball & {9, 13, 29}) == 3: return 1359 - if len(set_ball & {9, 13, 30}) == 3: return 1360 - if len(set_ball & {9, 13, 36}) == 3: return 1361 - if len(set_ball & {9, 13, 40}) == 3: return 1362 - if len(set_ball & {9, 13, 44}) == 3: return 1363 - if len(set_ball & {9, 14, 19}) == 3: return 1364 - if len(set_ball & {9, 14, 24}) == 3: return 1365 - if len(set_ball & {9, 14, 32}) == 3: return 1366 - if len(set_ball & {9, 14, 37}) == 3: return 1367 - if len(set_ball & {9, 14, 39}) == 3: return 1368 - if len(set_ball & {9, 14, 40}) == 3: return 1369 - if len(set_ball & {9, 14, 45}) == 3: return 1370 - if len(set_ball & {9, 15, 18}) == 3: return 1371 - if len(set_ball & {9, 15, 24}) == 3: return 1372 - if len(set_ball & {9, 15, 32}) == 3: return 1373 - if len(set_ball & {9, 15, 35}) == 3: return 1374 - if len(set_ball & {9, 15, 41}) == 3: return 1375 - if len(set_ball & {9, 15, 44}) == 3: return 1376 - if len(set_ball & {9, 15, 45}) == 3: return 1377 - if len(set_ball & {9, 16, 18}) == 3: return 1378 - if len(set_ball & {9, 16, 20}) == 3: return 1379 - if len(set_ball & {9, 16, 22}) == 3: return 1380 - if len(set_ball & {9, 16, 30}) == 3: return 1381 - if len(set_ball & {9, 16, 31}) == 3: return 1382 - if len(set_ball & {9, 16, 33}) == 3: return 1383 - if len(set_ball & {9, 16, 39}) == 3: return 1384 - if len(set_ball & {9, 16, 42}) == 3: return 1385 - if len(set_ball & {9, 17, 20}) == 3: return 1386 - if len(set_ball & {9, 17, 23}) == 3: return 1387 - if len(set_ball & {9, 17, 27}) == 3: return 1388 - if len(set_ball & {9, 17, 40}) == 3: return 1389 - if len(set_ball & {9, 17, 41}) == 3: return 1390 - if len(set_ball & {9, 18, 31}) == 3: return 1391 - if len(set_ball & {9, 18, 39}) == 3: return 1392 - if len(set_ball & {9, 18, 41}) == 3: return 1393 - if len(set_ball & {9, 18, 45}) == 3: return 1394 - if len(set_ball & {9, 19, 21}) == 3: return 1395 - if len(set_ball & {9, 19, 24}) == 3: return 1396 - if len(set_ball & {9, 19, 26}) == 3: return 1397 - if len(set_ball & {9, 19, 27}) == 3: return 1398 - if len(set_ball & {9, 19, 28}) == 3: return 1399 - if len(set_ball & {9, 19, 29}) == 3: return 1400 - if len(set_ball & {9, 19, 31}) == 3: return 1401 - if len(set_ball & {9, 19, 32}) == 3: return 1402 - if len(set_ball & {9, 19, 37}) == 3: return 1403 - if len(set_ball & {9, 19, 38}) == 3: return 1404 - if len(set_ball & {9, 19, 43}) == 3: return 1405 - if len(set_ball & {9, 19, 44}) == 3: return 1406 - if len(set_ball & {9, 19, 45}) == 3: return 1407 - if len(set_ball & {9, 20, 23}) == 3: return 1408 - if len(set_ball & {9, 20, 31}) == 3: return 1409 - if len(set_ball & {9, 20, 32}) == 3: return 1410 - if len(set_ball & {9, 20, 35}) == 3: return 1411 - if len(set_ball & {9, 20, 40}) == 3: return 1412 - if len(set_ball & {9, 20, 42}) == 3: return 1413 - if len(set_ball & {9, 21, 23}) == 3: return 1414 - if len(set_ball & {9, 21, 24}) == 3: return 1415 - if len(set_ball & {9, 21, 38}) == 3: return 1416 - if len(set_ball & {9, 21, 39}) == 3: return 1417 - if len(set_ball & {9, 21, 44}) == 3: return 1418 - if len(set_ball & {9, 21, 45}) == 3: return 1419 - if len(set_ball & {9, 22, 23}) == 3: return 1420 - if len(set_ball & {9, 22, 26}) == 3: return 1421 - if len(set_ball & {9, 22, 28}) == 3: return 1422 - if len(set_ball & {9, 22, 29}) == 3: return 1423 - if len(set_ball & {9, 22, 32}) == 3: return 1424 - if len(set_ball & {9, 22, 39}) == 3: return 1425 - if len(set_ball & {9, 22, 40}) == 3: return 1426 - if len(set_ball & {9, 22, 41}) == 3: return 1427 - if len(set_ball & {9, 22, 43}) == 3: return 1428 - if len(set_ball & {9, 23, 27}) == 3: return 1429 - if len(set_ball & {9, 23, 30}) == 3: return 1430 - if len(set_ball & {9, 23, 31}) == 3: return 1431 - if len(set_ball & {9, 23, 36}) == 3: return 1432 - if len(set_ball & {9, 23, 41}) == 3: return 1433 - if len(set_ball & {9, 23, 42}) == 3: return 1434 - if len(set_ball & {9, 24, 26}) == 3: return 1435 - if len(set_ball & {9, 24, 28}) == 3: return 1436 - if len(set_ball & {9, 24, 35}) == 3: return 1437 - if len(set_ball & {9, 24, 37}) == 3: return 1438 - if len(set_ball & {9, 24, 39}) == 3: return 1439 - if len(set_ball & {9, 24, 40}) == 3: return 1440 - if len(set_ball & {9, 24, 42}) == 3: return 1441 - if len(set_ball & {9, 25, 38}) == 3: return 1442 - if len(set_ball & {9, 26, 32}) == 3: return 1443 - if len(set_ball & {9, 26, 34}) == 3: return 1444 - if len(set_ball & {9, 26, 36}) == 3: return 1445 - if len(set_ball & {9, 26, 39}) == 3: return 1446 - if len(set_ball & {9, 27, 28}) == 3: return 1447 - if len(set_ball & {9, 27, 30}) == 3: return 1448 - if len(set_ball & {9, 27, 33}) == 3: return 1449 - if len(set_ball & {9, 28, 29}) == 3: return 1450 - if len(set_ball & {9, 28, 32}) == 3: return 1451 - if len(set_ball & {9, 28, 37}) == 3: return 1452 - if len(set_ball & {9, 28, 42}) == 3: return 1453 - if len(set_ball & {9, 28, 44}) == 3: return 1454 - if len(set_ball & {9, 29, 30}) == 3: return 1455 - if len(set_ball & {9, 29, 35}) == 3: return 1456 - if len(set_ball & {9, 29, 36}) == 3: return 1457 - if len(set_ball & {9, 29, 42}) == 3: return 1458 - if len(set_ball & {9, 29, 44}) == 3: return 1459 - if len(set_ball & {9, 30, 32}) == 3: return 1460 - if len(set_ball & {9, 30, 38}) == 3: return 1461 - if len(set_ball & {9, 30, 45}) == 3: return 1462 - if len(set_ball & {9, 31, 36}) == 3: return 1463 - if len(set_ball & {9, 31, 37}) == 3: return 1464 - if len(set_ball & {9, 31, 42}) == 3: return 1465 - if len(set_ball & {9, 31, 43}) == 3: return 1466 - if len(set_ball & {9, 32, 34}) == 3: return 1467 - if len(set_ball & {9, 32, 41}) == 3: return 1468 - if len(set_ball & {9, 32, 45}) == 3: return 1469 - if len(set_ball & {9, 33, 35}) == 3: return 1470 - if len(set_ball & {9, 34, 44}) == 3: return 1471 - if len(set_ball & {9, 35, 36}) == 3: return 1472 - if len(set_ball & {9, 35, 44}) == 3: return 1473 - if len(set_ball & {9, 37, 41}) == 3: return 1474 - if len(set_ball & {9, 37, 43}) == 3: return 1475 - if len(set_ball & {9, 37, 45}) == 3: return 1476 - if len(set_ball & {9, 38, 40}) == 3: return 1477 - if len(set_ball & {9, 38, 41}) == 3: return 1478 - if len(set_ball & {9, 38, 42}) == 3: return 1479 - if len(set_ball & {9, 39, 40}) == 3: return 1480 - if len(set_ball & {9, 39, 42}) == 3: return 1481 - if len(set_ball & {9, 40, 44}) == 3: return 1482 - if len(set_ball & {9, 42, 45}) == 3: return 1483 - if len(set_ball & {9, 43, 44}) == 3: return 1484 - if len(set_ball & {9, 44, 45}) == 3: return 1485 - if len(set_ball & {10, 11, 13}) == 3: return 1486 - if len(set_ball & {10, 11, 16}) == 3: return 1487 - if len(set_ball & {10, 11, 17}) == 3: return 1488 - if len(set_ball & {10, 11, 30}) == 3: return 1489 - if len(set_ball & {10, 11, 33}) == 3: return 1490 - if len(set_ball & {10, 11, 40}) == 3: return 1491 - if len(set_ball & {10, 11, 43}) == 3: return 1492 - if len(set_ball & {10, 12, 17}) == 3: return 1493 - if len(set_ball & {10, 12, 23}) == 3: return 1494 - if len(set_ball & {10, 12, 30}) == 3: return 1495 - if len(set_ball & {10, 12, 32}) == 3: return 1496 - if len(set_ball & {10, 12, 34}) == 3: return 1497 - if len(set_ball & {10, 12, 36}) == 3: return 1498 - if len(set_ball & {10, 12, 37}) == 3: return 1499 - if len(set_ball & {10, 12, 41}) == 3: return 1500 - if len(set_ball & {10, 13, 14}) == 3: return 1501 - if len(set_ball & {10, 13, 17}) == 3: return 1502 - if len(set_ball & {10, 13, 20}) == 3: return 1503 - if len(set_ball & {10, 13, 30}) == 3: return 1504 - if len(set_ball & {10, 14, 17}) == 3: return 1505 - if len(set_ball & {10, 14, 26}) == 3: return 1506 - if len(set_ball & {10, 14, 34}) == 3: return 1507 - if len(set_ball & {10, 14, 41}) == 3: return 1508 - if len(set_ball & {10, 15, 28}) == 3: return 1509 - if len(set_ball & {10, 15, 31}) == 3: return 1510 - if len(set_ball & {10, 15, 40}) == 3: return 1511 - if len(set_ball & {10, 15, 45}) == 3: return 1512 - if len(set_ball & {10, 16, 21}) == 3: return 1513 - if len(set_ball & {10, 16, 22}) == 3: return 1514 - if len(set_ball & {10, 16, 23}) == 3: return 1515 - if len(set_ball & {10, 16, 30}) == 3: return 1516 - if len(set_ball & {10, 17, 20}) == 3: return 1517 - if len(set_ball & {10, 17, 24}) == 3: return 1518 - if len(set_ball & {10, 17, 25}) == 3: return 1519 - if len(set_ball & {10, 17, 26}) == 3: return 1520 - if len(set_ball & {10, 17, 28}) == 3: return 1521 - if len(set_ball & {10, 17, 36}) == 3: return 1522 - if len(set_ball & {10, 17, 39}) == 3: return 1523 - if len(set_ball & {10, 17, 40}) == 3: return 1524 - if len(set_ball & {10, 17, 41}) == 3: return 1525 - if len(set_ball & {10, 17, 45}) == 3: return 1526 - if len(set_ball & {10, 18, 33}) == 3: return 1527 - if len(set_ball & {10, 19, 26}) == 3: return 1528 - if len(set_ball & {10, 19, 30}) == 3: return 1529 - if len(set_ball & {10, 19, 36}) == 3: return 1530 - if len(set_ball & {10, 19, 41}) == 3: return 1531 - if len(set_ball & {10, 20, 22}) == 3: return 1532 - if len(set_ball & {10, 20, 29}) == 3: return 1533 - if len(set_ball & {10, 20, 37}) == 3: return 1534 - if len(set_ball & {10, 21, 23}) == 3: return 1535 - if len(set_ball & {10, 21, 24}) == 3: return 1536 - if len(set_ball & {10, 21, 26}) == 3: return 1537 - if len(set_ball & {10, 21, 28}) == 3: return 1538 - if len(set_ball & {10, 21, 32}) == 3: return 1539 - if len(set_ball & {10, 21, 33}) == 3: return 1540 - if len(set_ball & {10, 21, 44}) == 3: return 1541 - if len(set_ball & {10, 22, 26}) == 3: return 1542 - if len(set_ball & {10, 22, 38}) == 3: return 1543 - if len(set_ball & {10, 22, 41}) == 3: return 1544 - if len(set_ball & {10, 22, 45}) == 3: return 1545 - if len(set_ball & {10, 23, 34}) == 3: return 1546 - if len(set_ball & {10, 23, 38}) == 3: return 1547 - if len(set_ball & {10, 23, 41}) == 3: return 1548 - if len(set_ball & {10, 23, 45}) == 3: return 1549 - if len(set_ball & {10, 24, 30}) == 3: return 1550 - if len(set_ball & {10, 24, 34}) == 3: return 1551 - if len(set_ball & {10, 25, 28}) == 3: return 1552 - if len(set_ball & {10, 25, 30}) == 3: return 1553 - if len(set_ball & {10, 25, 32}) == 3: return 1554 - if len(set_ball & {10, 25, 38}) == 3: return 1555 - if len(set_ball & {10, 25, 39}) == 3: return 1556 - if len(set_ball & {10, 25, 42}) == 3: return 1557 - if len(set_ball & {10, 25, 43}) == 3: return 1558 - if len(set_ball & {10, 25, 45}) == 3: return 1559 - if len(set_ball & {10, 26, 27}) == 3: return 1560 - if len(set_ball & {10, 26, 30}) == 3: return 1561 - if len(set_ball & {10, 26, 42}) == 3: return 1562 - if len(set_ball & {10, 26, 45}) == 3: return 1563 - if len(set_ball & {10, 27, 30}) == 3: return 1564 - if len(set_ball & {10, 27, 32}) == 3: return 1565 - if len(set_ball & {10, 27, 34}) == 3: return 1566 - if len(set_ball & {10, 27, 36}) == 3: return 1567 - if len(set_ball & {10, 27, 44}) == 3: return 1568 - if len(set_ball & {10, 28, 29}) == 3: return 1569 - if len(set_ball & {10, 28, 32}) == 3: return 1570 - if len(set_ball & {10, 28, 35}) == 3: return 1571 - if len(set_ball & {10, 28, 43}) == 3: return 1572 - if len(set_ball & {10, 29, 30}) == 3: return 1573 - if len(set_ball & {10, 29, 36}) == 3: return 1574 - if len(set_ball & {10, 29, 39}) == 3: return 1575 - if len(set_ball & {10, 30, 40}) == 3: return 1576 - if len(set_ball & {10, 30, 41}) == 3: return 1577 - if len(set_ball & {10, 30, 45}) == 3: return 1578 - if len(set_ball & {10, 31, 38}) == 3: return 1579 - if len(set_ball & {10, 32, 39}) == 3: return 1580 - if len(set_ball & {10, 33, 34}) == 3: return 1581 - if len(set_ball & {10, 33, 39}) == 3: return 1582 - if len(set_ball & {10, 33, 43}) == 3: return 1583 - if len(set_ball & {10, 34, 39}) == 3: return 1584 - if len(set_ball & {10, 35, 44}) == 3: return 1585 - if len(set_ball & {10, 35, 45}) == 3: return 1586 - if len(set_ball & {10, 36, 43}) == 3: return 1587 - if len(set_ball & {10, 36, 45}) == 3: return 1588 - if len(set_ball & {10, 37, 42}) == 3: return 1589 - if len(set_ball & {10, 37, 44}) == 3: return 1590 - if len(set_ball & {10, 37, 45}) == 3: return 1591 - if len(set_ball & {10, 38, 39}) == 3: return 1592 - if len(set_ball & {10, 39, 45}) == 3: return 1593 - if len(set_ball & {10, 42, 45}) == 3: return 1594 - if len(set_ball & {10, 43, 45}) == 3: return 1595 - if len(set_ball & {11, 12, 17}) == 3: return 1596 - if len(set_ball & {11, 12, 22}) == 3: return 1597 - if len(set_ball & {11, 12, 28}) == 3: return 1598 - if len(set_ball & {11, 12, 30}) == 3: return 1599 - if len(set_ball & {11, 12, 34}) == 3: return 1600 - if len(set_ball & {11, 12, 40}) == 3: return 1601 - if len(set_ball & {11, 12, 41}) == 3: return 1602 - if len(set_ball & {11, 12, 43}) == 3: return 1603 - if len(set_ball & {11, 13, 27}) == 3: return 1604 - if len(set_ball & {11, 13, 39}) == 3: return 1605 - if len(set_ball & {11, 13, 41}) == 3: return 1606 - if len(set_ball & {11, 14, 20}) == 3: return 1607 - if len(set_ball & {11, 14, 24}) == 3: return 1608 - if len(set_ball & {11, 14, 25}) == 3: return 1609 - if len(set_ball & {11, 14, 34}) == 3: return 1610 - if len(set_ball & {11, 14, 40}) == 3: return 1611 - if len(set_ball & {11, 14, 42}) == 3: return 1612 - if len(set_ball & {11, 14, 44}) == 3: return 1613 - if len(set_ball & {11, 15, 19}) == 3: return 1614 - if len(set_ball & {11, 15, 22}) == 3: return 1615 - if len(set_ball & {11, 15, 27}) == 3: return 1616 - if len(set_ball & {11, 15, 29}) == 3: return 1617 - if len(set_ball & {11, 15, 30}) == 3: return 1618 - if len(set_ball & {11, 15, 33}) == 3: return 1619 - if len(set_ball & {11, 15, 38}) == 3: return 1620 - if len(set_ball & {11, 16, 20}) == 3: return 1621 - if len(set_ball & {11, 16, 23}) == 3: return 1622 - if len(set_ball & {11, 16, 30}) == 3: return 1623 - if len(set_ball & {11, 16, 34}) == 3: return 1624 - if len(set_ball & {11, 16, 42}) == 3: return 1625 - if len(set_ball & {11, 17, 32}) == 3: return 1626 - if len(set_ball & {11, 17, 41}) == 3: return 1627 - if len(set_ball & {11, 17, 42}) == 3: return 1628 - if len(set_ball & {11, 17, 43}) == 3: return 1629 - if len(set_ball & {11, 18, 25}) == 3: return 1630 - if len(set_ball & {11, 18, 30}) == 3: return 1631 - if len(set_ball & {11, 18, 32}) == 3: return 1632 - if len(set_ball & {11, 18, 33}) == 3: return 1633 - if len(set_ball & {11, 18, 34}) == 3: return 1634 - if len(set_ball & {11, 18, 44}) == 3: return 1635 - if len(set_ball & {11, 19, 23}) == 3: return 1636 - if len(set_ball & {11, 19, 30}) == 3: return 1637 - if len(set_ball & {11, 19, 33}) == 3: return 1638 - if len(set_ball & {11, 19, 37}) == 3: return 1639 - if len(set_ball & {11, 19, 38}) == 3: return 1640 - if len(set_ball & {11, 19, 40}) == 3: return 1641 - if len(set_ball & {11, 19, 42}) == 3: return 1642 - if len(set_ball & {11, 19, 44}) == 3: return 1643 - if len(set_ball & {11, 20, 24}) == 3: return 1644 - if len(set_ball & {11, 20, 30}) == 3: return 1645 - if len(set_ball & {11, 20, 34}) == 3: return 1646 - if len(set_ball & {11, 20, 36}) == 3: return 1647 - if len(set_ball & {11, 20, 38}) == 3: return 1648 - if len(set_ball & {11, 20, 40}) == 3: return 1649 - if len(set_ball & {11, 21, 29}) == 3: return 1650 - if len(set_ball & {11, 21, 40}) == 3: return 1651 - if len(set_ball & {11, 22, 23}) == 3: return 1652 - if len(set_ball & {11, 22, 27}) == 3: return 1653 - if len(set_ball & {11, 22, 31}) == 3: return 1654 - if len(set_ball & {11, 22, 33}) == 3: return 1655 - if len(set_ball & {11, 22, 34}) == 3: return 1656 - if len(set_ball & {11, 22, 40}) == 3: return 1657 - if len(set_ball & {11, 22, 43}) == 3: return 1658 - if len(set_ball & {11, 22, 45}) == 3: return 1659 - if len(set_ball & {11, 23, 27}) == 3: return 1660 - if len(set_ball & {11, 23, 31}) == 3: return 1661 - if len(set_ball & {11, 23, 33}) == 3: return 1662 - if len(set_ball & {11, 23, 41}) == 3: return 1663 - if len(set_ball & {11, 24, 25}) == 3: return 1664 - if len(set_ball & {11, 24, 31}) == 3: return 1665 - if len(set_ball & {11, 24, 34}) == 3: return 1666 - if len(set_ball & {11, 24, 43}) == 3: return 1667 - if len(set_ball & {11, 25, 37}) == 3: return 1668 - if len(set_ball & {11, 25, 38}) == 3: return 1669 - if len(set_ball & {11, 25, 42}) == 3: return 1670 - if len(set_ball & {11, 25, 43}) == 3: return 1671 - if len(set_ball & {11, 26, 30}) == 3: return 1672 - if len(set_ball & {11, 26, 32}) == 3: return 1673 - if len(set_ball & {11, 26, 42}) == 3: return 1674 - if len(set_ball & {11, 27, 30}) == 3: return 1675 - if len(set_ball & {11, 27, 34}) == 3: return 1676 - if len(set_ball & {11, 27, 42}) == 3: return 1677 - if len(set_ball & {11, 27, 43}) == 3: return 1678 - if len(set_ball & {11, 27, 45}) == 3: return 1679 - if len(set_ball & {11, 28, 31}) == 3: return 1680 - if len(set_ball & {11, 28, 38}) == 3: return 1681 - if len(set_ball & {11, 29, 34}) == 3: return 1682 - if len(set_ball & {11, 29, 35}) == 3: return 1683 - if len(set_ball & {11, 29, 37}) == 3: return 1684 - if len(set_ball & {11, 29, 40}) == 3: return 1685 - if len(set_ball & {11, 30, 36}) == 3: return 1686 - if len(set_ball & {11, 30, 37}) == 3: return 1687 - if len(set_ball & {11, 31, 45}) == 3: return 1688 - if len(set_ball & {11, 32, 34}) == 3: return 1689 - if len(set_ball & {11, 32, 41}) == 3: return 1690 - if len(set_ball & {11, 32, 42}) == 3: return 1691 - if len(set_ball & {11, 32, 43}) == 3: return 1692 - if len(set_ball & {11, 33, 34}) == 3: return 1693 - if len(set_ball & {11, 33, 36}) == 3: return 1694 - if len(set_ball & {11, 33, 41}) == 3: return 1695 - if len(set_ball & {11, 33, 45}) == 3: return 1696 - if len(set_ball & {11, 34, 36}) == 3: return 1697 - if len(set_ball & {11, 34, 37}) == 3: return 1698 - if len(set_ball & {11, 34, 38}) == 3: return 1699 - if len(set_ball & {11, 34, 39}) == 3: return 1700 - if len(set_ball & {11, 34, 40}) == 3: return 1701 - if len(set_ball & {11, 34, 45}) == 3: return 1702 - if len(set_ball & {11, 35, 38}) == 3: return 1703 - if len(set_ball & {11, 36, 41}) == 3: return 1704 - if len(set_ball & {11, 36, 42}) == 3: return 1705 - if len(set_ball & {11, 36, 44}) == 3: return 1706 - if len(set_ball & {11, 37, 38}) == 3: return 1707 - if len(set_ball & {11, 37, 44}) == 3: return 1708 - if len(set_ball & {11, 38, 40}) == 3: return 1709 - if len(set_ball & {11, 39, 40}) == 3: return 1710 - if len(set_ball & {11, 40, 42}) == 3: return 1711 - if len(set_ball & {11, 40, 43}) == 3: return 1712 - if len(set_ball & {11, 40, 44}) == 3: return 1713 - if len(set_ball & {11, 40, 45}) == 3: return 1714 - if len(set_ball & {12, 13, 14}) == 3: return 1715 - if len(set_ball & {12, 13, 16}) == 3: return 1716 - if len(set_ball & {12, 13, 23}) == 3: return 1717 - if len(set_ball & {12, 13, 26}) == 3: return 1718 - if len(set_ball & {12, 13, 27}) == 3: return 1719 - if len(set_ball & {12, 13, 28}) == 3: return 1720 - if len(set_ball & {12, 13, 30}) == 3: return 1721 - if len(set_ball & {12, 14, 19}) == 3: return 1722 - if len(set_ball & {12, 14, 29}) == 3: return 1723 - if len(set_ball & {12, 14, 31}) == 3: return 1724 - if len(set_ball & {12, 14, 36}) == 3: return 1725 - if len(set_ball & {12, 15, 31}) == 3: return 1726 - if len(set_ball & {12, 15, 33}) == 3: return 1727 - if len(set_ball & {12, 15, 35}) == 3: return 1728 - if len(set_ball & {12, 16, 17}) == 3: return 1729 - if len(set_ball & {12, 16, 25}) == 3: return 1730 - if len(set_ball & {12, 16, 27}) == 3: return 1731 - if len(set_ball & {12, 16, 31}) == 3: return 1732 - if len(set_ball & {12, 16, 32}) == 3: return 1733 - if len(set_ball & {12, 16, 33}) == 3: return 1734 - if len(set_ball & {12, 16, 35}) == 3: return 1735 - if len(set_ball & {12, 16, 36}) == 3: return 1736 - if len(set_ball & {12, 17, 27}) == 3: return 1737 - if len(set_ball & {12, 17, 30}) == 3: return 1738 - if len(set_ball & {12, 17, 38}) == 3: return 1739 - if len(set_ball & {12, 18, 33}) == 3: return 1740 - if len(set_ball & {12, 18, 36}) == 3: return 1741 - if len(set_ball & {12, 18, 37}) == 3: return 1742 - if len(set_ball & {12, 18, 44}) == 3: return 1743 - if len(set_ball & {12, 18, 45}) == 3: return 1744 - if len(set_ball & {12, 19, 30}) == 3: return 1745 - if len(set_ball & {12, 19, 38}) == 3: return 1746 - if len(set_ball & {12, 20, 22}) == 3: return 1747 - if len(set_ball & {12, 20, 32}) == 3: return 1748 - if len(set_ball & {12, 20, 37}) == 3: return 1749 - if len(set_ball & {12, 20, 40}) == 3: return 1750 - if len(set_ball & {12, 20, 43}) == 3: return 1751 - if len(set_ball & {12, 21, 22}) == 3: return 1752 - if len(set_ball & {12, 21, 23}) == 3: return 1753 - if len(set_ball & {12, 21, 28}) == 3: return 1754 - if len(set_ball & {12, 21, 33}) == 3: return 1755 - if len(set_ball & {12, 21, 42}) == 3: return 1756 - if len(set_ball & {12, 21, 44}) == 3: return 1757 - if len(set_ball & {12, 22, 27}) == 3: return 1758 - if len(set_ball & {12, 22, 29}) == 3: return 1759 - if len(set_ball & {12, 22, 35}) == 3: return 1760 - if len(set_ball & {12, 22, 38}) == 3: return 1761 - if len(set_ball & {12, 22, 39}) == 3: return 1762 - if len(set_ball & {12, 22, 43}) == 3: return 1763 - if len(set_ball & {12, 22, 45}) == 3: return 1764 - if len(set_ball & {12, 23, 24}) == 3: return 1765 - if len(set_ball & {12, 23, 25}) == 3: return 1766 - if len(set_ball & {12, 23, 29}) == 3: return 1767 - if len(set_ball & {12, 23, 33}) == 3: return 1768 - if len(set_ball & {12, 23, 38}) == 3: return 1769 - if len(set_ball & {12, 23, 40}) == 3: return 1770 - if len(set_ball & {12, 23, 41}) == 3: return 1771 - if len(set_ball & {12, 25, 28}) == 3: return 1772 - if len(set_ball & {12, 25, 30}) == 3: return 1773 - if len(set_ball & {12, 26, 32}) == 3: return 1774 - if len(set_ball & {12, 26, 37}) == 3: return 1775 - if len(set_ball & {12, 27, 31}) == 3: return 1776 - if len(set_ball & {12, 27, 34}) == 3: return 1777 - if len(set_ball & {12, 27, 42}) == 3: return 1778 - if len(set_ball & {12, 28, 29}) == 3: return 1779 - if len(set_ball & {12, 28, 31}) == 3: return 1780 - if len(set_ball & {12, 28, 33}) == 3: return 1781 - if len(set_ball & {12, 28, 37}) == 3: return 1782 - if len(set_ball & {12, 29, 30}) == 3: return 1783 - if len(set_ball & {12, 30, 33}) == 3: return 1784 - if len(set_ball & {12, 30, 35}) == 3: return 1785 - if len(set_ball & {12, 31, 36}) == 3: return 1786 - if len(set_ball & {12, 31, 37}) == 3: return 1787 - if len(set_ball & {12, 31, 45}) == 3: return 1788 - if len(set_ball & {12, 33, 34}) == 3: return 1789 - if len(set_ball & {12, 33, 35}) == 3: return 1790 - if len(set_ball & {12, 33, 37}) == 3: return 1791 - if len(set_ball & {12, 33, 43}) == 3: return 1792 - if len(set_ball & {12, 34, 39}) == 3: return 1793 - if len(set_ball & {12, 35, 36}) == 3: return 1794 - if len(set_ball & {12, 35, 39}) == 3: return 1795 - if len(set_ball & {12, 35, 44}) == 3: return 1796 - if len(set_ball & {12, 36, 38}) == 3: return 1797 - if len(set_ball & {12, 37, 42}) == 3: return 1798 - if len(set_ball & {12, 37, 43}) == 3: return 1799 - if len(set_ball & {12, 37, 44}) == 3: return 1800 - if len(set_ball & {12, 38, 39}) == 3: return 1801 - if len(set_ball & {12, 38, 44}) == 3: return 1802 - if len(set_ball & {12, 43, 45}) == 3: return 1803 - if len(set_ball & {13, 14, 23}) == 3: return 1804 - if len(set_ball & {13, 14, 29}) == 3: return 1805 - if len(set_ball & {13, 14, 31}) == 3: return 1806 - if len(set_ball & {13, 15, 19}) == 3: return 1807 - if len(set_ball & {13, 15, 20}) == 3: return 1808 - if len(set_ball & {13, 15, 22}) == 3: return 1809 - if len(set_ball & {13, 15, 30}) == 3: return 1810 - if len(set_ball & {13, 15, 32}) == 3: return 1811 - if len(set_ball & {13, 16, 17}) == 3: return 1812 - if len(set_ball & {13, 16, 20}) == 3: return 1813 - if len(set_ball & {13, 16, 21}) == 3: return 1814 - if len(set_ball & {13, 16, 22}) == 3: return 1815 - if len(set_ball & {13, 16, 34}) == 3: return 1816 - if len(set_ball & {13, 16, 39}) == 3: return 1817 - if len(set_ball & {13, 16, 40}) == 3: return 1818 - if len(set_ball & {13, 16, 41}) == 3: return 1819 - if len(set_ball & {13, 16, 42}) == 3: return 1820 - if len(set_ball & {13, 17, 24}) == 3: return 1821 - if len(set_ball & {13, 17, 26}) == 3: return 1822 - if len(set_ball & {13, 17, 30}) == 3: return 1823 - if len(set_ball & {13, 17, 35}) == 3: return 1824 - if len(set_ball & {13, 17, 37}) == 3: return 1825 - if len(set_ball & {13, 17, 38}) == 3: return 1826 - if len(set_ball & {13, 18, 36}) == 3: return 1827 - if len(set_ball & {13, 18, 43}) == 3: return 1828 - if len(set_ball & {13, 19, 21}) == 3: return 1829 - if len(set_ball & {13, 19, 23}) == 3: return 1830 - if len(set_ball & {13, 19, 24}) == 3: return 1831 - if len(set_ball & {13, 19, 29}) == 3: return 1832 - if len(set_ball & {13, 19, 30}) == 3: return 1833 - if len(set_ball & {13, 19, 34}) == 3: return 1834 - if len(set_ball & {13, 19, 39}) == 3: return 1835 - if len(set_ball & {13, 19, 41}) == 3: return 1836 - if len(set_ball & {13, 19, 44}) == 3: return 1837 - if len(set_ball & {13, 20, 26}) == 3: return 1838 - if len(set_ball & {13, 20, 34}) == 3: return 1839 - if len(set_ball & {13, 21, 27}) == 3: return 1840 - if len(set_ball & {13, 21, 28}) == 3: return 1841 - if len(set_ball & {13, 21, 31}) == 3: return 1842 - if len(set_ball & {13, 21, 35}) == 3: return 1843 - if len(set_ball & {13, 21, 36}) == 3: return 1844 - if len(set_ball & {13, 21, 38}) == 3: return 1845 - if len(set_ball & {13, 21, 41}) == 3: return 1846 - if len(set_ball & {13, 22, 41}) == 3: return 1847 - if len(set_ball & {13, 22, 43}) == 3: return 1848 - if len(set_ball & {13, 23, 25}) == 3: return 1849 - if len(set_ball & {13, 23, 27}) == 3: return 1850 - if len(set_ball & {13, 23, 29}) == 3: return 1851 - if len(set_ball & {13, 23, 30}) == 3: return 1852 - if len(set_ball & {13, 23, 33}) == 3: return 1853 - if len(set_ball & {13, 23, 34}) == 3: return 1854 - if len(set_ball & {13, 23, 37}) == 3: return 1855 - if len(set_ball & {13, 23, 41}) == 3: return 1856 - if len(set_ball & {13, 23, 42}) == 3: return 1857 - if len(set_ball & {13, 24, 30}) == 3: return 1858 - if len(set_ball & {13, 24, 31}) == 3: return 1859 - if len(set_ball & {13, 24, 43}) == 3: return 1860 - if len(set_ball & {13, 25, 30}) == 3: return 1861 - if len(set_ball & {13, 25, 39}) == 3: return 1862 - if len(set_ball & {13, 25, 40}) == 3: return 1863 - if len(set_ball & {13, 26, 41}) == 3: return 1864 - if len(set_ball & {13, 26, 42}) == 3: return 1865 - if len(set_ball & {13, 26, 45}) == 3: return 1866 - if len(set_ball & {13, 27, 33}) == 3: return 1867 - if len(set_ball & {13, 27, 35}) == 3: return 1868 - if len(set_ball & {13, 27, 36}) == 3: return 1869 - if len(set_ball & {13, 27, 39}) == 3: return 1870 - if len(set_ball & {13, 28, 33}) == 3: return 1871 - if len(set_ball & {13, 28, 35}) == 3: return 1872 - if len(set_ball & {13, 29, 32}) == 3: return 1873 - if len(set_ball & {13, 29, 45}) == 3: return 1874 - if len(set_ball & {13, 30, 32}) == 3: return 1875 - if len(set_ball & {13, 30, 34}) == 3: return 1876 - if len(set_ball & {13, 30, 37}) == 3: return 1877 - if len(set_ball & {13, 30, 42}) == 3: return 1878 - if len(set_ball & {13, 30, 44}) == 3: return 1879 - if len(set_ball & {13, 31, 40}) == 3: return 1880 - if len(set_ball & {13, 34, 35}) == 3: return 1881 - if len(set_ball & {13, 34, 37}) == 3: return 1882 - if len(set_ball & {13, 35, 36}) == 3: return 1883 - if len(set_ball & {13, 35, 37}) == 3: return 1884 - if len(set_ball & {13, 35, 41}) == 3: return 1885 - if len(set_ball & {13, 35, 42}) == 3: return 1886 - if len(set_ball & {13, 35, 44}) == 3: return 1887 - if len(set_ball & {13, 36, 39}) == 3: return 1888 - if len(set_ball & {13, 36, 43}) == 3: return 1889 - if len(set_ball & {13, 37, 42}) == 3: return 1890 - if len(set_ball & {13, 37, 44}) == 3: return 1891 - if len(set_ball & {13, 38, 44}) == 3: return 1892 - if len(set_ball & {13, 39, 44}) == 3: return 1893 - if len(set_ball & {13, 41, 43}) == 3: return 1894 - if len(set_ball & {13, 42, 43}) == 3: return 1895 - if len(set_ball & {13, 42, 44}) == 3: return 1896 - if len(set_ball & {13, 43, 44}) == 3: return 1897 - if len(set_ball & {14, 16, 20}) == 3: return 1898 - if len(set_ball & {14, 16, 22}) == 3: return 1899 - if len(set_ball & {14, 16, 23}) == 3: return 1900 - if len(set_ball & {14, 16, 26}) == 3: return 1901 - if len(set_ball & {14, 16, 30}) == 3: return 1902 - if len(set_ball & {14, 16, 32}) == 3: return 1903 - if len(set_ball & {14, 16, 33}) == 3: return 1904 - if len(set_ball & {14, 16, 34}) == 3: return 1905 - if len(set_ball & {14, 16, 41}) == 3: return 1906 - if len(set_ball & {14, 17, 23}) == 3: return 1907 - if len(set_ball & {14, 17, 25}) == 3: return 1908 - if len(set_ball & {14, 17, 28}) == 3: return 1909 - if len(set_ball & {14, 17, 29}) == 3: return 1910 - if len(set_ball & {14, 17, 34}) == 3: return 1911 - if len(set_ball & {14, 17, 43}) == 3: return 1912 - if len(set_ball & {14, 18, 19}) == 3: return 1913 - if len(set_ball & {14, 18, 25}) == 3: return 1914 - if len(set_ball & {14, 18, 33}) == 3: return 1915 - if len(set_ball & {14, 18, 38}) == 3: return 1916 - if len(set_ball & {14, 18, 40}) == 3: return 1917 - if len(set_ball & {14, 18, 41}) == 3: return 1918 - if len(set_ball & {14, 18, 43}) == 3: return 1919 - if len(set_ball & {14, 18, 45}) == 3: return 1920 - if len(set_ball & {14, 19, 29}) == 3: return 1921 - if len(set_ball & {14, 19, 32}) == 3: return 1922 - if len(set_ball & {14, 19, 33}) == 3: return 1923 - if len(set_ball & {14, 19, 42}) == 3: return 1924 - if len(set_ball & {14, 20, 21}) == 3: return 1925 - if len(set_ball & {14, 20, 26}) == 3: return 1926 - if len(set_ball & {14, 20, 27}) == 3: return 1927 - if len(set_ball & {14, 20, 29}) == 3: return 1928 - if len(set_ball & {14, 20, 32}) == 3: return 1929 - if len(set_ball & {14, 20, 41}) == 3: return 1930 - if len(set_ball & {14, 20, 45}) == 3: return 1931 - if len(set_ball & {14, 21, 24}) == 3: return 1932 - if len(set_ball & {14, 21, 28}) == 3: return 1933 - if len(set_ball & {14, 21, 33}) == 3: return 1934 - if len(set_ball & {14, 21, 42}) == 3: return 1935 - if len(set_ball & {14, 22, 29}) == 3: return 1936 - if len(set_ball & {14, 22, 42}) == 3: return 1937 - if len(set_ball & {14, 23, 24}) == 3: return 1938 - if len(set_ball & {14, 23, 27}) == 3: return 1939 - if len(set_ball & {14, 23, 29}) == 3: return 1940 - if len(set_ball & {14, 23, 41}) == 3: return 1941 - if len(set_ball & {14, 24, 29}) == 3: return 1942 - if len(set_ball & {14, 24, 30}) == 3: return 1943 - if len(set_ball & {14, 24, 38}) == 3: return 1944 - if len(set_ball & {14, 24, 43}) == 3: return 1945 - if len(set_ball & {14, 25, 38}) == 3: return 1946 - if len(set_ball & {14, 25, 41}) == 3: return 1947 - if len(set_ball & {14, 25, 42}) == 3: return 1948 - if len(set_ball & {14, 26, 29}) == 3: return 1949 - if len(set_ball & {14, 27, 41}) == 3: return 1950 - if len(set_ball & {14, 28, 33}) == 3: return 1951 - if len(set_ball & {14, 28, 38}) == 3: return 1952 - if len(set_ball & {14, 28, 42}) == 3: return 1953 - if len(set_ball & {14, 28, 43}) == 3: return 1954 - if len(set_ball & {14, 28, 44}) == 3: return 1955 - if len(set_ball & {14, 29, 38}) == 3: return 1956 - if len(set_ball & {14, 29, 39}) == 3: return 1957 - if len(set_ball & {14, 29, 41}) == 3: return 1958 - if len(set_ball & {14, 29, 42}) == 3: return 1959 - if len(set_ball & {14, 29, 43}) == 3: return 1960 - if len(set_ball & {14, 30, 35}) == 3: return 1961 - if len(set_ball & {14, 31, 35}) == 3: return 1962 - if len(set_ball & {14, 31, 43}) == 3: return 1963 - if len(set_ball & {14, 32, 43}) == 3: return 1964 - if len(set_ball & {14, 32, 44}) == 3: return 1965 - if len(set_ball & {14, 34, 36}) == 3: return 1966 - if len(set_ball & {14, 34, 39}) == 3: return 1967 - if len(set_ball & {14, 36, 41}) == 3: return 1968 - if len(set_ball & {14, 37, 43}) == 3: return 1969 - if len(set_ball & {14, 37, 44}) == 3: return 1970 - if len(set_ball & {14, 38, 41}) == 3: return 1971 - if len(set_ball & {14, 38, 44}) == 3: return 1972 - if len(set_ball & {14, 41, 43}) == 3: return 1973 - if len(set_ball & {14, 41, 44}) == 3: return 1974 - if len(set_ball & {14, 41, 45}) == 3: return 1975 - if len(set_ball & {14, 42, 43}) == 3: return 1976 - if len(set_ball & {15, 16, 18}) == 3: return 1977 - if len(set_ball & {15, 16, 23}) == 3: return 1978 - if len(set_ball & {15, 16, 27}) == 3: return 1979 - if len(set_ball & {15, 16, 33}) == 3: return 1980 - if len(set_ball & {15, 16, 35}) == 3: return 1981 - if len(set_ball & {15, 16, 36}) == 3: return 1982 - if len(set_ball & {15, 16, 40}) == 3: return 1983 - if len(set_ball & {15, 16, 44}) == 3: return 1984 - if len(set_ball & {15, 17, 20}) == 3: return 1985 - if len(set_ball & {15, 17, 22}) == 3: return 1986 - if len(set_ball & {15, 17, 28}) == 3: return 1987 - if len(set_ball & {15, 17, 32}) == 3: return 1988 - if len(set_ball & {15, 18, 20}) == 3: return 1989 - if len(set_ball & {15, 18, 25}) == 3: return 1990 - if len(set_ball & {15, 18, 29}) == 3: return 1991 - if len(set_ball & {15, 18, 30}) == 3: return 1992 - if len(set_ball & {15, 18, 31}) == 3: return 1993 - if len(set_ball & {15, 18, 33}) == 3: return 1994 - if len(set_ball & {15, 18, 37}) == 3: return 1995 - if len(set_ball & {15, 18, 38}) == 3: return 1996 - if len(set_ball & {15, 18, 43}) == 3: return 1997 - if len(set_ball & {15, 19, 20}) == 3: return 1998 - if len(set_ball & {15, 19, 29}) == 3: return 1999 - if len(set_ball & {15, 19, 31}) == 3: return 2000 - if len(set_ball & {15, 19, 32}) == 3: return 2001 - if len(set_ball & {15, 19, 33}) == 3: return 2002 - if len(set_ball & {15, 19, 35}) == 3: return 2003 - if len(set_ball & {15, 20, 32}) == 3: return 2004 - if len(set_ball & {15, 20, 37}) == 3: return 2005 - if len(set_ball & {15, 20, 40}) == 3: return 2006 - if len(set_ball & {15, 20, 45}) == 3: return 2007 - if len(set_ball & {15, 21, 24}) == 3: return 2008 - if len(set_ball & {15, 21, 37}) == 3: return 2009 - if len(set_ball & {15, 21, 40}) == 3: return 2010 - if len(set_ball & {15, 21, 42}) == 3: return 2011 - if len(set_ball & {15, 22, 29}) == 3: return 2012 - if len(set_ball & {15, 22, 30}) == 3: return 2013 - if len(set_ball & {15, 22, 31}) == 3: return 2014 - if len(set_ball & {15, 23, 24}) == 3: return 2015 - if len(set_ball & {15, 23, 27}) == 3: return 2016 - if len(set_ball & {15, 23, 30}) == 3: return 2017 - if len(set_ball & {15, 23, 33}) == 3: return 2018 - if len(set_ball & {15, 23, 36}) == 3: return 2019 - if len(set_ball & {15, 24, 26}) == 3: return 2020 - if len(set_ball & {15, 24, 34}) == 3: return 2021 - if len(set_ball & {15, 25, 27}) == 3: return 2022 - if len(set_ball & {15, 25, 38}) == 3: return 2023 - if len(set_ball & {15, 25, 40}) == 3: return 2024 - if len(set_ball & {15, 25, 44}) == 3: return 2025 - if len(set_ball & {15, 26, 29}) == 3: return 2026 - if len(set_ball & {15, 26, 32}) == 3: return 2027 - if len(set_ball & {15, 26, 38}) == 3: return 2028 - if len(set_ball & {15, 27, 28}) == 3: return 2029 - if len(set_ball & {15, 27, 31}) == 3: return 2030 - if len(set_ball & {15, 27, 36}) == 3: return 2031 - if len(set_ball & {15, 27, 37}) == 3: return 2032 - if len(set_ball & {15, 27, 38}) == 3: return 2033 - if len(set_ball & {15, 27, 39}) == 3: return 2034 - if len(set_ball & {15, 27, 44}) == 3: return 2035 - if len(set_ball & {15, 28, 32}) == 3: return 2036 - if len(set_ball & {15, 28, 35}) == 3: return 2037 - if len(set_ball & {15, 28, 38}) == 3: return 2038 - if len(set_ball & {15, 28, 44}) == 3: return 2039 - if len(set_ball & {15, 29, 31}) == 3: return 2040 - if len(set_ball & {15, 29, 32}) == 3: return 2041 - if len(set_ball & {15, 29, 33}) == 3: return 2042 - if len(set_ball & {15, 29, 36}) == 3: return 2043 - if len(set_ball & {15, 29, 41}) == 3: return 2044 - if len(set_ball & {15, 30, 32}) == 3: return 2045 - if len(set_ball & {15, 30, 34}) == 3: return 2046 - if len(set_ball & {15, 30, 35}) == 3: return 2047 - if len(set_ball & {15, 30, 36}) == 3: return 2048 - if len(set_ball & {15, 30, 40}) == 3: return 2049 - if len(set_ball & {15, 30, 41}) == 3: return 2050 - if len(set_ball & {15, 30, 42}) == 3: return 2051 - if len(set_ball & {15, 31, 36}) == 3: return 2052 - if len(set_ball & {15, 31, 37}) == 3: return 2053 - if len(set_ball & {15, 31, 39}) == 3: return 2054 - if len(set_ball & {15, 31, 44}) == 3: return 2055 - if len(set_ball & {15, 31, 45}) == 3: return 2056 - if len(set_ball & {15, 32, 37}) == 3: return 2057 - if len(set_ball & {15, 32, 38}) == 3: return 2058 - if len(set_ball & {15, 33, 34}) == 3: return 2059 - if len(set_ball & {15, 33, 36}) == 3: return 2060 - if len(set_ball & {15, 33, 42}) == 3: return 2061 - if len(set_ball & {15, 33, 44}) == 3: return 2062 - if len(set_ball & {15, 36, 43}) == 3: return 2063 - if len(set_ball & {15, 36, 45}) == 3: return 2064 - if len(set_ball & {15, 37, 41}) == 3: return 2065 - if len(set_ball & {15, 38, 39}) == 3: return 2066 - if len(set_ball & {15, 38, 40}) == 3: return 2067 - if len(set_ball & {15, 38, 42}) == 3: return 2068 - if len(set_ball & {15, 38, 44}) == 3: return 2069 - if len(set_ball & {15, 39, 45}) == 3: return 2070 - if len(set_ball & {15, 40, 45}) == 3: return 2071 - if len(set_ball & {15, 41, 45}) == 3: return 2072 - if len(set_ball & {15, 44, 45}) == 3: return 2073 - if len(set_ball & {16, 17, 18}) == 3: return 2074 - if len(set_ball & {16, 17, 21}) == 3: return 2075 - if len(set_ball & {16, 17, 27}) == 3: return 2076 - if len(set_ball & {16, 17, 35}) == 3: return 2077 - if len(set_ball & {16, 18, 22}) == 3: return 2078 - if len(set_ball & {16, 18, 33}) == 3: return 2079 - if len(set_ball & {16, 18, 36}) == 3: return 2080 - if len(set_ball & {16, 18, 40}) == 3: return 2081 - if len(set_ball & {16, 18, 41}) == 3: return 2082 - if len(set_ball & {16, 19, 26}) == 3: return 2083 - if len(set_ball & {16, 19, 28}) == 3: return 2084 - if len(set_ball & {16, 19, 30}) == 3: return 2085 - if len(set_ball & {16, 20, 21}) == 3: return 2086 - if len(set_ball & {16, 20, 22}) == 3: return 2087 - if len(set_ball & {16, 20, 34}) == 3: return 2088 - if len(set_ball & {16, 20, 37}) == 3: return 2089 - if len(set_ball & {16, 20, 38}) == 3: return 2090 - if len(set_ball & {16, 20, 45}) == 3: return 2091 - if len(set_ball & {16, 21, 38}) == 3: return 2092 - if len(set_ball & {16, 21, 40}) == 3: return 2093 - if len(set_ball & {16, 21, 45}) == 3: return 2094 - if len(set_ball & {16, 22, 24}) == 3: return 2095 - if len(set_ball & {16, 22, 25}) == 3: return 2096 - if len(set_ball & {16, 22, 26}) == 3: return 2097 - if len(set_ball & {16, 22, 27}) == 3: return 2098 - if len(set_ball & {16, 22, 32}) == 3: return 2099 - if len(set_ball & {16, 22, 33}) == 3: return 2100 - if len(set_ball & {16, 22, 35}) == 3: return 2101 - if len(set_ball & {16, 22, 41}) == 3: return 2102 - if len(set_ball & {16, 22, 45}) == 3: return 2103 - if len(set_ball & {16, 23, 28}) == 3: return 2104 - if len(set_ball & {16, 23, 37}) == 3: return 2105 - if len(set_ball & {16, 24, 31}) == 3: return 2106 - if len(set_ball & {16, 24, 34}) == 3: return 2107 - if len(set_ball & {16, 25, 28}) == 3: return 2108 - if len(set_ball & {16, 25, 32}) == 3: return 2109 - if len(set_ball & {16, 26, 27}) == 3: return 2110 - if len(set_ball & {16, 26, 32}) == 3: return 2111 - if len(set_ball & {16, 26, 35}) == 3: return 2112 - if len(set_ball & {16, 26, 37}) == 3: return 2113 - if len(set_ball & {16, 27, 30}) == 3: return 2114 - if len(set_ball & {16, 27, 32}) == 3: return 2115 - if len(set_ball & {16, 28, 29}) == 3: return 2116 - if len(set_ball & {16, 28, 31}) == 3: return 2117 - if len(set_ball & {16, 28, 32}) == 3: return 2118 - if len(set_ball & {16, 28, 33}) == 3: return 2119 - if len(set_ball & {16, 28, 45}) == 3: return 2120 - if len(set_ball & {16, 29, 32}) == 3: return 2121 - if len(set_ball & {16, 29, 37}) == 3: return 2122 - if len(set_ball & {16, 29, 39}) == 3: return 2123 - if len(set_ball & {16, 29, 43}) == 3: return 2124 - if len(set_ball & {16, 30, 32}) == 3: return 2125 - if len(set_ball & {16, 30, 33}) == 3: return 2126 - if len(set_ball & {16, 30, 35}) == 3: return 2127 - if len(set_ball & {16, 30, 39}) == 3: return 2128 - if len(set_ball & {16, 31, 33}) == 3: return 2129 - if len(set_ball & {16, 31, 42}) == 3: return 2130 - if len(set_ball & {16, 31, 45}) == 3: return 2131 - if len(set_ball & {16, 32, 35}) == 3: return 2132 - if len(set_ball & {16, 32, 36}) == 3: return 2133 - if len(set_ball & {16, 32, 37}) == 3: return 2134 - if len(set_ball & {16, 32, 40}) == 3: return 2135 - if len(set_ball & {16, 32, 42}) == 3: return 2136 - if len(set_ball & {16, 32, 44}) == 3: return 2137 - if len(set_ball & {16, 33, 34}) == 3: return 2138 - if len(set_ball & {16, 33, 37}) == 3: return 2139 - if len(set_ball & {16, 34, 41}) == 3: return 2140 - if len(set_ball & {16, 35, 41}) == 3: return 2141 - if len(set_ball & {16, 35, 42}) == 3: return 2142 - if len(set_ball & {16, 35, 44}) == 3: return 2143 - if len(set_ball & {16, 37, 42}) == 3: return 2144 - if len(set_ball & {16, 38, 42}) == 3: return 2145 - if len(set_ball & {16, 38, 43}) == 3: return 2146 - if len(set_ball & {16, 43, 44}) == 3: return 2147 - if len(set_ball & {17, 18, 37}) == 3: return 2148 - if len(set_ball & {17, 19, 26}) == 3: return 2149 - if len(set_ball & {17, 19, 29}) == 3: return 2150 - if len(set_ball & {17, 19, 31}) == 3: return 2151 - if len(set_ball & {17, 19, 33}) == 3: return 2152 - if len(set_ball & {17, 19, 39}) == 3: return 2153 - if len(set_ball & {17, 20, 21}) == 3: return 2154 - if len(set_ball & {17, 20, 22}) == 3: return 2155 - if len(set_ball & {17, 20, 23}) == 3: return 2156 - if len(set_ball & {17, 20, 25}) == 3: return 2157 - if len(set_ball & {17, 21, 28}) == 3: return 2158 - if len(set_ball & {17, 21, 35}) == 3: return 2159 - if len(set_ball & {17, 21, 38}) == 3: return 2160 - if len(set_ball & {17, 21, 41}) == 3: return 2161 - if len(set_ball & {17, 21, 42}) == 3: return 2162 - if len(set_ball & {17, 21, 43}) == 3: return 2163 - if len(set_ball & {17, 22, 29}) == 3: return 2164 - if len(set_ball & {17, 22, 32}) == 3: return 2165 - if len(set_ball & {17, 22, 38}) == 3: return 2166 - if len(set_ball & {17, 22, 41}) == 3: return 2167 - if len(set_ball & {17, 22, 42}) == 3: return 2168 - if len(set_ball & {17, 22, 44}) == 3: return 2169 - if len(set_ball & {17, 23, 26}) == 3: return 2170 - if len(set_ball & {17, 23, 31}) == 3: return 2171 - if len(set_ball & {17, 23, 33}) == 3: return 2172 - if len(set_ball & {17, 24, 28}) == 3: return 2173 - if len(set_ball & {17, 24, 32}) == 3: return 2174 - if len(set_ball & {17, 24, 33}) == 3: return 2175 - if len(set_ball & {17, 24, 34}) == 3: return 2176 - if len(set_ball & {17, 24, 38}) == 3: return 2177 - if len(set_ball & {17, 24, 42}) == 3: return 2178 - if len(set_ball & {17, 24, 43}) == 3: return 2179 - if len(set_ball & {17, 25, 29}) == 3: return 2180 - if len(set_ball & {17, 25, 32}) == 3: return 2181 - if len(set_ball & {17, 26, 32}) == 3: return 2182 - if len(set_ball & {17, 26, 33}) == 3: return 2183 - if len(set_ball & {17, 26, 34}) == 3: return 2184 - if len(set_ball & {17, 26, 44}) == 3: return 2185 - if len(set_ball & {17, 27, 30}) == 3: return 2186 - if len(set_ball & {17, 27, 41}) == 3: return 2187 - if len(set_ball & {17, 27, 42}) == 3: return 2188 - if len(set_ball & {17, 28, 31}) == 3: return 2189 - if len(set_ball & {17, 28, 32}) == 3: return 2190 - if len(set_ball & {17, 28, 34}) == 3: return 2191 - if len(set_ball & {17, 28, 38}) == 3: return 2192 - if len(set_ball & {17, 29, 36}) == 3: return 2193 - if len(set_ball & {17, 29, 37}) == 3: return 2194 - if len(set_ball & {17, 29, 41}) == 3: return 2195 - if len(set_ball & {17, 29, 42}) == 3: return 2196 - if len(set_ball & {17, 30, 39}) == 3: return 2197 - if len(set_ball & {17, 31, 35}) == 3: return 2198 - if len(set_ball & {17, 31, 41}) == 3: return 2199 - if len(set_ball & {17, 32, 36}) == 3: return 2200 - if len(set_ball & {17, 32, 38}) == 3: return 2201 - if len(set_ball & {17, 32, 40}) == 3: return 2202 - if len(set_ball & {17, 32, 43}) == 3: return 2203 - if len(set_ball & {17, 33, 37}) == 3: return 2204 - if len(set_ball & {17, 33, 39}) == 3: return 2205 - if len(set_ball & {17, 33, 43}) == 3: return 2206 - if len(set_ball & {17, 34, 40}) == 3: return 2207 - if len(set_ball & {17, 35, 37}) == 3: return 2208 - if len(set_ball & {17, 38, 39}) == 3: return 2209 - if len(set_ball & {17, 38, 40}) == 3: return 2210 - if len(set_ball & {17, 38, 42}) == 3: return 2211 - if len(set_ball & {17, 39, 41}) == 3: return 2212 - if len(set_ball & {17, 40, 41}) == 3: return 2213 - if len(set_ball & {17, 40, 42}) == 3: return 2214 - if len(set_ball & {17, 40, 45}) == 3: return 2215 - if len(set_ball & {18, 19, 20}) == 3: return 2216 - if len(set_ball & {18, 19, 35}) == 3: return 2217 - if len(set_ball & {18, 19, 36}) == 3: return 2218 - if len(set_ball & {18, 19, 37}) == 3: return 2219 - if len(set_ball & {18, 19, 41}) == 3: return 2220 - if len(set_ball & {18, 19, 43}) == 3: return 2221 - if len(set_ball & {18, 20, 29}) == 3: return 2222 - if len(set_ball & {18, 20, 37}) == 3: return 2223 - if len(set_ball & {18, 20, 39}) == 3: return 2224 - if len(set_ball & {18, 20, 41}) == 3: return 2225 - if len(set_ball & {18, 21, 24}) == 3: return 2226 - if len(set_ball & {18, 21, 30}) == 3: return 2227 - if len(set_ball & {18, 21, 45}) == 3: return 2228 - if len(set_ball & {18, 22, 27}) == 3: return 2229 - if len(set_ball & {18, 22, 32}) == 3: return 2230 - if len(set_ball & {18, 22, 33}) == 3: return 2231 - if len(set_ball & {18, 22, 36}) == 3: return 2232 - if len(set_ball & {18, 22, 37}) == 3: return 2233 - if len(set_ball & {18, 22, 40}) == 3: return 2234 - if len(set_ball & {18, 22, 41}) == 3: return 2235 - if len(set_ball & {18, 23, 24}) == 3: return 2236 - if len(set_ball & {18, 23, 31}) == 3: return 2237 - if len(set_ball & {18, 23, 33}) == 3: return 2238 - if len(set_ball & {18, 23, 38}) == 3: return 2239 - if len(set_ball & {18, 23, 42}) == 3: return 2240 - if len(set_ball & {18, 24, 28}) == 3: return 2241 - if len(set_ball & {18, 24, 35}) == 3: return 2242 - if len(set_ball & {18, 24, 43}) == 3: return 2243 - if len(set_ball & {18, 25, 29}) == 3: return 2244 - if len(set_ball & {18, 25, 36}) == 3: return 2245 - if len(set_ball & {18, 25, 40}) == 3: return 2246 - if len(set_ball & {18, 25, 41}) == 3: return 2247 - if len(set_ball & {18, 25, 42}) == 3: return 2248 - if len(set_ball & {18, 25, 43}) == 3: return 2249 - if len(set_ball & {18, 26, 28}) == 3: return 2250 - if len(set_ball & {18, 27, 30}) == 3: return 2251 - if len(set_ball & {18, 27, 34}) == 3: return 2252 - if len(set_ball & {18, 27, 35}) == 3: return 2253 - if len(set_ball & {18, 27, 37}) == 3: return 2254 - if len(set_ball & {18, 27, 38}) == 3: return 2255 - if len(set_ball & {18, 27, 44}) == 3: return 2256 - if len(set_ball & {18, 28, 29}) == 3: return 2257 - if len(set_ball & {18, 28, 33}) == 3: return 2258 - if len(set_ball & {18, 28, 41}) == 3: return 2259 - if len(set_ball & {18, 28, 44}) == 3: return 2260 - if len(set_ball & {18, 29, 40}) == 3: return 2261 - if len(set_ball & {18, 29, 41}) == 3: return 2262 - if len(set_ball & {18, 29, 45}) == 3: return 2263 - if len(set_ball & {18, 30, 33}) == 3: return 2264 - if len(set_ball & {18, 30, 35}) == 3: return 2265 - if len(set_ball & {18, 31, 35}) == 3: return 2266 - if len(set_ball & {18, 32, 42}) == 3: return 2267 - if len(set_ball & {18, 33, 35}) == 3: return 2268 - if len(set_ball & {18, 33, 39}) == 3: return 2269 - if len(set_ball & {18, 33, 41}) == 3: return 2270 - if len(set_ball & {18, 33, 43}) == 3: return 2271 - if len(set_ball & {18, 34, 36}) == 3: return 2272 - if len(set_ball & {18, 34, 37}) == 3: return 2273 - if len(set_ball & {18, 35, 36}) == 3: return 2274 - if len(set_ball & {18, 35, 41}) == 3: return 2275 - if len(set_ball & {18, 36, 38}) == 3: return 2276 - if len(set_ball & {18, 36, 45}) == 3: return 2277 - if len(set_ball & {18, 37, 41}) == 3: return 2278 - if len(set_ball & {18, 38, 39}) == 3: return 2279 - if len(set_ball & {18, 38, 40}) == 3: return 2280 - if len(set_ball & {18, 38, 42}) == 3: return 2281 - if len(set_ball & {18, 40, 44}) == 3: return 2282 - if len(set_ball & {18, 41, 44}) == 3: return 2283 - if len(set_ball & {19, 20, 22}) == 3: return 2284 - if len(set_ball & {19, 20, 27}) == 3: return 2285 - if len(set_ball & {19, 20, 29}) == 3: return 2286 - if len(set_ball & {19, 20, 31}) == 3: return 2287 - if len(set_ball & {19, 20, 36}) == 3: return 2288 - if len(set_ball & {19, 20, 37}) == 3: return 2289 - if len(set_ball & {19, 21, 28}) == 3: return 2290 - if len(set_ball & {19, 21, 38}) == 3: return 2291 - if len(set_ball & {19, 22, 26}) == 3: return 2292 - if len(set_ball & {19, 22, 27}) == 3: return 2293 - if len(set_ball & {19, 22, 33}) == 3: return 2294 - if len(set_ball & {19, 22, 38}) == 3: return 2295 - if len(set_ball & {19, 22, 41}) == 3: return 2296 - if len(set_ball & {19, 22, 45}) == 3: return 2297 - if len(set_ball & {19, 23, 26}) == 3: return 2298 - if len(set_ball & {19, 23, 40}) == 3: return 2299 - if len(set_ball & {19, 24, 25}) == 3: return 2300 - if len(set_ball & {19, 24, 28}) == 3: return 2301 - if len(set_ball & {19, 24, 29}) == 3: return 2302 - if len(set_ball & {19, 24, 35}) == 3: return 2303 - if len(set_ball & {19, 24, 38}) == 3: return 2304 - if len(set_ball & {19, 25, 30}) == 3: return 2305 - if len(set_ball & {19, 25, 35}) == 3: return 2306 - if len(set_ball & {19, 25, 40}) == 3: return 2307 - if len(set_ball & {19, 26, 29}) == 3: return 2308 - if len(set_ball & {19, 26, 32}) == 3: return 2309 - if len(set_ball & {19, 26, 38}) == 3: return 2310 - if len(set_ball & {19, 27, 32}) == 3: return 2311 - if len(set_ball & {19, 27, 33}) == 3: return 2312 - if len(set_ball & {19, 27, 36}) == 3: return 2313 - if len(set_ball & {19, 27, 37}) == 3: return 2314 - if len(set_ball & {19, 27, 38}) == 3: return 2315 - if len(set_ball & {19, 27, 39}) == 3: return 2316 - if len(set_ball & {19, 28, 29}) == 3: return 2317 - if len(set_ball & {19, 28, 35}) == 3: return 2318 - if len(set_ball & {19, 28, 39}) == 3: return 2319 - if len(set_ball & {19, 28, 40}) == 3: return 2320 - if len(set_ball & {19, 29, 30}) == 3: return 2321 - if len(set_ball & {19, 29, 34}) == 3: return 2322 - if len(set_ball & {19, 29, 37}) == 3: return 2323 - if len(set_ball & {19, 29, 38}) == 3: return 2324 - if len(set_ball & {19, 29, 41}) == 3: return 2325 - if len(set_ball & {19, 29, 44}) == 3: return 2326 - if len(set_ball & {19, 30, 32}) == 3: return 2327 - if len(set_ball & {19, 31, 32}) == 3: return 2328 - if len(set_ball & {19, 31, 37}) == 3: return 2329 - if len(set_ball & {19, 31, 41}) == 3: return 2330 - if len(set_ball & {19, 32, 39}) == 3: return 2331 - if len(set_ball & {19, 32, 44}) == 3: return 2332 - if len(set_ball & {19, 33, 37}) == 3: return 2333 - if len(set_ball & {19, 35, 36}) == 3: return 2334 - if len(set_ball & {19, 35, 37}) == 3: return 2335 - if len(set_ball & {19, 35, 44}) == 3: return 2336 - if len(set_ball & {19, 36, 37}) == 3: return 2337 - if len(set_ball & {19, 36, 40}) == 3: return 2338 - if len(set_ball & {19, 36, 41}) == 3: return 2339 - if len(set_ball & {19, 38, 39}) == 3: return 2340 - if len(set_ball & {19, 38, 45}) == 3: return 2341 - if len(set_ball & {19, 39, 42}) == 3: return 2342 - if len(set_ball & {19, 39, 45}) == 3: return 2343 - if len(set_ball & {20, 21, 23}) == 3: return 2344 - if len(set_ball & {20, 21, 27}) == 3: return 2345 - if len(set_ball & {20, 21, 28}) == 3: return 2346 - if len(set_ball & {20, 21, 31}) == 3: return 2347 - if len(set_ball & {20, 21, 35}) == 3: return 2348 - if len(set_ball & {20, 21, 36}) == 3: return 2349 - if len(set_ball & {20, 21, 38}) == 3: return 2350 - if len(set_ball & {20, 21, 42}) == 3: return 2351 - if len(set_ball & {20, 21, 43}) == 3: return 2352 - if len(set_ball & {20, 22, 31}) == 3: return 2353 - if len(set_ball & {20, 22, 32}) == 3: return 2354 - if len(set_ball & {20, 22, 35}) == 3: return 2355 - if len(set_ball & {20, 22, 39}) == 3: return 2356 - if len(set_ball & {20, 22, 45}) == 3: return 2357 - if len(set_ball & {20, 23, 25}) == 3: return 2358 - if len(set_ball & {20, 23, 33}) == 3: return 2359 - if len(set_ball & {20, 23, 41}) == 3: return 2360 - if len(set_ball & {20, 24, 26}) == 3: return 2361 - if len(set_ball & {20, 24, 29}) == 3: return 2362 - if len(set_ball & {20, 24, 35}) == 3: return 2363 - if len(set_ball & {20, 25, 26}) == 3: return 2364 - if len(set_ball & {20, 25, 27}) == 3: return 2365 - if len(set_ball & {20, 25, 30}) == 3: return 2366 - if len(set_ball & {20, 25, 35}) == 3: return 2367 - if len(set_ball & {20, 25, 42}) == 3: return 2368 - if len(set_ball & {20, 25, 44}) == 3: return 2369 - if len(set_ball & {20, 26, 34}) == 3: return 2370 - if len(set_ball & {20, 27, 29}) == 3: return 2371 - if len(set_ball & {20, 27, 30}) == 3: return 2372 - if len(set_ball & {20, 27, 34}) == 3: return 2373 - if len(set_ball & {20, 27, 45}) == 3: return 2374 - if len(set_ball & {20, 28, 29}) == 3: return 2375 - if len(set_ball & {20, 28, 31}) == 3: return 2376 - if len(set_ball & {20, 28, 33}) == 3: return 2377 - if len(set_ball & {20, 28, 34}) == 3: return 2378 - if len(set_ball & {20, 28, 42}) == 3: return 2379 - if len(set_ball & {20, 28, 45}) == 3: return 2380 - if len(set_ball & {20, 29, 30}) == 3: return 2381 - if len(set_ball & {20, 29, 32}) == 3: return 2382 - if len(set_ball & {20, 29, 37}) == 3: return 2383 - if len(set_ball & {20, 29, 40}) == 3: return 2384 - if len(set_ball & {20, 30, 32}) == 3: return 2385 - if len(set_ball & {20, 30, 42}) == 3: return 2386 - if len(set_ball & {20, 30, 43}) == 3: return 2387 - if len(set_ball & {20, 31, 39}) == 3: return 2388 - if len(set_ball & {20, 34, 36}) == 3: return 2389 - if len(set_ball & {20, 34, 38}) == 3: return 2390 - if len(set_ball & {20, 34, 39}) == 3: return 2391 - if len(set_ball & {20, 37, 41}) == 3: return 2392 - if len(set_ball & {20, 37, 44}) == 3: return 2393 - if len(set_ball & {20, 38, 39}) == 3: return 2394 - if len(set_ball & {20, 39, 40}) == 3: return 2395 - if len(set_ball & {20, 39, 43}) == 3: return 2396 - if len(set_ball & {20, 43, 45}) == 3: return 2397 - if len(set_ball & {21, 22, 29}) == 3: return 2398 - if len(set_ball & {21, 22, 40}) == 3: return 2399 - if len(set_ball & {21, 22, 43}) == 3: return 2400 - if len(set_ball & {21, 22, 45}) == 3: return 2401 - if len(set_ball & {21, 23, 28}) == 3: return 2402 - if len(set_ball & {21, 23, 29}) == 3: return 2403 - if len(set_ball & {21, 23, 35}) == 3: return 2404 - if len(set_ball & {21, 23, 36}) == 3: return 2405 - if len(set_ball & {21, 23, 37}) == 3: return 2406 - if len(set_ball & {21, 23, 38}) == 3: return 2407 - if len(set_ball & {21, 23, 41}) == 3: return 2408 - if len(set_ball & {21, 23, 42}) == 3: return 2409 - if len(set_ball & {21, 24, 25}) == 3: return 2410 - if len(set_ball & {21, 24, 28}) == 3: return 2411 - if len(set_ball & {21, 24, 32}) == 3: return 2412 - if len(set_ball & {21, 24, 34}) == 3: return 2413 - if len(set_ball & {21, 24, 35}) == 3: return 2414 - if len(set_ball & {21, 24, 38}) == 3: return 2415 - if len(set_ball & {21, 25, 28}) == 3: return 2416 - if len(set_ball & {21, 25, 31}) == 3: return 2417 - if len(set_ball & {21, 25, 43}) == 3: return 2418 - if len(set_ball & {21, 26, 28}) == 3: return 2419 - if len(set_ball & {21, 27, 28}) == 3: return 2420 - if len(set_ball & {21, 27, 30}) == 3: return 2421 - if len(set_ball & {21, 28, 29}) == 3: return 2422 - if len(set_ball & {21, 28, 30}) == 3: return 2423 - if len(set_ball & {21, 28, 32}) == 3: return 2424 - if len(set_ball & {21, 28, 33}) == 3: return 2425 - if len(set_ball & {21, 28, 41}) == 3: return 2426 - if len(set_ball & {21, 28, 44}) == 3: return 2427 - if len(set_ball & {21, 29, 30}) == 3: return 2428 - if len(set_ball & {21, 29, 36}) == 3: return 2429 - if len(set_ball & {21, 29, 41}) == 3: return 2430 - if len(set_ball & {21, 30, 31}) == 3: return 2431 - if len(set_ball & {21, 30, 41}) == 3: return 2432 - if len(set_ball & {21, 31, 34}) == 3: return 2433 - if len(set_ball & {21, 31, 42}) == 3: return 2434 - if len(set_ball & {21, 33, 43}) == 3: return 2435 - if len(set_ball & {21, 33, 44}) == 3: return 2436 - if len(set_ball & {21, 33, 45}) == 3: return 2437 - if len(set_ball & {21, 34, 39}) == 3: return 2438 - if len(set_ball & {21, 34, 40}) == 3: return 2439 - if len(set_ball & {21, 36, 42}) == 3: return 2440 - if len(set_ball & {21, 37, 39}) == 3: return 2441 - if len(set_ball & {21, 39, 42}) == 3: return 2442 - if len(set_ball & {21, 40, 43}) == 3: return 2443 - if len(set_ball & {21, 41, 45}) == 3: return 2444 - if len(set_ball & {21, 42, 43}) == 3: return 2445 - if len(set_ball & {21, 42, 44}) == 3: return 2446 - if len(set_ball & {21, 43, 45}) == 3: return 2447 - if len(set_ball & {22, 23, 26}) == 3: return 2448 - if len(set_ball & {22, 23, 27}) == 3: return 2449 - if len(set_ball & {22, 23, 28}) == 3: return 2450 - if len(set_ball & {22, 23, 31}) == 3: return 2451 - if len(set_ball & {22, 23, 33}) == 3: return 2452 - if len(set_ball & {22, 23, 39}) == 3: return 2453 - if len(set_ball & {22, 23, 40}) == 3: return 2454 - if len(set_ball & {22, 23, 41}) == 3: return 2455 - if len(set_ball & {22, 23, 45}) == 3: return 2456 - if len(set_ball & {22, 24, 25}) == 3: return 2457 - if len(set_ball & {22, 24, 29}) == 3: return 2458 - if len(set_ball & {22, 24, 39}) == 3: return 2459 - if len(set_ball & {22, 24, 43}) == 3: return 2460 - if len(set_ball & {22, 24, 45}) == 3: return 2461 - if len(set_ball & {22, 25, 26}) == 3: return 2462 - if len(set_ball & {22, 25, 27}) == 3: return 2463 - if len(set_ball & {22, 25, 35}) == 3: return 2464 - if len(set_ball & {22, 25, 39}) == 3: return 2465 - if len(set_ball & {22, 26, 28}) == 3: return 2466 - if len(set_ball & {22, 26, 32}) == 3: return 2467 - if len(set_ball & {22, 27, 29}) == 3: return 2468 - if len(set_ball & {22, 27, 32}) == 3: return 2469 - if len(set_ball & {22, 27, 41}) == 3: return 2470 - if len(set_ball & {22, 27, 45}) == 3: return 2471 - if len(set_ball & {22, 28, 29}) == 3: return 2472 - if len(set_ball & {22, 28, 30}) == 3: return 2473 - if len(set_ball & {22, 29, 38}) == 3: return 2474 - if len(set_ball & {22, 29, 41}) == 3: return 2475 - if len(set_ball & {22, 29, 42}) == 3: return 2476 - if len(set_ball & {22, 30, 31}) == 3: return 2477 - if len(set_ball & {22, 30, 32}) == 3: return 2478 - if len(set_ball & {22, 30, 33}) == 3: return 2479 - if len(set_ball & {22, 31, 33}) == 3: return 2480 - if len(set_ball & {22, 32, 37}) == 3: return 2481 - if len(set_ball & {22, 32, 41}) == 3: return 2482 - if len(set_ball & {22, 32, 43}) == 3: return 2483 - if len(set_ball & {22, 33, 38}) == 3: return 2484 - if len(set_ball & {22, 33, 39}) == 3: return 2485 - if len(set_ball & {22, 33, 43}) == 3: return 2486 - if len(set_ball & {22, 33, 44}) == 3: return 2487 - if len(set_ball & {22, 34, 43}) == 3: return 2488 - if len(set_ball & {22, 35, 44}) == 3: return 2489 - if len(set_ball & {22, 35, 45}) == 3: return 2490 - if len(set_ball & {22, 36, 43}) == 3: return 2491 - if len(set_ball & {22, 39, 40}) == 3: return 2492 - if len(set_ball & {22, 39, 42}) == 3: return 2493 - if len(set_ball & {22, 39, 43}) == 3: return 2494 - if len(set_ball & {22, 40, 41}) == 3: return 2495 - if len(set_ball & {22, 40, 44}) == 3: return 2496 - if len(set_ball & {22, 41, 44}) == 3: return 2497 - if len(set_ball & {22, 41, 45}) == 3: return 2498 - if len(set_ball & {22, 42, 44}) == 3: return 2499 - if len(set_ball & {23, 24, 25}) == 3: return 2500 - if len(set_ball & {23, 24, 26}) == 3: return 2501 - if len(set_ball & {23, 24, 33}) == 3: return 2502 - if len(set_ball & {23, 24, 34}) == 3: return 2503 - if len(set_ball & {23, 24, 40}) == 3: return 2504 - if len(set_ball & {23, 24, 41}) == 3: return 2505 - if len(set_ball & {23, 24, 42}) == 3: return 2506 - if len(set_ball & {23, 25, 26}) == 3: return 2507 - if len(set_ball & {23, 25, 31}) == 3: return 2508 - if len(set_ball & {23, 25, 34}) == 3: return 2509 - if len(set_ball & {23, 26, 32}) == 3: return 2510 - if len(set_ball & {23, 26, 34}) == 3: return 2511 - if len(set_ball & {23, 26, 37}) == 3: return 2512 - if len(set_ball & {23, 26, 38}) == 3: return 2513 - if len(set_ball & {23, 26, 41}) == 3: return 2514 - if len(set_ball & {23, 27, 30}) == 3: return 2515 - if len(set_ball & {23, 27, 32}) == 3: return 2516 - if len(set_ball & {23, 27, 37}) == 3: return 2517 - if len(set_ball & {23, 27, 39}) == 3: return 2518 - if len(set_ball & {23, 28, 31}) == 3: return 2519 - if len(set_ball & {23, 28, 32}) == 3: return 2520 - if len(set_ball & {23, 28, 41}) == 3: return 2521 - if len(set_ball & {23, 29, 32}) == 3: return 2522 - if len(set_ball & {23, 30, 31}) == 3: return 2523 - if len(set_ball & {23, 30, 33}) == 3: return 2524 - if len(set_ball & {23, 30, 39}) == 3: return 2525 - if len(set_ball & {23, 30, 42}) == 3: return 2526 - if len(set_ball & {23, 31, 32}) == 3: return 2527 - if len(set_ball & {23, 31, 41}) == 3: return 2528 - if len(set_ball & {23, 31, 42}) == 3: return 2529 - if len(set_ball & {23, 32, 33}) == 3: return 2530 - if len(set_ball & {23, 32, 35}) == 3: return 2531 - if len(set_ball & {23, 32, 37}) == 3: return 2532 - if len(set_ball & {23, 32, 41}) == 3: return 2533 - if len(set_ball & {23, 32, 42}) == 3: return 2534 - if len(set_ball & {23, 33, 38}) == 3: return 2535 - if len(set_ball & {23, 33, 45}) == 3: return 2536 - if len(set_ball & {23, 34, 37}) == 3: return 2537 - if len(set_ball & {23, 35, 39}) == 3: return 2538 - if len(set_ball & {23, 35, 41}) == 3: return 2539 - if len(set_ball & {23, 35, 42}) == 3: return 2540 - if len(set_ball & {23, 36, 42}) == 3: return 2541 - if len(set_ball & {23, 37, 41}) == 3: return 2542 - if len(set_ball & {23, 37, 44}) == 3: return 2543 - if len(set_ball & {23, 38, 41}) == 3: return 2544 - if len(set_ball & {23, 38, 44}) == 3: return 2545 - if len(set_ball & {23, 39, 40}) == 3: return 2546 - if len(set_ball & {23, 39, 41}) == 3: return 2547 - if len(set_ball & {23, 40, 42}) == 3: return 2548 - if len(set_ball & {23, 40, 43}) == 3: return 2549 - if len(set_ball & {23, 41, 42}) == 3: return 2550 - if len(set_ball & {24, 25, 28}) == 3: return 2551 - if len(set_ball & {24, 25, 41}) == 3: return 2552 - if len(set_ball & {24, 25, 42}) == 3: return 2553 - if len(set_ball & {24, 25, 45}) == 3: return 2554 - if len(set_ball & {24, 26, 27}) == 3: return 2555 - if len(set_ball & {24, 26, 28}) == 3: return 2556 - if len(set_ball & {24, 26, 31}) == 3: return 2557 - if len(set_ball & {24, 26, 32}) == 3: return 2558 - if len(set_ball & {24, 26, 33}) == 3: return 2559 - if len(set_ball & {24, 26, 43}) == 3: return 2560 - if len(set_ball & {24, 26, 44}) == 3: return 2561 - if len(set_ball & {24, 27, 33}) == 3: return 2562 - if len(set_ball & {24, 27, 38}) == 3: return 2563 - if len(set_ball & {24, 27, 40}) == 3: return 2564 - if len(set_ball & {24, 28, 29}) == 3: return 2565 - if len(set_ball & {24, 28, 31}) == 3: return 2566 - if len(set_ball & {24, 28, 33}) == 3: return 2567 - if len(set_ball & {24, 28, 34}) == 3: return 2568 - if len(set_ball & {24, 28, 41}) == 3: return 2569 - if len(set_ball & {24, 28, 42}) == 3: return 2570 - if len(set_ball & {24, 28, 43}) == 3: return 2571 - if len(set_ball & {24, 28, 44}) == 3: return 2572 - if len(set_ball & {24, 28, 45}) == 3: return 2573 - if len(set_ball & {24, 29, 32}) == 3: return 2574 - if len(set_ball & {24, 29, 36}) == 3: return 2575 - if len(set_ball & {24, 29, 39}) == 3: return 2576 - if len(set_ball & {24, 30, 37}) == 3: return 2577 - if len(set_ball & {24, 30, 40}) == 3: return 2578 - if len(set_ball & {24, 30, 42}) == 3: return 2579 - if len(set_ball & {24, 30, 43}) == 3: return 2580 - if len(set_ball & {24, 30, 44}) == 3: return 2581 - if len(set_ball & {24, 31, 35}) == 3: return 2582 - if len(set_ball & {24, 31, 37}) == 3: return 2583 - if len(set_ball & {24, 31, 38}) == 3: return 2584 - if len(set_ball & {24, 31, 41}) == 3: return 2585 - if len(set_ball & {24, 31, 43}) == 3: return 2586 - if len(set_ball & {24, 31, 45}) == 3: return 2587 - if len(set_ball & {24, 32, 43}) == 3: return 2588 - if len(set_ball & {24, 33, 43}) == 3: return 2589 - if len(set_ball & {24, 34, 37}) == 3: return 2590 - if len(set_ball & {24, 34, 43}) == 3: return 2591 - if len(set_ball & {24, 35, 39}) == 3: return 2592 - if len(set_ball & {24, 35, 41}) == 3: return 2593 - if len(set_ball & {24, 35, 42}) == 3: return 2594 - if len(set_ball & {24, 36, 42}) == 3: return 2595 - if len(set_ball & {24, 36, 43}) == 3: return 2596 - if len(set_ball & {24, 37, 39}) == 3: return 2597 - if len(set_ball & {24, 37, 42}) == 3: return 2598 - if len(set_ball & {24, 37, 43}) == 3: return 2599 - if len(set_ball & {24, 38, 41}) == 3: return 2600 - if len(set_ball & {24, 38, 43}) == 3: return 2601 - if len(set_ball & {24, 39, 43}) == 3: return 2602 - if len(set_ball & {24, 40, 45}) == 3: return 2603 - if len(set_ball & {24, 42, 43}) == 3: return 2604 - if len(set_ball & {25, 26, 28}) == 3: return 2605 - if len(set_ball & {25, 26, 32}) == 3: return 2606 - if len(set_ball & {25, 26, 35}) == 3: return 2607 - if len(set_ball & {25, 26, 39}) == 3: return 2608 - if len(set_ball & {25, 26, 41}) == 3: return 2609 - if len(set_ball & {25, 26, 42}) == 3: return 2610 - if len(set_ball & {25, 27, 28}) == 3: return 2611 - if len(set_ball & {25, 27, 30}) == 3: return 2612 - if len(set_ball & {25, 27, 33}) == 3: return 2613 - if len(set_ball & {25, 27, 42}) == 3: return 2614 - if len(set_ball & {25, 28, 31}) == 3: return 2615 - if len(set_ball & {25, 28, 34}) == 3: return 2616 - if len(set_ball & {25, 28, 35}) == 3: return 2617 - if len(set_ball & {25, 28, 40}) == 3: return 2618 - if len(set_ball & {25, 28, 41}) == 3: return 2619 - if len(set_ball & {25, 29, 39}) == 3: return 2620 - if len(set_ball & {25, 29, 41}) == 3: return 2621 - if len(set_ball & {25, 30, 34}) == 3: return 2622 - if len(set_ball & {25, 30, 35}) == 3: return 2623 - if len(set_ball & {25, 30, 37}) == 3: return 2624 - if len(set_ball & {25, 30, 38}) == 3: return 2625 - if len(set_ball & {25, 30, 39}) == 3: return 2626 - if len(set_ball & {25, 31, 35}) == 3: return 2627 - if len(set_ball & {25, 31, 41}) == 3: return 2628 - if len(set_ball & {25, 31, 42}) == 3: return 2629 - if len(set_ball & {25, 32, 34}) == 3: return 2630 - if len(set_ball & {25, 32, 35}) == 3: return 2631 - if len(set_ball & {25, 32, 38}) == 3: return 2632 - if len(set_ball & {25, 32, 39}) == 3: return 2633 - if len(set_ball & {25, 32, 41}) == 3: return 2634 - if len(set_ball & {25, 33, 37}) == 3: return 2635 - if len(set_ball & {25, 33, 42}) == 3: return 2636 - if len(set_ball & {25, 34, 42}) == 3: return 2637 - if len(set_ball & {25, 34, 43}) == 3: return 2638 - if len(set_ball & {25, 34, 45}) == 3: return 2639 - if len(set_ball & {25, 36, 41}) == 3: return 2640 - if len(set_ball & {25, 37, 40}) == 3: return 2641 - if len(set_ball & {25, 38, 43}) == 3: return 2642 - if len(set_ball & {25, 39, 41}) == 3: return 2643 - if len(set_ball & {25, 40, 41}) == 3: return 2644 - if len(set_ball & {25, 41, 42}) == 3: return 2645 - if len(set_ball & {25, 41, 43}) == 3: return 2646 - if len(set_ball & {25, 42, 44}) == 3: return 2647 - if len(set_ball & {25, 43, 45}) == 3: return 2648 - if len(set_ball & {26, 28, 38}) == 3: return 2649 - if len(set_ball & {26, 28, 39}) == 3: return 2650 - if len(set_ball & {26, 29, 32}) == 3: return 2651 - if len(set_ball & {26, 29, 35}) == 3: return 2652 - if len(set_ball & {26, 30, 37}) == 3: return 2653 - if len(set_ball & {26, 30, 44}) == 3: return 2654 - if len(set_ball & {26, 32, 37}) == 3: return 2655 - if len(set_ball & {26, 32, 41}) == 3: return 2656 - if len(set_ball & {26, 32, 43}) == 3: return 2657 - if len(set_ball & {26, 32, 44}) == 3: return 2658 - if len(set_ball & {26, 32, 45}) == 3: return 2659 - if len(set_ball & {26, 34, 35}) == 3: return 2660 - if len(set_ball & {26, 34, 37}) == 3: return 2661 - if len(set_ball & {26, 34, 39}) == 3: return 2662 - if len(set_ball & {26, 35, 36}) == 3: return 2663 - if len(set_ball & {26, 35, 41}) == 3: return 2664 - if len(set_ball & {26, 35, 44}) == 3: return 2665 - if len(set_ball & {26, 35, 45}) == 3: return 2666 - if len(set_ball & {26, 36, 38}) == 3: return 2667 - if len(set_ball & {26, 38, 42}) == 3: return 2668 - if len(set_ball & {26, 38, 44}) == 3: return 2669 - if len(set_ball & {26, 39, 43}) == 3: return 2670 - if len(set_ball & {26, 40, 44}) == 3: return 2671 - if len(set_ball & {26, 40, 45}) == 3: return 2672 - if len(set_ball & {26, 41, 43}) == 3: return 2673 - if len(set_ball & {26, 41, 44}) == 3: return 2674 - if len(set_ball & {27, 28, 31}) == 3: return 2675 - if len(set_ball & {27, 28, 33}) == 3: return 2676 - if len(set_ball & {27, 28, 34}) == 3: return 2677 - if len(set_ball & {27, 29, 32}) == 3: return 2678 - if len(set_ball & {27, 29, 39}) == 3: return 2679 - if len(set_ball & {27, 31, 33}) == 3: return 2680 - if len(set_ball & {27, 32, 33}) == 3: return 2681 - if len(set_ball & {27, 32, 35}) == 3: return 2682 - if len(set_ball & {27, 32, 36}) == 3: return 2683 - if len(set_ball & {27, 32, 39}) == 3: return 2684 - if len(set_ball & {27, 32, 41}) == 3: return 2685 - if len(set_ball & {27, 32, 43}) == 3: return 2686 - if len(set_ball & {27, 32, 44}) == 3: return 2687 - if len(set_ball & {27, 33, 34}) == 3: return 2688 - if len(set_ball & {27, 33, 42}) == 3: return 2689 - if len(set_ball & {27, 34, 37}) == 3: return 2690 - if len(set_ball & {27, 36, 42}) == 3: return 2691 - if len(set_ball & {27, 37, 38}) == 3: return 2692 - if len(set_ball & {27, 39, 41}) == 3: return 2693 - if len(set_ball & {27, 39, 42}) == 3: return 2694 - if len(set_ball & {27, 42, 44}) == 3: return 2695 - if len(set_ball & {28, 29, 31}) == 3: return 2696 - if len(set_ball & {28, 29, 32}) == 3: return 2697 - if len(set_ball & {28, 29, 35}) == 3: return 2698 - if len(set_ball & {28, 29, 37}) == 3: return 2699 - if len(set_ball & {28, 29, 38}) == 3: return 2700 - if len(set_ball & {28, 29, 41}) == 3: return 2701 - if len(set_ball & {28, 29, 42}) == 3: return 2702 - if len(set_ball & {28, 29, 45}) == 3: return 2703 - if len(set_ball & {28, 30, 31}) == 3: return 2704 - if len(set_ball & {28, 30, 37}) == 3: return 2705 - if len(set_ball & {28, 30, 40}) == 3: return 2706 - if len(set_ball & {28, 31, 35}) == 3: return 2707 - if len(set_ball & {28, 31, 37}) == 3: return 2708 - if len(set_ball & {28, 31, 40}) == 3: return 2709 - if len(set_ball & {28, 31, 42}) == 3: return 2710 - if len(set_ball & {28, 32, 33}) == 3: return 2711 - if len(set_ball & {28, 32, 35}) == 3: return 2712 - if len(set_ball & {28, 32, 39}) == 3: return 2713 - if len(set_ball & {28, 32, 41}) == 3: return 2714 - if len(set_ball & {28, 32, 42}) == 3: return 2715 - if len(set_ball & {28, 32, 43}) == 3: return 2716 - if len(set_ball & {28, 32, 44}) == 3: return 2717 - if len(set_ball & {28, 33, 34}) == 3: return 2718 - if len(set_ball & {28, 33, 43}) == 3: return 2719 - if len(set_ball & {28, 34, 37}) == 3: return 2720 - if len(set_ball & {28, 35, 36}) == 3: return 2721 - if len(set_ball & {28, 35, 45}) == 3: return 2722 - if len(set_ball & {28, 36, 37}) == 3: return 2723 - if len(set_ball & {28, 36, 38}) == 3: return 2724 - if len(set_ball & {28, 36, 43}) == 3: return 2725 - if len(set_ball & {28, 37, 41}) == 3: return 2726 - if len(set_ball & {28, 38, 41}) == 3: return 2727 - if len(set_ball & {28, 39, 44}) == 3: return 2728 - if len(set_ball & {28, 40, 42}) == 3: return 2729 - if len(set_ball & {28, 40, 44}) == 3: return 2730 - if len(set_ball & {28, 40, 45}) == 3: return 2731 - if len(set_ball & {28, 41, 45}) == 3: return 2732 - if len(set_ball & {28, 42, 44}) == 3: return 2733 - if len(set_ball & {28, 44, 45}) == 3: return 2734 - if len(set_ball & {29, 30, 32}) == 3: return 2735 - if len(set_ball & {29, 30, 36}) == 3: return 2736 - if len(set_ball & {29, 30, 40}) == 3: return 2737 - if len(set_ball & {29, 31, 41}) == 3: return 2738 - if len(set_ball & {29, 32, 34}) == 3: return 2739 - if len(set_ball & {29, 32, 35}) == 3: return 2740 - if len(set_ball & {29, 32, 41}) == 3: return 2741 - if len(set_ball & {29, 33, 36}) == 3: return 2742 - if len(set_ball & {29, 34, 41}) == 3: return 2743 - if len(set_ball & {29, 34, 42}) == 3: return 2744 - if len(set_ball & {29, 34, 43}) == 3: return 2745 - if len(set_ball & {29, 35, 39}) == 3: return 2746 - if len(set_ball & {29, 35, 41}) == 3: return 2747 - if len(set_ball & {29, 35, 45}) == 3: return 2748 - if len(set_ball & {29, 36, 42}) == 3: return 2749 - if len(set_ball & {29, 36, 44}) == 3: return 2750 - if len(set_ball & {29, 37, 42}) == 3: return 2751 - if len(set_ball & {29, 37, 44}) == 3: return 2752 - if len(set_ball & {29, 41, 43}) == 3: return 2753 - if len(set_ball & {29, 42, 44}) == 3: return 2754 - if len(set_ball & {29, 44, 45}) == 3: return 2755 - if len(set_ball & {30, 31, 32}) == 3: return 2756 - if len(set_ball & {30, 31, 35}) == 3: return 2757 - if len(set_ball & {30, 31, 36}) == 3: return 2758 - if len(set_ball & {30, 32, 36}) == 3: return 2759 - if len(set_ball & {30, 32, 44}) == 3: return 2760 - if len(set_ball & {30, 33, 40}) == 3: return 2761 - if len(set_ball & {30, 35, 45}) == 3: return 2762 - if len(set_ball & {30, 36, 40}) == 3: return 2763 - if len(set_ball & {30, 37, 42}) == 3: return 2764 - if len(set_ball & {30, 38, 42}) == 3: return 2765 - if len(set_ball & {30, 40, 45}) == 3: return 2766 - if len(set_ball & {30, 43, 44}) == 3: return 2767 - if len(set_ball & {30, 44, 45}) == 3: return 2768 - if len(set_ball & {31, 32, 35}) == 3: return 2769 - if len(set_ball & {31, 32, 42}) == 3: return 2770 - if len(set_ball & {31, 32, 44}) == 3: return 2771 - if len(set_ball & {31, 33, 35}) == 3: return 2772 - if len(set_ball & {31, 33, 39}) == 3: return 2773 - if len(set_ball & {31, 33, 43}) == 3: return 2774 - if len(set_ball & {31, 34, 41}) == 3: return 2775 - if len(set_ball & {31, 35, 36}) == 3: return 2776 - if len(set_ball & {31, 35, 41}) == 3: return 2777 - if len(set_ball & {31, 35, 42}) == 3: return 2778 - if len(set_ball & {31, 36, 39}) == 3: return 2779 - if len(set_ball & {31, 36, 41}) == 3: return 2780 - if len(set_ball & {31, 36, 42}) == 3: return 2781 - if len(set_ball & {31, 37, 39}) == 3: return 2782 - if len(set_ball & {31, 37, 45}) == 3: return 2783 - if len(set_ball & {31, 38, 42}) == 3: return 2784 - if len(set_ball & {31, 39, 42}) == 3: return 2785 - if len(set_ball & {31, 42, 44}) == 3: return 2786 - if len(set_ball & {31, 42, 45}) == 3: return 2787 - if len(set_ball & {32, 34, 35}) == 3: return 2788 - if len(set_ball & {32, 34, 37}) == 3: return 2789 - if len(set_ball & {32, 35, 38}) == 3: return 2790 - if len(set_ball & {32, 35, 39}) == 3: return 2791 - if len(set_ball & {32, 35, 41}) == 3: return 2792 - if len(set_ball & {32, 35, 42}) == 3: return 2793 - if len(set_ball & {32, 35, 43}) == 3: return 2794 - if len(set_ball & {32, 36, 37}) == 3: return 2795 - if len(set_ball & {32, 36, 41}) == 3: return 2796 - if len(set_ball & {32, 36, 44}) == 3: return 2797 - if len(set_ball & {32, 37, 38}) == 3: return 2798 - if len(set_ball & {32, 37, 42}) == 3: return 2799 - if len(set_ball & {32, 38, 41}) == 3: return 2800 - if len(set_ball & {32, 38, 45}) == 3: return 2801 - if len(set_ball & {32, 39, 44}) == 3: return 2802 - if len(set_ball & {32, 40, 44}) == 3: return 2803 - if len(set_ball & {32, 41, 44}) == 3: return 2804 - if len(set_ball & {32, 42, 43}) == 3: return 2805 - if len(set_ball & {32, 42, 45}) == 3: return 2806 - if len(set_ball & {32, 43, 45}) == 3: return 2807 - if len(set_ball & {33, 34, 41}) == 3: return 2808 - if len(set_ball & {33, 34, 45}) == 3: return 2809 - if len(set_ball & {33, 35, 42}) == 3: return 2810 - if len(set_ball & {33, 37, 39}) == 3: return 2811 - if len(set_ball & {33, 38, 41}) == 3: return 2812 - if len(set_ball & {33, 38, 43}) == 3: return 2813 - if len(set_ball & {33, 38, 44}) == 3: return 2814 - if len(set_ball & {33, 39, 42}) == 3: return 2815 - if len(set_ball & {33, 39, 43}) == 3: return 2816 - if len(set_ball & {33, 39, 45}) == 3: return 2817 - if len(set_ball & {33, 41, 43}) == 3: return 2818 - if len(set_ball & {33, 43, 44}) == 3: return 2819 - if len(set_ball & {34, 35, 38}) == 3: return 2820 - if len(set_ball & {34, 36, 38}) == 3: return 2821 - if len(set_ball & {34, 36, 39}) == 3: return 2822 - if len(set_ball & {34, 36, 43}) == 3: return 2823 - if len(set_ball & {34, 37, 41}) == 3: return 2824 - if len(set_ball & {34, 37, 42}) == 3: return 2825 - if len(set_ball & {34, 37, 43}) == 3: return 2826 - if len(set_ball & {34, 37, 44}) == 3: return 2827 - if len(set_ball & {34, 38, 44}) == 3: return 2828 - if len(set_ball & {35, 36, 38}) == 3: return 2829 - if len(set_ball & {35, 37, 38}) == 3: return 2830 - if len(set_ball & {35, 38, 42}) == 3: return 2831 - if len(set_ball & {35, 39, 40}) == 3: return 2832 - if len(set_ball & {35, 40, 41}) == 3: return 2833 - if len(set_ball & {35, 40, 43}) == 3: return 2834 - if len(set_ball & {36, 37, 39}) == 3: return 2835 - if len(set_ball & {36, 38, 42}) == 3: return 2836 - if len(set_ball & {37, 39, 42}) == 3: return 2837 - if len(set_ball & {37, 42, 44}) == 3: return 2838 - if len(set_ball & {38, 39, 45}) == 3: return 2839 - if len(set_ball & {38, 40, 41}) == 3: return 2840 - if len(set_ball & {38, 41, 42}) == 3: return 2841 - if len(set_ball & {38, 42, 44}) == 3: return 2842 - if len(set_ball & {38, 43, 45}) == 3: return 2843 - if len(set_ball & {38, 44, 45}) == 3: return 2844 - if len(set_ball & {39, 40, 42}) == 3: return 2845 - if len(set_ball & {39, 40, 45}) == 3: return 2846 - if len(set_ball & {39, 42, 43}) == 3: return 2847 - if len(set_ball & {39, 42, 44}) == 3: return 2848 - if len(set_ball & {40, 43, 45}) == 3: return 2849 - if len(set_ball & {40, 44, 45}) == 3: return 2850 - if len(set_ball & {41, 42, 44}) == 3: return 2851 - if len(set_ball & {1, 9, 12}) == 3: return 2852 - if len(set_ball & {1, 9, 28}) == 3: return 2853 - if len(set_ball & {1, 12, 23}) == 3: return 2854 - if len(set_ball & {1, 28, 41}) == 3: return 2855 - if len(set_ball & {2, 25, 28}) == 3: return 2856 - if len(set_ball & {2, 26, 43}) == 3: return 2857 - if len(set_ball & {3, 32, 45}) == 3: return 2858 - if len(set_ball & {4, 12, 24}) == 3: return 2859 - if len(set_ball & {4, 26, 33}) == 3: return 2860 - if len(set_ball & {4, 28, 40}) == 3: return 2861 - if len(set_ball & {4, 33, 40}) == 3: return 2862 - if len(set_ball & {6, 7, 15}) == 3: return 2863 - if len(set_ball & {6, 14, 21}) == 3: return 2864 - if len(set_ball & {6, 18, 31}) == 3: return 2865 - if len(set_ball & {7, 18, 23}) == 3: return 2866 - if len(set_ball & {7, 22, 40}) == 3: return 2867 - if len(set_ball & {7, 42, 45}) == 3: return 2868 - if len(set_ball & {8, 17, 27}) == 3: return 2869 - if len(set_ball & {8, 18, 45}) == 3: return 2870 - if len(set_ball & {8, 19, 21}) == 3: return 2871 - if len(set_ball & {8, 21, 31}) == 3: return 2872 - if len(set_ball & {10, 11, 39}) == 3: return 2873 - if len(set_ball & {10, 16, 41}) == 3: return 2874 - if len(set_ball & {11, 14, 21}) == 3: return 2875 - if len(set_ball & {11, 16, 21}) == 3: return 2876 - if len(set_ball & {11, 17, 21}) == 3: return 2877 - if len(set_ball & {11, 19, 21}) == 3: return 2878 - if len(set_ball & {11, 26, 44}) == 3: return 2879 - if len(set_ball & {11, 29, 44}) == 3: return 2880 - if len(set_ball & {12, 13, 32}) == 3: return 2881 - if len(set_ball & {12, 13, 33}) == 3: return 2882 - if len(set_ball & {12, 15, 34}) == 3: return 2883 - if len(set_ball & {12, 24, 27}) == 3: return 2884 - if len(set_ball & {12, 33, 40}) == 3: return 2885 - if len(set_ball & {12, 33, 42}) == 3: return 2886 - if len(set_ball & {12, 34, 42}) == 3: return 2887 - if len(set_ball & {13, 29, 39}) == 3: return 2888 - if len(set_ball & {13, 33, 45}) == 3: return 2889 - if len(set_ball & {14, 27, 30}) == 3: return 2890 - if len(set_ball & {14, 31, 40}) == 3: return 2891 - if len(set_ball & {14, 35, 39}) == 3: return 2892 - if len(set_ball & {15, 21, 34}) == 3: return 2893 - #if len(set_ball & {16, 25, 36}) == 2: return 2894 - if len(set_ball & {16, 27, 35}) == 3: return 2895 - if len(set_ball & {16, 31, 36}) == 3: return 2896 - if len(set_ball & {17, 30, 31}) == 3: return 2897 - if len(set_ball & {17, 34, 45}) == 3: return 2898 - if len(set_ball & {18, 30, 34}) == 3: return 2899 - if len(set_ball & {18, 30, 41}) == 3: return 2900 - if len(set_ball & {18, 31, 38}) == 3: return 2901 - if len(set_ball & {19, 21, 45}) == 3: return 2902 - if len(set_ball & {20, 26, 35}) == 3: return 2903 - if len(set_ball & {21, 34, 44}) == 3: return 2904 - if len(set_ball & {23, 29, 44}) == 3: return 2905 - if len(set_ball & {23, 30, 34}) == 3: return 2906 - if len(set_ball & {24, 27, 35}) == 3: return 2907 - if len(set_ball & {27, 29, 40}) == 3: return 2908 - if len(set_ball & {30, 39, 43}) == 3: return 2909 - if len(set_ball & {32, 33, 40}) == 3: return 2910 - if len(set_ball & {32, 40, 41}) == 3: return 2911 - if len(set_ball & {34, 42, 45}) == 3: return 2912 - if len(set_ball & {35, 43, 45}) == 3: return 2913 - if len(set_ball & {1, 3, 27}) == 3: return 2914 - if len(set_ball & {3, 12, 13}) == 3: return 2915 - if len(set_ball & {3, 20, 24}) == 3: return 2916 - if len(set_ball & {6, 18, 38}) == 3: return 2917 - if len(set_ball & {6, 37, 38}) == 3: return 2918 - if len(set_ball & {12, 15, 24}) == 3: return 2919 - #if len(set_ball & {14, 30, 38}) == 2: return 2920 - if len(set_ball & {15, 28, 34}) == 3: return 2921 - if len(set_ball & {17, 26, 36}) == 3: return 2922 - if len(set_ball & {18, 31, 34}) == 3: return 2923 - if len(set_ball & {21, 26, 36}) == 3: return 2924 - if len(set_ball & {23, 35, 43}) == 3: return 2925 - if len(set_ball & {33, 37, 40}) == 3: return 2926 - if len(set_ball & {3, 8, 27}) == 3: return 2927 - if len(set_ball & {3, 20, 44}) == 3: return 2928 - return None - - def extract_final_candidates(self, ball, no=None, until_end=False, df=None): - """ - - until_end=False: 첫 실패 사유만 빠르게 반환(후보 대량 평가/MC 추정용) - - until_end=True: 모든 실패 사유를 누적(분석/디버깅용) - """ - if df is None: - raise ValueError("df is required (needs previous-draw/window features).") - if no is None: - raise ValueError("no is required.") - - ball = sorted(list(ball)) - if self.isInValidBall(ball): - return {"Invalid ball"} - - p_ball = self._get_df_ball(df, int(no) - 1) - if p_ball is None: - p_ball = ball - - filter_set = set() - - def _fail(reason: str): - filter_set.add(reason) - if not until_end: - return filter_set - return None - - def _enabled(name: str, default: bool = True) -> bool: - return is_enabled(get_filter_cfg(self.ruleset, name), default=default) - - def _allowed_value(name: str, value: int, fallback_allowed: Optional[set] = None) -> bool: - cfg = get_filter_cfg(self.ruleset, name) - if not is_enabled(cfg, default=True): - return True - r = get_range(cfg, key="range") - if r is not None: - lo, hi = r - return lo <= value <= hi - allowed = cfg.get("allowed") - if isinstance(allowed, list): - return value in set(allowed) - if fallback_allowed is None: - return True - return value in fallback_allowed - - def _allowed_abs_diff(name: str, diff: int, fallback_allowed: Optional[set] = None) -> bool: - cfg = get_filter_cfg(self.ruleset, name) - if not is_enabled(cfg, default=True): - return True - max_abs = get_int(cfg, "max_abs_diff") - if max_abs is not None: - return diff <= max_abs - allowed = cfg.get("allowed") - if isinstance(allowed, list): - return diff in set(allowed) - if fallback_allowed is None: - return True - return diff in fallback_allowed - - # 0) 이전 당첨 번호(중복 조합 방지) - if _enabled("no_repeat_winner", default=True): - if self.hasWon(ball, no): - if _fail("이전 당첨 번호"): - return filter_set - - # 1) 앞 3개 합 + 전주차 - front3 = ball[0] + ball[1] + ball[2] - p_front3 = p_ball[0] + p_ball[1] + p_ball[2] - if not _allowed_value( - "front3_sum", - front3, - fallback_allowed=set([20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,42,45,46,47,48]), - ): - if _fail("b1+b2+b3: {}".format(front3)): - return filter_set - if not _allowed_abs_diff( - "front3_prev_diff", - abs(front3 - p_front3), - fallback_allowed=set([6,7,8,9,10,11,12,13,14,15,17,18,19,20,21,22,23,24,25]), - ): - if _fail("b1+b2+b3 전주차: {}".format(abs(front3 - p_front3))): - return filter_set - - # 2) 6개 합 + 전주차 diff(초기구간 강화 옵션 포함) - sum6 = sum(ball) - p_sum6 = sum(p_ball) - if not _allowed_value( - "sum", - sum6, - fallback_allowed={112, 114, 121, 123, 126, 127, 131, 132, 138, 146, 148}, - ): - if _fail(f"6개 합: {sum6}"): - return filter_set - - sum_diff = abs(sum6 - p_sum6) - cfg_sumdiff = get_filter_cfg(self.ruleset, "sum_prev_diff") - if is_enabled(cfg_sumdiff, default=True): - # early 구간(no<=max_no)은 후보 수가 튀는 경향이 있어 sum_prev_diff를 더 강하게 제한 - # (meta 설정값을 우선 적용) - meta = self.ruleset.get("meta") or {} - max_no = int(meta.get("early_strict_sum_prev_diff_max_no") or 0) - allowed_early = meta.get("early_strict_sum_prev_diff_allowed") - if max_no and int(no) <= max_no and isinstance(allowed_early, list) and allowed_early: - if sum_diff not in set(allowed_early): - if _fail(f"6개 합 전주차(초기강화): {sum_diff}"): - return filter_set - else: - if not _allowed_abs_diff( - "sum_prev_diff", - sum_diff, - fallback_allowed={2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 18, 24, 25}, - ): - if _fail(f"6개 합 전주차: {sum_diff}"): - return filter_set - else: - meta = self.ruleset.get("meta") or {} - max_no = int(meta.get("early_strict_sum_prev_diff_max_no") or 0) - allowed = meta.get("early_strict_sum_prev_diff_allowed") - if max_no and int(no) <= max_no and isinstance(allowed, list) and allowed: - if sum_diff not in set(allowed): - if _fail(f"6개 합 전주차(초기강화): {sum_diff}"): - return filter_set - - # 3) 평균 + 전주차 - avg_int = int(sum6 / 6) - p_avg_int = int(p_sum6 / 6) - if not _allowed_value( - "avg_int", - avg_int, - fallback_allowed=set([18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]), - ): - if _fail(f"6개 평균: {avg_int}"): - return filter_set - if not _allowed_abs_diff( - "avg_prev_diff", - abs(avg_int - p_avg_int), - fallback_allowed=set(range(0, 10)), - ): - if _fail(f"6개 평균 전주차: {abs(avg_int - p_avg_int)}"): - return filter_set - - # 4) 뒤 3개 합 + 전주차 - back3 = ball[3] + ball[4] + ball[5] - p_back3 = p_ball[3] + p_ball[4] + p_ball[5] - - # (선택) sum_diff 값에 따라 back3_sum을 추가로 제한 (survivors 과다 회차 억제) - meta = self.ruleset.get("meta") or {} - cond = meta.get("cond_back3_by_sumdiff") or {} - try: - key = str(int(sum_diff)) - if key in cond: - lo_hi = cond.get(key) - if isinstance(lo_hi, (list, tuple)) and len(lo_hi) == 2: - lo, hi = int(lo_hi[0]), int(lo_hi[1]) - if not (lo <= back3 <= hi): - if _fail(f"b4+b5+b6(cond sumdiff={sum_diff}): {back3}"): - return filter_set - except Exception: - # cond 파싱 실패 시에는 무시(보수적으로 기존 로직 유지) - pass - - if not _allowed_value( - "back3_sum", - back3, - # train 분포에서 빈도가 높은 값(97,105,106,114~117)을 포함해 out-of-sample 과도 탈락을 완화한다. - fallback_allowed=set([86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117]), - ): - if _fail(f"b4+b5+b6: {back3}"): - return filter_set - if not _allowed_abs_diff( - "back3_prev_diff", - abs(back3 - p_back3), - fallback_allowed=set([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,24,25,26,27,28,29,30,31]), - ): - if _fail(f"b4+b5+b6 전주차: {abs(back3 - p_back3)}"): - return filter_set - - # 5) 23 기준 저/고 개수 - low_cnt, high_cnt = self.getHigLowRate(ball) - if _enabled("high_low_min2", default=True): - if low_cnt in [0, 1] or high_cnt in [0, 1]: - if _fail(f"high/low: {low_cnt}/{high_cnt}"): - return filter_set - - # 6) 고저합 + 전주차 - minmax_sum = ball[0] + ball[5] - p_minmax_sum = p_ball[0] + p_ball[5] - if not _allowed_value("minmax_sum", minmax_sum, fallback_allowed=set(range(38, 58))): - if _fail("고저합: {}".format(minmax_sum)): - return filter_set - if not _allowed_abs_diff("minmax_prev_diff", abs(minmax_sum - p_minmax_sum), fallback_allowed=set(range(1, 16))): - if _fail("고저합 전주차: {}".format(abs(minmax_sum - p_minmax_sum))): - return filter_set - - # 7) 간격합 + 전주차 - interval_sum = self.get_ball_interval(ball) - p_interval_sum = self.get_ball_interval(p_ball) - - # (선택) sum_diff 값에 따라 interval_sum을 추가로 제한 (survivors 과다 회차 억제) - # 기본 ruleset에서는 후보가 너무 적게 남는 현상을 방지하기 위해 비활성화한다. - meta = self.ruleset.get("meta") or {} - try: - if bool(meta.get("cond_interval_allowed_by_sumdiff_enabled")): - key = str(int(sum_diff)) - cond_allowed = meta.get("cond_interval_allowed_by_sumdiff") or {} - if key in cond_allowed: - allowed_list = cond_allowed.get(key) - if isinstance(allowed_list, list) and allowed_list: - if interval_sum not in set(int(x) for x in allowed_list): - if _fail(f"Interval_sum(cond sumdiff={sum_diff}): {interval_sum}"): - return filter_set - except Exception: - # cond 파싱 실패 시에는 무시(보수적으로 기존 로직 유지) - pass - - if not _allowed_value("interval_sum", interval_sum, fallback_allowed=set(range(27, 45))): - if _fail("Interval_sum: {}".format(interval_sum)): - return filter_set - if not _allowed_abs_diff("interval_prev_diff", abs(interval_sum - p_interval_sum), fallback_allowed=set(range(0, 18))): - if _fail("Interval_sum 전주차: {}".format(abs(interval_sum - p_interval_sum))): - return filter_set - - # 8) 첫/끝 자리합 + 전주차 - first_letter_sum = self.getFirstLetterSumBall(ball) - p_first_letter_sum = self.getFirstLetterSumBall(p_ball) - if not _allowed_value("first_digit_sum", first_letter_sum, fallback_allowed=set([8, 9, 10, 11, 12, 13, 14, 15])): - if _fail("첫수합: {}".format(first_letter_sum)): - return filter_set - if not _allowed_abs_diff("first_digit_prev_diff", abs(first_letter_sum - p_first_letter_sum), fallback_allowed=set([0, 1, 2, 3, 4, 5, 6])): - if _fail("첫수합 전주차: {}".format(abs(first_letter_sum - p_first_letter_sum))): - return filter_set - - last_letter_sum = self.getLastLetterSumBall(ball) - p_last_letter_sum = self.getLastLetterSumBall(p_ball) - if not _allowed_value("last_digit_sum", last_letter_sum, fallback_allowed=set([16,21,23,24,25,26,27,28,29,30,31,32,33,34,36,37,38])): - if _fail("끝수합: {}".format(last_letter_sum)): - return filter_set - if not _allowed_abs_diff("last_digit_prev_diff", abs(last_letter_sum - p_last_letter_sum), fallback_allowed=set(range(0, 15))): - if _fail("끝수합 전주차: {}".format(abs(last_letter_sum - p_last_letter_sum))): - return filter_set - - # 9) 첫수/마지막수 + 전주차 - if not _allowed_value("first_ball", ball[0], fallback_allowed=set([1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 14])): - if _fail("첫수: {}".format(ball[0])): - return filter_set - if not _allowed_abs_diff("first_ball_prev_diff", abs(ball[0] - p_ball[0]), fallback_allowed=set(range(0, 13))): - if _fail(f"전주와 첫수 차: {abs(ball[0] - p_ball[0])}"): - return filter_set - - if not _allowed_value("last_ball", ball[5], fallback_allowed=set([36, 38, 39, 40, 41, 42, 43, 44, 45])): - if _fail("마지막 공: {}".format(ball[5])): - return filter_set - if not _allowed_abs_diff("last_ball_prev_diff", abs(ball[5] - p_ball[5]), fallback_allowed=set(range(0, 10))): - if _fail("마지막 공: {}".format(abs(ball[5] - p_ball[5]))): - return filter_set - - # 10) 유니크 끝자리 + 전주차 - uniq_last = self.filterOneDigitPattern(ball) - p_uniq_last = self.filterOneDigitPattern(p_ball) - if not _allowed_value("uniq_last_digit_count", uniq_last, fallback_allowed={4, 5, 6}): - if _fail("Unique 끝수 개수: {}".format(uniq_last)): - return filter_set - # 완화: 전주차 대비 Unique 끝수 개수 diff=2도 허용 (요청사항) - if not _allowed_abs_diff("uniq_last_digit_prev_diff", abs(uniq_last - p_uniq_last), fallback_allowed={0, 1, 2}): - if _fail("Unique 끝수 개수 전주차: {}".format(abs(uniq_last - p_uniq_last))): - return filter_set - - # 11) AC + 전주차 - ac_val = self.getACValue(ball) - p_ac_val = self.getACValue(p_ball) - if not _allowed_value("ac_value", ac_val, fallback_allowed={7, 8, 9, 10}): - if _fail("ac: {}".format(ac_val)): - return filter_set - if not _allowed_abs_diff("ac_prev_diff", abs(ac_val - p_ac_val), fallback_allowed={0, 1, 2, 3}): - if _fail("ac 전주: {}".format(abs(ac_val - p_ac_val))): - return filter_set - - # 12) 배수 + 전주차 - multiples = [ - (3, {1, 2, 3}, {0, 1, 2}), - (4, {0, 1, 2}, {0, 1, 2}), - (5, {0, 1, 2}, {0, 1, 2}), - (6, {0, 1, 2}, {0, 1, 2}), - (7, {0, 1, 2}, {0, 1}), - (8, {0, 1}, {0, 1}), - (9, {0, 1, 2}, {0, 1}), - (10, {0, 1}, {0, 1}), - (11, {0, 1, 2}, {0, 1}), - (13, {0, 1}, {0, 1}), - (17, {0, 1}, {0, 1}), - (19, {0, 1}, {0, 1}), - (23, {0}, {0, 1}), - ] - for n_mul, allowed_cnt, allowed_diff in multiples: - name_cnt = f"mul_{n_mul}_count" - name_diff = f"mul_{n_mul}_prev_diff" - if not _enabled(name_cnt, default=True): - continue - cnt = len([b for b in ball if b % n_mul == 0]) - p_cnt = len([b for b in p_ball if b % n_mul == 0]) - # sum=152 케이스는 train 분포상 존재하며, 일부 out-of-sample에서 과도한 탈락을 유발할 수 있어 제한적으로 완화 - if not (sum6 == 152 and n_mul == 8 and cnt == 2) and not _allowed_value(name_cnt, cnt, fallback_allowed=allowed_cnt): - if _fail(f"{n_mul}의배수: {cnt}"): - return filter_set - if not (sum6 == 152 and n_mul == 6 and abs(cnt - p_cnt) == 2) and not _allowed_abs_diff(name_diff, abs(cnt - p_cnt), fallback_allowed=allowed_diff): - if _fail(f"{n_mul}의배수 전주차: {abs(cnt - p_cnt)}"): - return filter_set - - # 13) 소수/복소수 - if _enabled("prime_count", default=True): - pn = len(set(ball) & set(self.primeNumber)) - if not _allowed_value("prime_count", pn, fallback_allowed=set([1, 2, 3])): - if _fail("소수: {}".format(pn)): - return filter_set - if _enabled("composite_count", default=True): - cn = len(set(ball) & set(self.compositeNumber)) - p_cn = len(set(p_ball) & set(self.compositeNumber)) - if not _allowed_value("composite_count", cn, fallback_allowed=set([3, 4, 5])): - if _fail("복소수: {}".format(cn)): - return filter_set - if not _allowed_abs_diff("composite_prev_diff", abs(cn - p_cn), fallback_allowed=set([0, 1, 2, 3])): - if _fail("복소수 전주차: {}".format(abs(cn - p_cn))): - return filter_set - - # 14) 홀짝(짝수) - even_cnt = len([b for b in ball if b % 2 == 0]) - p_even_cnt = len([b for b in p_ball if b % 2 == 0]) - if not _allowed_value("even_count", even_cnt, fallback_allowed=set([2, 3, 4])): - if _fail("짝수 (0,2,4): {}".format(even_cnt)): - return filter_set - if not _allowed_abs_diff("even_prev_diff", abs(even_cnt - p_even_cnt), fallback_allowed=set([0, 1, 2])): - if _fail("짝수 (0,2,4) 전주차: {}".format(abs(even_cnt - p_even_cnt))): - return filter_set - - # 15) 용지 패턴 - if _enabled("paper_patterns", default=True): - vs = [ - self.filterPatternInPaper1(ball), - self.filterPatternInPaper2(ball), - self.filterPatternInPaper3(ball), - self.filterPatternInPaper4(ball), - self.filterPatternInPaper5(ball), - self.filterPatternInPaper6(ball), - ] - for v in vs: - if v is not None: - if _fail(v): - return filter_set - - # 16) 전회차 수/좌우수 - if _enabled("previous_neighbors", default=True): - if self.filterPreviousNumber(ball, no): - if _fail("이전회차 수/좌우수"): - return filter_set - - # 17) 10구간 수 + 전주차 - section10 = self.getNumberOfAppearancesInSection10(ball) - p_section10 = self.getNumberOfAppearancesInSection10(p_ball) - if not _allowed_value("section10_count", section10, fallback_allowed={3, 4, 5}): - if _fail(f"같은 10구간대만 출현: {section10}"): - return filter_set - if not _allowed_abs_diff("section10_prev_diff", abs(section10 - p_section10), fallback_allowed={0, 1, 2}): - if _fail(f"같은 10구간대만 출현 전주차: {abs(section10 - p_section10)}"): - return filter_set - - # 18) 최근 N주 교집합 + 전주차 - weeks = [ - (8, {3, 4, 5, 6}, {0, 1, 2, 3}), - (12, {3, 4, 5, 6}, {0, 1, 2}), - (16, {4, 5, 6}, {0, 1}), - (20, {5, 6}, {0, 1}), - ] - for w, allowed_cnt, allowed_diff in weeks: - name_cnt = f"weeks_{w}_count" - name_diff = f"weeks_{w}_prev_diff" - if not _enabled(name_cnt, default=True): - continue - cnt = self.getWeeksFrequency(ball, df, no, week=w) - p_cnt = self.getWeeksFrequency(p_ball, df, no, week=w) - if not _allowed_value(name_cnt, cnt, fallback_allowed=allowed_cnt): - if _fail(f"{w} weeks"): - return filter_set - if not _allowed_abs_diff(name_diff, abs(cnt - p_cnt), fallback_allowed=allowed_diff): - if _fail(f"{w} weeks 전주차"): - return filter_set - - # 19) 3개 금지 조합(legacy) - if _enabled("ban_triples_legacy", default=True): - t3 = self.filterTriplePairBall(ball) - if t3 is not None: - if _fail(f"직관 3개 볼을 제거: {t3}"): - return filter_set - - # 20) 이전 7회차 모두 포함 - if _enabled("all_in_previous7", default=True): - if self.filterAllPreivous7(ball, no): - if _fail("이전 17차"): - return filter_set - - # 21) 연속수 - if _enabled("max_continuous_len_3", default=True): - continous_ball = self.getContinusNumber(ball) - if 3 < continous_ball: - if _fail("연속볼"): - return filter_set - - return filter_set - - def filter(self, ball, no, until_end=False, df=None, filter_ball=None): - filter_type = self.extract_final_candidates(ball=ball, no=no, until_end=until_end, df=df) - - return filter_type \ No newline at end of file diff --git a/filter_model_2.py b/filter_model_2.py deleted file mode 100644 index aa2c3dc..0000000 --- a/filter_model_2.py +++ /dev/null @@ -1,1256 +0,0 @@ -import json -from collections import Counter -import socket -from dataclasses import dataclass -from pathlib import Path -from typing import Any, Dict, Optional, Tuple - -import numpy as np -import pandas as pd - -# -# ruleset.py 기능 통합 (load_ruleset / get_filter_cfg / is_enabled / get_range / get_int) -# - - -class RulesetError(ValueError): - pass - - -def _as_int_pair(v: Any, key: str) -> Tuple[int, int]: - if not isinstance(v, (list, tuple)) or len(v) != 2: - raise RulesetError(f"{key} must be a 2-item list/tuple, got: {v!r}") - a, b = v - if not isinstance(a, int) or not isinstance(b, int): - raise RulesetError(f"{key} must be ints, got: {v!r}") - if a > b: - raise RulesetError(f"{key} must satisfy lo<=hi, got: {v!r}") - return a, b - - -def load_ruleset(path: Optional[str]) -> Dict[str, Any]: - """ - Load and minimally validate a ruleset JSON. - Returns dict; callers should treat it as read-only. - """ - if path is None: - return {} - p = Path(path) - if not p.exists(): - raise RulesetError(f"ruleset not found: {path}") - data = json.loads(p.read_text(encoding="utf-8")) - if not isinstance(data, dict): - raise RulesetError("ruleset root must be an object") - # minimal structural checks - if "filters" in data and not isinstance(data["filters"], dict): - raise RulesetError("ruleset.filters must be an object") - if "lottery" in data and not isinstance(data["lottery"], dict): - raise RulesetError("ruleset.lottery must be an object") - return data - - -def get_filter_cfg(ruleset: Dict[str, Any], name: str) -> Dict[str, Any]: - return (ruleset.get("filters") or {}).get(name) or {} - - -def is_enabled(cfg: Dict[str, Any], default: bool = True) -> bool: - v = cfg.get("enabled", default) - return bool(v) - - -def get_range(cfg: Dict[str, Any], key: str = "range") -> Optional[Tuple[int, int]]: - if key not in cfg: - return None - return _as_int_pair(cfg[key], key) - - -def get_int(cfg: Dict[str, Any], key: str) -> Optional[int]: - if key not in cfg: - return None - v = cfg[key] - if not isinstance(v, int): - raise RulesetError(f"{key} must be int, got: {v!r}") - return v - -socket.getaddrinfo(socket.gethostname(), None) - -class BallFilter: - history_ball_dict = None - history_ball_no_dict = None - history_ball_date_dict = None - history_ball_list = None - - primeNumber = None - compositeNumber = None - - def __init__( - self, - lottoHistoryFileName: Optional[str] = None, - ruleset_path: Optional[str] = None, - ruleset: Optional[Dict[str, Any]] = None, - ): - # ruleset 우선순위: dict 주입 > ruleset_path 로드 > 빈 dict - self.ruleset: Dict[str, Any] = ruleset if ruleset is not None else load_ruleset(ruleset_path) - # 별도 ruleset 파일 없이도 동작하도록, 기본(학습 기반 튜닝 결과) ruleset을 내장한다. - # NOTE: 사용자가 ruleset을 명시적으로 주입한 경우에는 그대로 존중한다. - if not self.ruleset: - self.ruleset = self._default_ruleset() - lottery_cfg = self.ruleset.get("lottery") or {} - # 공식 제약(기본): 1~45, 6개, 중복 없음 (범위는 isInValidBall에서 사용) - self.number_min = int(lottery_cfg.get("number_min") or 1) - self.number_max = int(lottery_cfg.get("number_max") or 45) - self.draw_size = int(lottery_cfg.get("draw_size") or 6) - - if lottoHistoryFileName is not None: - inFp = open(lottoHistoryFileName, 'r', encoding='utf-8') - self.history_ball_list = [] - self.history_ball_no_ymd = {} - self.history_ball_no_dict = {} - self.history_ball_date_dict = {} - self.history_ball_dict = {} - while True: - line = inFp.readline() - if not line or line == '\n': - break - data = json.loads(line) - self.history_ball_list.append(sorted([data['drwtNo1'], data['drwtNo2'], data['drwtNo3'], data['drwtNo4'], data['drwtNo5'], data['drwtNo6']])) - self.history_ball_no_dict[str(self.history_ball_list[len(self.history_ball_list) - 1])] = data['drwNo'] - self.history_ball_date_dict[data['drwNoDate'].replace('-', '')] = data['drwNo'] - self.history_ball_dict[data['drwNo']] = {'date': data['drwNoDate'], 'ball': [data['drwtNo1'], data['drwtNo2'], data['drwtNo3'], data['drwtNo4'], data['drwtNo5'], data['drwtNo6']]} - self.history_ball_no_ymd[data['drwNo']] = data['drwNoDate'].replace('-','') - inFp.close() - - # ball 평균과 합 구하기 - ball_avg = {} - ball_sum = {} - for i in range(len(self.history_ball_list)): - WIN_BALL = list(self.history_ball_list[-i]) - avg = sum(WIN_BALL) / 6 - if avg not in ball_avg: - ball_avg[avg] = 1 - else: - ball_avg[avg] += 1 - - if sum(self.history_ball_list[-i]) in ball_sum: - ball_sum[sum(self.history_ball_list[-i])] += 1 - else: - ball_sum[sum(self.history_ball_list[-i])] = 1 - - self.primeNumber = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43] - self.compositeNumber = [4, 6, 8, 9, 10, 12, 14, 15, 16, 18, 20, 21, 22, 24, 25, 26, 27, 28, 30, 32, 33, 34, 35, 36, 38, 39, 40, 42, 44, 45] - - # df lookup cache (for fast df[df["no"]==...] replacement) - # key: id(df) -> dict[int no] = list[int] balls (b1..b6) - self._df_no_to_ball_cache: Dict[int, Dict[int, list]] = {} - - return - - def _default_ruleset(self) -> Dict[str, Any]: - """ - 기본 ruleset (train=1~800 기준으로 튜닝된 결과를 코드에 내장). - 목표: - - train(21~800) hit-rate >= 1% (>= 8 hits / 780 draws) - - valid(801~1000) hits >= 3 / 200 - - survivors(평균) <= 300 (Monte Carlo 근사) - - 통계적 한계: - - 로또는 독립/균등 가설이 기본이며, 이 ruleset은 '예측'이 아니라 '후보 수를 줄이는 필터'이다. - """ - legacy_front3 = [ - 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, - 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, - 42, 45, 46, 47, 48, - ] - # train 분포에서 빈도가 있었지만 legacy에서 누락된 값(40, 49, 50)을 추가 - tuned_front3 = sorted(set(legacy_front3 + [40, 49, 50])) - - return { - "meta": { - # 운영/추천 품질을 위해 '특정 회차에서 통과 조합이 과도하게 많아지는' 현상을 완화한다. - # no가 작은 구간(초기 데이터)에서 통계/윈도우 기반 필터가 덜 강해지는 경향이 있어, - # 해당 구간에 한해 전주차 sum diff 필터를 부분적으로(allowed set) 적용한다. - "early_strict_sum_prev_diff_max_no": 200, - # 초기 구간에서 후보 과다 방지용(회차별 추천 수 300 미만 목표): - # sum_prev_diff를 매우 강하게 적용한다. - # train hit(71/139/147) 보호를 위해 필요한 값들을 포함 - "early_strict_sum_prev_diff_allowed": [26, 30, 40], - # sum_prev_diff(=abs(sum - prev_sum)) 값에 따라 back3_sum을 추가로 제한해, - # 일부 회차에서 survivors가 300을 초과하는 현상을 억제한다. - # (데이터 기반으로 최소한만 적용; 답안 예: no=900(sum_diff=13, back3_sum=91)) - "cond_back3_by_sumdiff": { - # diff: [lo, hi] inclusive - "13": [88, 96], - "14": [95, 110], - # diff=29에서 survivors가 과도하게 커지는 케이스(예: no=593)를 억제하기 위해 범위를 더 타이트하게 제한 - "29": [95, 95], - }, - # sum_diff 조건에 따라 interval_sum(간격합)을 추가 제한 (회차별 후보 과다 억제) - # - key: sum_diff - # - value: allowed interval_sum values - "cond_interval_allowed_by_sumdiff": { - "4": [27, 40], - "6": [29], - "13": [31], - "14": [33], - "17": [29], - "18": [37, 39, 43], - "26": [34], - "28": [34, 43], - "29": [29], - "30": [36], - "32": [29], - "39": [37], - "40": [38], - }, - }, - "filters": { - # 6개 합: 후보 수에 큰 영향을 주는 축이므로 allowed를 크게 늘리지 않는다. - # train 분포에서 등장하는 152를 추가해 out-of-sample 과도 탈락을 완화한다. - "sum": {"enabled": True, "allowed": [112, 114, 121, 123, 126, 127, 131, 132, 138, 146, 148, 152]}, - # 전주 대비 '6개 합 차이'는 후보 수를 크게 줄이는 축(특히 500+로 튀는 회차에서 효과적). - # 기본은 활성화하되, allowed를 보수적으로 구성해 hit를 유지한다. - # NOTE: valid hit 회차(841,900) diff=14,13 포함. train hit(초기) 보호는 meta early_strict로 별도 처리. - # train 분포에서 충분히 자주 등장(coverage 기여)하는 32를 추가해, out-of-sample에서의 과도한 탈락을 완화. - "sum_prev_diff": {"enabled": True, "allowed": [4, 6, 13, 14, 17, 18, 26, 28, 29, 30, 32, 39, 40]}, - # 앞 3개 합은 강력한 압축 필터이므로 유지하되, - # train에서 자주 등장한 누락 값을 소폭 허용해 과도한 탈락을 완화. - "front3_sum": {"enabled": True, "allowed": tuned_front3}, - - # ------------------------------------------------------------ - # Candidate-size control 강화 (목표: 회차별 survivors <= 300) - # - # NOTE - # - 아래 allowed는 실제 당첨 조합(샘플 회차)에서 관측된 값들을 기반으로 구성한다. - # - 목적은 "정답 통과(당첨개수 유지) + 추천 수 과다(수천~수만) 억제"이다. - # - extract_final_candidates()에서 fallback_allowed로 넓게 열어 둔 축들을 ruleset로 '좁혀'준다. - # ------------------------------------------------------------ - - # 뒤 3개 합 - "back3_sum": {"enabled": True, "allowed": [86, 87, 90, 91, 94, 95, 99, 100, 101, 103, 109, 112, 113, 116]}, - # 고저합(최소+최대) - "minmax_sum": {"enabled": True, "allowed": [38, 39, 43, 45, 46, 47, 50, 51, 52, 53, 57]}, - # 간격합 - "interval_sum": {"enabled": True, "allowed": [27, 29, 31, 33, 34, 36, 37, 38, 39, 40, 43]}, - # 첫자리수 합 / 끝자리수 합 - "first_digit_sum": {"enabled": True, "allowed": [8, 9, 10, 11, 12]}, - "last_digit_sum": {"enabled": True, "allowed": [16, 21, 26, 27, 28, 32, 33, 34, 37, 38]}, - # AC 값 - "ac_value": {"enabled": True, "allowed": [8, 9, 10]}, - - # 전주차 diff 축들(범위를 넓게 두면 survivors가 급증하므로 allowed로 좁힘) - "front3_prev_diff": {"enabled": True, "allowed": [7, 11, 12, 13, 14, 17, 18, 19, 20, 22, 24, 25]}, - "back3_prev_diff": {"enabled": True, "allowed": [1, 2, 10, 11, 13, 15, 16, 18, 19, 25]}, - "minmax_prev_diff": {"enabled": True, "allowed": [1, 2, 3, 4, 5, 7, 8, 12, 14]}, - "interval_prev_diff": {"enabled": True, "allowed": [0, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14]}, - "first_digit_prev_diff": {"enabled": True, "allowed": [0, 1, 2, 3, 4]}, - "last_digit_prev_diff": {"enabled": True, "allowed": [0, 1, 2, 4, 6, 7, 8, 10, 12, 14]}, - # 10구간 출현 전주차 diff는 2까지 열어두면 survivors가 늘 수 있어 0~1로 제한 - "section10_prev_diff": {"enabled": True, "allowed": [0, 1]}, - # 평균 전주차 diff도 지나치게 크면 후보가 늘 수 있어 상한을 둠 - "avg_prev_diff": {"enabled": True, "range": [0, 6]}, - } - } - - def getBall(self, no): - if no in self.history_ball_dict: - return self.history_ball_dict[no]['ball'] - return [] - - def getLastNo(self, YMD): - if YMD in self.history_ball_date_dict: - return self.history_ball_date_dict[YMD] - return len(self.history_ball_no_dict) - - def getNextNo(self, YMD): - if YMD in self.history_ball_date_dict: - return self.history_ball_date_dict[YMD] - return len(self.history_ball_no_dict) + 1 - - def getYMD(self, no): - if no in self.history_ball_no_ymd: - return self.history_ball_no_ymd[no] - if self.history_ball_no_ymd: - return self.history_ball_no_ymd[max(self.history_ball_no_ymd.keys())] - return "" - - def _get_df_ball(self, df: pd.DataFrame, no: int) -> Optional[list]: - """ - Fast lookup for draw balls (b1..b6) by draw number. - Falls back to pandas filtering if cache missing. - """ - df_id = id(df) - mapping = self._df_no_to_ball_cache.get(df_id) - if mapping is None: - try: - # build once per df instance - mapping = {} - for row in df[["no", "b1", "b2", "b3", "b4", "b5", "b6"]].itertuples(index=False, name=None): - mapping[int(row[0])] = list(row[1:7]) - self._df_no_to_ball_cache[df_id] = mapping - except Exception: - # fallback: no cache - row = df[df["no"] == no].values.tolist() - return row[0][1:7] if row else None - - return mapping.get(int(no)) - - def isInValidBall(self, ball): - for i, b in enumerate(ball): - if b < self.number_min or self.number_max < b: - return True - if i > 0: - if ball[i - 1] == b: - return True - - return False - - def hasWon(self, ball, NO=None): - # 기존 당첨 번호라면 - sorted_ball = sorted(ball) - if NO == None: - if str(sorted_ball) in self.history_ball_no_dict: - return True - else: - if str(sorted_ball) in self.history_ball_no_dict: - no = self.history_ball_no_dict[str(sorted_ball)] - if no == NO: - return False - return True - return False - - def filterFrequency3Windows(self, drwNo, ball, N, given_count): - """ - 24주간 당첨 번호들에 대해서 출현 빈도 순으로 정렬하고, 정렬된 리스트에서 상위 N개, 중간 N개, 하위 N개만 취함 - 예, N=10 이라면 1~10, 23-10/0~23+10/0 ,36~45 - 세 개 구간에 대해서 이번 회차의 번호와 겹치는 숫자의 개수를 구하고 given_count 이하 개수라면 filter - """ - if drwNo - 2 - 24 < 1: - return True - - fBall = [] - for j in range(drwNo - 2, drwNo - 2 - 24, -1): - for b in self.history_ball_list[j]: - fBall.append(b) - - ball_count = dict(Counter(fBall)) - ball_count_sort = sorted(ball_count.items(), key=lambda x: x[1], reverse=True) - - ball_sort = [b[0] for b in ball_count_sort] - - ball_set = set(ball) - match_check_ball = set(ball_set) & ( - set(ball_sort[:N]) | set(ball_sort[int(23 - N / 2):int(23 + N / 2)]) | set(ball_sort[45 - N:])) - - if len(match_check_ball) <= given_count: - return True - - return False - - def filterFirstBallUnderNumber(self, ball, N=5): - """ - 첫 숫자가 N 이하인 경우 - """ - - WIN_BALL = sorted(ball) - if WIN_BALL[0] <= N: - return True - - return False - - def filterLastBallOverNumber(self, ball, N=5): - """ - 마지막 숫자가 N 이상인 경우 - """ - - WIN_BALL = sorted(ball) - if WIN_BALL[5] >= N: - return True - - return False - - def filterLastBallUnderNumber(self, ball, N=20): - """ - 마지막 숫자가 N 이상인 경우 - """ - - WIN_BALL = sorted(ball) - if WIN_BALL[5] <= N: - return True - - return False - - - def getEndNumberCount(self, ball): - return set([int(str(b).zfill(2)[1]) for b in ball]) - - def filterEndNumberCount(self, ball, N_list=None): - if N_list is None: - N_list = [4, 5] - - size = self.getEndNumberCount(ball) - if size in N_list: - return True - return False - - def getFirstBallOverNumber(self, ball, N=0): - """ - 첫 숫자가 N 이상은 버림 - """ - - WIN_BALL = sorted(ball) - return WIN_BALL[N] - - def isContinusFriendNumber(self, drwNo, ball): - """ - 이웃수 체크: 특정 번호에 대해서 다음 수나 이전 수가 나오는 경우 - 이전 당첨 번호 중 하나가 7이라면 이번에 6혹은 8이 없어야 함. - 이런 식의 이웃수가 있다면 True - """ - if drwNo <= 2: - return False - - P_WIN_BALL = list(self.history_ball_list[drwNo - 2]) - WIN_BALL_SET = set(ball) - isValid = False - for b in P_WIN_BALL: - if b - 1 in WIN_BALL_SET or b + 1 in WIN_BALL_SET: - isValid = True - break - return isValid - - def isOverlapNumber(self, drwNo, ball, N): - """ - 연속해서 겹치는 수가 출현하는지 체크 - """ - - if drwNo <= N: - return True - - WIN_BALL_SET = set(sorted(ball)) - overlapCount = [] - for i in range(N): - P_WIN_BALL_SET = set(sorted(self.history_ball_list[drwNo - (i + 2)])) - - if len(WIN_BALL_SET & P_WIN_BALL_SET) > 0: - overlapCount.append(1) - else: - overlapCount.append(0) - if sum(overlapCount) == N: - return True - - return False - - def filterContinusNumber(self, ball, N): - """ - 하나의 당첨 번호에서 N개 연속된 숫자인지 체크하여 필터링 - """ - - WIN_BALL = sorted(ball) - if N == 6: - if ( - WIN_BALL[0] + 5 == WIN_BALL[1] + 4 == WIN_BALL[2] + 3 == WIN_BALL[3] + 2 == WIN_BALL[4] + 1 == - WIN_BALL[5] - ): - return True - if N == 5: - if ( - WIN_BALL[0] + 4 == WIN_BALL[1] + 3 == WIN_BALL[2] + 2 == WIN_BALL[3] + 1 == WIN_BALL[4] - or WIN_BALL[1] + 4 == WIN_BALL[2] + 3 == WIN_BALL[3] + 2 == WIN_BALL[4] + 1 == WIN_BALL[5] - ): - return True - if N == 4: - if ( - WIN_BALL[0] + 3 == WIN_BALL[1] + 2 == WIN_BALL[2] + 1 == WIN_BALL[3] - or WIN_BALL[1] + 3 == WIN_BALL[2] + 2 == WIN_BALL[3] + 1 == WIN_BALL[4] - or WIN_BALL[2] + 3 == WIN_BALL[3] + 2 == WIN_BALL[4] + 1 == WIN_BALL[5] - ): - return True - if N == 3: - if ( - WIN_BALL[0] + 2 == WIN_BALL[1] + 1 == WIN_BALL[2] - or WIN_BALL[1] + 2 == WIN_BALL[2] + 1 == WIN_BALL[3] - or WIN_BALL[2] + 2 == WIN_BALL[3] + 1 == WIN_BALL[4] - or WIN_BALL[3] + 2 == WIN_BALL[4] + 1 == WIN_BALL[5] - ): - return True - if N == 2: - if ( - WIN_BALL[0] + 1 == WIN_BALL[1] - or WIN_BALL[1] + 1 == WIN_BALL[2] - or WIN_BALL[2] + 1 == WIN_BALL[3] - or WIN_BALL[3] + 1 == WIN_BALL[4] - or WIN_BALL[4] + 1 == WIN_BALL[5] - ): - return True - - return False - - def getContinusNumber(self, ball): - """ - 하나의 당첨 번호에서 N개 연속된 숫자인지 체크하여 필터링 - """ - - WIN_BALL = sorted(ball) - - if (WIN_BALL[0] + 5 == WIN_BALL[1] + 4 == WIN_BALL[2] + 3 == WIN_BALL[3] + 2 == WIN_BALL[4] + 1 == WIN_BALL[5]): - return 6 - if (WIN_BALL[0] + 4 == WIN_BALL[1] + 3 == WIN_BALL[2] + 2 == WIN_BALL[3] + 1 == WIN_BALL[4] - or WIN_BALL[1] + 4 == WIN_BALL[2] + 3 == WIN_BALL[3] + 2 == WIN_BALL[4] + 1 == WIN_BALL[5]): - return 5 - if (WIN_BALL[0] + 3 == WIN_BALL[1] + 2 == WIN_BALL[2] + 1 == WIN_BALL[3] - or WIN_BALL[1] + 3 == WIN_BALL[2] + 2 == WIN_BALL[3] + 1 == WIN_BALL[4] - or WIN_BALL[2] + 3 == WIN_BALL[3] + 2 == WIN_BALL[4] + 1 == WIN_BALL[5]): - return 4 - if (WIN_BALL[0] + 2 == WIN_BALL[1] + 1 == WIN_BALL[2] - or WIN_BALL[1] + 2 == WIN_BALL[2] + 1 == WIN_BALL[3] - or WIN_BALL[2] + 2 == WIN_BALL[3] + 1 == WIN_BALL[4] - or WIN_BALL[3] + 2 == WIN_BALL[4] + 1 == WIN_BALL[5]): - return 3 - if (WIN_BALL[0] + 1 == WIN_BALL[1] - or WIN_BALL[1] + 1 == WIN_BALL[2] - or WIN_BALL[2] + 1 == WIN_BALL[3] - or WIN_BALL[3] + 1 == WIN_BALL[4] - or WIN_BALL[4] + 1 == WIN_BALL[5]): - return 2 - - return 1 - - def filterContinusWinCount(self, drwNo, ball, N=3): - """ - 특정 한 번호가 이전 회차에서 N번 연속 당첨한 경우는 필터링 - """ - - if drwNo <= N: - return True - - section = self.history_ball_list[drwNo - N - 1:drwNo - 1] - - WIN_BALL_SET = set(sorted(ball)) - for b in WIN_BALL_SET: - overlapCount = [] - for i in range(len(section) - 1, -1, -1): - P_WIN_BALL_SET = set(sorted(section[i])) - - if b in P_WIN_BALL_SET: - overlapCount.append(1) - else: - overlapCount.append(0) - if sum(overlapCount) == N: - return True - - return False - - def filterBallAverage(self, ball): - # 6개 당첨 공들의 평균 - # if sum(ball)/6 not in self.VALID_AVG: - # if sum(ball)/6 < min(self.VALID_AVG.keys()) or max(self.VALID_AVG.keys()) < sum(ball)/6: - avg_value = sum(ball) / 6 - if not (19 < avg_value < 20 or 21 < avg_value < 22 or 28 < avg_value < 29): - return True - return False - - def getBallAverage(self, ball): - # 6개 당첨 공들의 평균 - return sum(ball) / 6 - - def filterTotalSum(self, ball): - # 6개 당첨 공들의 평균 - # if sum(ball) < min(self.VALID_SUM.keys()) or max(self.VALID_SUM.keys()) < sum(ball): - sum_value = sum(ball) - if not (115 < sum_value < 120 or 125 < sum_value < 130 or 170 < sum_value < 175): - return True - return False - - def getTotalSum(self, ball): - # 6개 당첨 공들의 평균 - return sum(ball) - - def getNonAppearances(self, drwNo, ball): - """ - 미출현 회수 - """ - - b0, b1, b2, b3, b4, b5 = 0, 0, 0, 0, 0, 0 - c0, c1, c2, c3, c4, c5 = 0, 0, 0, 0, 0, 0 - for idx in range(drwNo - 2, 0, -1): - h_ball = self.history_ball_list[idx] - if c0 == 0 and ball[0] not in h_ball: - b0 += 1 - if ball[0] in h_ball: - c0 = 1 - - if c1 == 0 and ball[1] not in h_ball: - b1 += 1 - if ball[1] in h_ball: - c1 = 1 - - if c2 == 0 and ball[2] not in h_ball: - b2 += 1 - if ball[2] in h_ball: - c2 = 1 - - if c3 == 0 and ball[3] not in h_ball: - b3 += 1 - if ball[3] in h_ball: - c3 = 1 - - if c4 == 0 and ball[4] not in h_ball: - b4 += 1 - if ball[4] in h_ball: - c4 = 1 - - if c5 == 0 and ball[5] not in h_ball: - b5 += 1 - if ball[5] in h_ball: - c5 = 1 - - if c0 == 1 and c1 == 1 and c2 == 1 and c3 == 1 and c4 == 1 and c5 == 1: - break - - return b0, b1, b2, b3, b4, b5 - - # 앞번호 숫자들의 합 - def getFrontDigitsSum(self, ball): - return sum([int(str(b).zfill(2)[0]) for b in ball]) - - # 뒷번호 숫자들의 합 - def getLastDigitsSum(self, ball): - return sum([int(str(b).zfill(2)[1]) for b in ball]) - - def filterEvenCount(self, ball): - """ - 모두 짝수이거나 홀수이면 필터 [0, 4, 6, 8, 10], [1, 2, 5, 7, 9, 11] - """ - - even_list = [b for b in ball if b % 2 == 0] - # odd_list = [b for b in ball if b % 0 == 1] - - return len(even_list) - - def getEvenCount(self, ball): - """ - 모두 짝수이거나 홀수이면 필터 [0, 4, 6, 8, 10], [1, 2, 5, 7, 9, 11] - """ - - return len([b for b in ball if b % 2 == 0]) - - def filterNTimesIn15UnitSections(self, ball, N=4): - # 같은 5단위 4개 이상인 경우 - # [1, 0, 2, 4, 15, 16] - # [15, 11, 11, 14, 25, 36] - # [15, 22, 23, 24, 25, 36] - # [15, 32, 33, 34, 25, 36] - # [41, 42, 43, 44, 15, 36] - - b1 = [b for b in ball if 1 <= b <= 15] - b2 = [b for b in ball if 16 <= b <= 30] - b3 = [b for b in ball if 31 <= b <= 45] - - if len(b1) >= N or len(b2) >= N or len(b3) >= N: - return True - - return False - - def filterNTimesIn10UnitSections(self, ball, N=4): - # 같은 10단위 4개 이상인 경우 - - b1 = [b for b in ball if 1 <= b <= 10] - b2 = [b for b in ball if 11 <= b <= 20] - b3 = [b for b in ball if 21 <= b <= 30] - b4 = [b for b in ball if 31 <= b <= 40] - b5 = [b for b in ball if 41 <= b <= 45] - - if len(b1) >= N or len(b2) >= N or len(b3) >= N or len(b4) >= N or len(b5) >= N: - return True - - return False - - def filterNTimesIn9UnitSections(self, ball, N=4): - # 같은 9단위 4개 이상인 경우 - # [1, 0, 2, 4, 15, 16] - # [15, 11, 11, 14, 25, 36] - # [15, 22, 23, 24, 25, 36] - # [15, 32, 33, 34, 25, 36] - # [41, 42, 43, 44, 15, 36] - - b1 = [b for b in ball if 1 <= b <= 9] - b2 = [b for b in ball if 10 <= b <= 18] - b3 = [b for b in ball if 19 <= b <= 27] - b4 = [b for b in ball if 28 <= b <= 36] - b5 = [b for b in ball if 37 <= b <= 45] - - if len(b1) >= N or len(b2) >= N or len(b3) >= N or len(b4) >= N or len(b5) >= N: - return True - - return False - - def filterNTimesIn7UnitSections(self, ball, N=4): - # 같은 5단위 4개 이상인 경우 - - b1 = [b for b in ball if 1 <= b <= 7] - b2 = [b for b in ball if 6 <= b <= 14] - b3 = [b for b in ball if 11 <= b <= 21] - b4 = [b for b in ball if 16 <= b <= 28] - b5 = [b for b in ball if 21 <= b <= 25] - b6 = [b for b in ball if 26 <= b <= 30] - b7 = [b for b in ball if 31 <= b <= 35] - b8 = [b for b in ball if 36 <= b <= 40] - b9 = [b for b in ball if 41 <= b <= 45] - - if ( - len(b1) >= N or len(b2) >= N or len(b3) >= N or len(b4) >= N or len(b5) >= N - or len(b6) >= N or len(b7) >= N or len(b8) >= N or len(b9) >= N - ): - return True - - return False - - def filterNTimesIn5UnitSections(self, ball, N=4): - # 같은 5단위 4개 이상인 경우 - - b1 = [b for b in ball if 1 <= b <= 5] - b2 = [b for b in ball if 6 <= b <= 10] - b3 = [b for b in ball if 11 <= b <= 15] - b4 = [b for b in ball if 16 <= b <= 20] - b5 = [b for b in ball if 21 <= b <= 25] - b6 = [b for b in ball if 26 <= b <= 30] - b7 = [b for b in ball if 31 <= b <= 35] - b8 = [b for b in ball if 36 <= b <= 40] - b9 = [b for b in ball if 41 <= b <= 45] - - if ( - len(b1) >= N or len(b2) >= N or len(b3) >= N or len(b4) >= N or len(b5) >= N - or len(b6) >= N or len(b7) >= N or len(b8) >= N or len(b9) >= N - ): - return True - - return False - - def filterGivenData(self, ball): - if not (ball[0] < 5 and ball[1] < 10 and 37 < ball[5]): - return True - - return False - - def filterPreviousNumber(self, ball, no): - previous_ball = self.getBall(no-1) - pb_set = set(previous_ball) - - if ( - ball[0] not in pb_set and ball[0] - 1 not in pb_set and ball[0] + 1 not in pb_set and - ball[1] not in pb_set and ball[1] - 1 not in pb_set and ball[1] + 1 not in pb_set and - ball[2] not in pb_set and ball[2] - 1 not in pb_set and ball[2] + 1 not in pb_set and - ball[3] not in pb_set and ball[3] - 1 not in pb_set and ball[3] + 1 not in pb_set and - ball[4] not in pb_set and ball[4] - 1 not in pb_set and ball[4] + 1 not in pb_set and - ball[5] not in pb_set and ball[5] - 1 not in pb_set and ball[5] + 1 not in pb_set - ): - return True - return False - - def getACValue(self, ball): - ac = set() - for i in range(5, -1, -1): - for j in range(i-1, -1, -1): - ac.add( ball[i] - ball[j]) - return len(ac) - (6-1) - - def getNumberOfAppearancesInSection10(self, ball): - section = set() - for b in ball: - v = int(b/10) - if v not in section: - section.add(v) - return len(section) - - def get_ball_interval(self, ball): - interval_sum = 0 - for i in range(1, len(ball)): - interval_sum += (ball[i] - ball[i-1]) - return interval_sum - - def getFirstLetterSumBall(self, ball): - acc = [str(b)[0] for b in ball if len(str(b))==2] - acc = [int(b) for b in acc] - return sum(acc) - - def getLastLetterSumBall(self, ball): - acc = [str(b)[1] for b in ball if len(str(b)) == 2] + [str(b) for b in ball if len(str(b)) == 1] - acc = [int(b) for b in acc] - return sum(acc) - - def getWeeksFrequency(self, answer, df=None, no=None, week=20): - if df is None: - # fallback to history if caller didn't provide df (build with 'no' column) - if self.history_ball_list is None: - return 0 - rows = [] - for idx, balls in enumerate(self.history_ball_list, start=1): - rows.append([idx] + list(balls) + [0]) - df = pd.DataFrame(rows, columns=["no", "b1", "b2", "b3", "b4", "b5", "b6", "bn"]) - - dic = {} - ball = [] - for w in range(1, week+1): - pb = self._get_df_ball(df, no - w) - if pb is None: - continue - ball += pb - - for b in ball: - if b not in dic: - dic[b] = 1 - else: - dic[b] += 1 - - exist_ball = set() - for b in answer: - if b in dic: - exist_ball.add(b) - - return len(exist_ball) - - def filterOverseas(self, ball, no): - if no in self.oversea_history_ball: - oversea_balls = self.oversea_history_ball[no] - match = [] - for b in ball: - if b in oversea_balls: - match.append(1) - if len(match) < 3: - return True - return False - - def filterAllPreivous7(self, ball, no): - pb_set = set() - for i in range(no-1, no-8, -1): - pb = self.getBall(i) - for b in pb: - if b not in pb_set: - pb_set.add(b) - if len(set(ball) & pb_set) == 6: - return True - return False - - def checkFilter_JapanMethod(self, df, week=26): - # https://xn--961bo7bg3gjne.com/menu_103.php - - all_balls = {} - pos = len(df) - 1 - try_num = 0 - for i in range(pos, pos - week, -1): - ball = [df['b1'].iloc[i], df['b2'].iloc[i], df['b3'].iloc[i], df['b4'].iloc[i], df['b5'].iloc[i], df['b6'].iloc[i]] - for b in ball: - if b not in all_balls: - all_balls[b] = 1 - else: - all_balls[b] += 1 - try_num += 1 - - all_balls_sorted = sorted(all_balls.items(), key=lambda x: x[1], reverse=True) - return set([bf[0] for bf in all_balls_sorted if bf[1] in [2,3]]) - - def getHigLowRate(self, ball): - low = [] - high = [] - for b in ball: - if b < 23: - low.append(b) - if 23 < b: - high.append(b) - return len(low), len(high) - - def filterOneDigitPattern(self, ball): - # 끝자리(0~9) 유니크 개수 - digit = set() - for b in ball: - digit.add(b % 10) - return len(digit) - - - def extract_final_candidates(self, ball, no=None, until_end=False, df=None): - """ - - until_end=False: 첫 실패 사유만 빠르게 반환(후보 대량 평가/MC 추정용) - - until_end=True: 모든 실패 사유를 누적(분석/디버깅용) - """ - if df is None: - raise ValueError("df is required (needs previous-draw/window features).") - if no is None: - raise ValueError("no is required.") - - ball = sorted(list(ball)) - if self.isInValidBall(ball): - return {"Invalid ball"} - - p_ball = self._get_df_ball(df, int(no) - 1) - if p_ball is None: - p_ball = ball - - filter_set = set() - - def _fail(reason: str): - filter_set.add(reason) - if not until_end: - return filter_set - return None - - def _enabled(name: str, default: bool = True) -> bool: - return is_enabled(get_filter_cfg(self.ruleset, name), default=default) - - def _allowed_value(name: str, value: int, fallback_allowed: Optional[set] = None) -> bool: - cfg = get_filter_cfg(self.ruleset, name) - if not is_enabled(cfg, default=True): - return True - r = get_range(cfg, key="range") - if r is not None: - lo, hi = r - return lo <= value <= hi - allowed = cfg.get("allowed") - if isinstance(allowed, list): - return value in set(allowed) - if fallback_allowed is None: - return True - return value in fallback_allowed - - def _allowed_abs_diff(name: str, diff: int, fallback_allowed: Optional[set] = None) -> bool: - cfg = get_filter_cfg(self.ruleset, name) - if not is_enabled(cfg, default=True): - return True - max_abs = get_int(cfg, "max_abs_diff") - if max_abs is not None: - return diff <= max_abs - allowed = cfg.get("allowed") - if isinstance(allowed, list): - return diff in set(allowed) - if fallback_allowed is None: - return True - return diff in fallback_allowed - - # 0) 이전 당첨 번호(중복 조합 방지) - if _enabled("no_repeat_winner", default=True): - if self.hasWon(ball, no): - if _fail("이전 당첨 번호"): - return filter_set - - # 1) 앞 3개 합 + 전주차 - front3 = ball[0] + ball[1] + ball[2] - p_front3 = p_ball[0] + p_ball[1] + p_ball[2] - if not _allowed_value( - "front3_sum", - front3, - fallback_allowed=set([20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,42,45,46,47,48]), - ): - if _fail("b1+b2+b3: {}".format(front3)): - return filter_set - if not _allowed_abs_diff( - "front3_prev_diff", - abs(front3 - p_front3), - fallback_allowed=set([6,7,8,9,10,11,12,13,14,15,17,18,19,20,21,22,23,24,25]), - ): - if _fail("b1+b2+b3 전주차: {}".format(abs(front3 - p_front3))): - return filter_set - - # 2) 6개 합 + 전주차 diff(초기구간 강화 옵션 포함) - sum6 = sum(ball) - p_sum6 = sum(p_ball) - if not _allowed_value( - "sum", - sum6, - fallback_allowed={112, 114, 121, 123, 126, 127, 131, 132, 138, 146, 148}, - ): - if _fail(f"6개 합: {sum6}"): - return filter_set - - sum_diff = abs(sum6 - p_sum6) - cfg_sumdiff = get_filter_cfg(self.ruleset, "sum_prev_diff") - if is_enabled(cfg_sumdiff, default=True): - # early 구간(no<=max_no)은 후보 수가 튀는 경향이 있어 sum_prev_diff를 더 강하게 제한 - # (meta 설정값을 우선 적용) - meta = self.ruleset.get("meta") or {} - max_no = int(meta.get("early_strict_sum_prev_diff_max_no") or 0) - allowed_early = meta.get("early_strict_sum_prev_diff_allowed") - if max_no and int(no) <= max_no and isinstance(allowed_early, list) and allowed_early: - if sum_diff not in set(allowed_early): - if _fail(f"6개 합 전주차(초기강화): {sum_diff}"): - return filter_set - else: - if not _allowed_abs_diff( - "sum_prev_diff", - sum_diff, - fallback_allowed={2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 18, 24, 25}, - ): - if _fail(f"6개 합 전주차: {sum_diff}"): - return filter_set - else: - meta = self.ruleset.get("meta") or {} - max_no = int(meta.get("early_strict_sum_prev_diff_max_no") or 0) - allowed = meta.get("early_strict_sum_prev_diff_allowed") - if max_no and int(no) <= max_no and isinstance(allowed, list) and allowed: - if sum_diff not in set(allowed): - if _fail(f"6개 합 전주차(초기강화): {sum_diff}"): - return filter_set - - # 3) 평균 + 전주차 - avg_int = int(sum6 / 6) - p_avg_int = int(p_sum6 / 6) - if not _allowed_value( - "avg_int", - avg_int, - fallback_allowed=set([18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]), - ): - if _fail(f"6개 평균: {avg_int}"): - return filter_set - if not _allowed_abs_diff( - "avg_prev_diff", - abs(avg_int - p_avg_int), - fallback_allowed=set(range(0, 10)), - ): - if _fail(f"6개 평균 전주차: {abs(avg_int - p_avg_int)}"): - return filter_set - - # 4) 뒤 3개 합 + 전주차 - back3 = ball[3] + ball[4] + ball[5] - p_back3 = p_ball[3] + p_ball[4] + p_ball[5] - - # (선택) sum_diff 값에 따라 back3_sum을 추가로 제한 (survivors 과다 회차 억제) - meta = self.ruleset.get("meta") or {} - cond = meta.get("cond_back3_by_sumdiff") or {} - try: - key = str(int(sum_diff)) - if key in cond: - lo_hi = cond.get(key) - if isinstance(lo_hi, (list, tuple)) and len(lo_hi) == 2: - lo, hi = int(lo_hi[0]), int(lo_hi[1]) - if not (lo <= back3 <= hi): - if _fail(f"b4+b5+b6(cond sumdiff={sum_diff}): {back3}"): - return filter_set - except Exception: - # cond 파싱 실패 시에는 무시(보수적으로 기존 로직 유지) - pass - - if not _allowed_value( - "back3_sum", - back3, - # train 분포에서 빈도가 높은 값(97,105,106,114~117)을 포함해 out-of-sample 과도 탈락을 완화한다. - fallback_allowed=set([86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117]), - ): - if _fail(f"b4+b5+b6: {back3}"): - return filter_set - if not _allowed_abs_diff( - "back3_prev_diff", - abs(back3 - p_back3), - fallback_allowed=set([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,24,25,26,27,28,29,30,31]), - ): - if _fail(f"b4+b5+b6 전주차: {abs(back3 - p_back3)}"): - return filter_set - - # 5) 23 기준 저/고 개수 - low_cnt, high_cnt = self.getHigLowRate(ball) - if _enabled("high_low_min2", default=True): - if low_cnt in [0, 1] or high_cnt in [0, 1]: - if _fail(f"high/low: {low_cnt}/{high_cnt}"): - return filter_set - - # 6) 고저합 + 전주차 - minmax_sum = ball[0] + ball[5] - p_minmax_sum = p_ball[0] + p_ball[5] - if not _allowed_value("minmax_sum", minmax_sum, fallback_allowed=set(range(38, 58))): - if _fail("고저합: {}".format(minmax_sum)): - return filter_set - if not _allowed_abs_diff("minmax_prev_diff", abs(minmax_sum - p_minmax_sum), fallback_allowed=set(range(1, 16))): - if _fail("고저합 전주차: {}".format(abs(minmax_sum - p_minmax_sum))): - return filter_set - - # 7) 간격합 + 전주차 - interval_sum = self.get_ball_interval(ball) - p_interval_sum = self.get_ball_interval(p_ball) - - # (선택) sum_diff 값에 따라 interval_sum을 추가로 제한 (survivors 과다 회차 억제) - meta = self.ruleset.get("meta") or {} - try: - key = str(int(sum_diff)) - cond_allowed = meta.get("cond_interval_allowed_by_sumdiff") or {} - if key in cond_allowed: - allowed_list = cond_allowed.get(key) - if isinstance(allowed_list, list) and allowed_list: - if interval_sum not in set(int(x) for x in allowed_list): - if _fail(f"Interval_sum(cond sumdiff={sum_diff}): {interval_sum}"): - return filter_set - except Exception: - # cond 파싱 실패 시에는 무시(보수적으로 기존 로직 유지) - pass - - if not _allowed_value("interval_sum", interval_sum, fallback_allowed=set(range(27, 45))): - if _fail("Interval_sum: {}".format(interval_sum)): - return filter_set - if not _allowed_abs_diff("interval_prev_diff", abs(interval_sum - p_interval_sum), fallback_allowed=set(range(0, 18))): - if _fail("Interval_sum 전주차: {}".format(abs(interval_sum - p_interval_sum))): - return filter_set - - # 8) 첫/끝 자리합 + 전주차 - first_letter_sum = self.getFirstLetterSumBall(ball) - p_first_letter_sum = self.getFirstLetterSumBall(p_ball) - if not _allowed_value("first_digit_sum", first_letter_sum, fallback_allowed=set([8, 9, 10, 11, 12, 13, 14, 15])): - if _fail("첫수합: {}".format(first_letter_sum)): - return filter_set - if not _allowed_abs_diff("first_digit_prev_diff", abs(first_letter_sum - p_first_letter_sum), fallback_allowed=set([0, 1, 2, 3, 4, 5, 6])): - if _fail("첫수합 전주차: {}".format(abs(first_letter_sum - p_first_letter_sum))): - return filter_set - - last_letter_sum = self.getLastLetterSumBall(ball) - p_last_letter_sum = self.getLastLetterSumBall(p_ball) - if not _allowed_value("last_digit_sum", last_letter_sum, fallback_allowed=set([16,21,23,24,25,26,27,28,29,30,31,32,33,34,36,37,38])): - if _fail("끝수합: {}".format(last_letter_sum)): - return filter_set - if not _allowed_abs_diff("last_digit_prev_diff", abs(last_letter_sum - p_last_letter_sum), fallback_allowed=set(range(0, 15))): - if _fail("끝수합 전주차: {}".format(abs(last_letter_sum - p_last_letter_sum))): - return filter_set - - # 9) 첫수/마지막수 + 전주차 - if not _allowed_value("first_ball", ball[0], fallback_allowed=set([1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 14])): - if _fail("첫수: {}".format(ball[0])): - return filter_set - if not _allowed_abs_diff("first_ball_prev_diff", abs(ball[0] - p_ball[0]), fallback_allowed=set(range(0, 13))): - if _fail(f"전주와 첫수 차: {abs(ball[0] - p_ball[0])}"): - return filter_set - - if not _allowed_value("last_ball", ball[5], fallback_allowed=set([36, 38, 39, 40, 41, 42, 43, 44, 45])): - if _fail("마지막 공: {}".format(ball[5])): - return filter_set - if not _allowed_abs_diff("last_ball_prev_diff", abs(ball[5] - p_ball[5]), fallback_allowed=set(range(0, 10))): - if _fail("마지막 공: {}".format(abs(ball[5] - p_ball[5]))): - return filter_set - - # 10) 유니크 끝자리 + 전주차 - uniq_last = self.filterOneDigitPattern(ball) - p_uniq_last = self.filterOneDigitPattern(p_ball) - if not _allowed_value("uniq_last_digit_count", uniq_last, fallback_allowed={4, 5, 6}): - if _fail("Unique 끝수 개수: {}".format(uniq_last)): - return filter_set - # 완화: 전주차 대비 Unique 끝수 개수 diff=2도 허용 (요청사항) - if not _allowed_abs_diff("uniq_last_digit_prev_diff", abs(uniq_last - p_uniq_last), fallback_allowed={0, 1, 2}): - if _fail("Unique 끝수 개수 전주차: {}".format(abs(uniq_last - p_uniq_last))): - return filter_set - - # 11) AC + 전주차 - ac_val = self.getACValue(ball) - p_ac_val = self.getACValue(p_ball) - if not _allowed_value("ac_value", ac_val, fallback_allowed={7, 8, 9, 10}): - if _fail("ac: {}".format(ac_val)): - return filter_set - if not _allowed_abs_diff("ac_prev_diff", abs(ac_val - p_ac_val), fallback_allowed={0, 1, 2, 3}): - if _fail("ac 전주: {}".format(abs(ac_val - p_ac_val))): - return filter_set - - # 12) 배수 + 전주차 - multiples = [ - (3, {1, 2, 3}, {0, 1, 2}), - (4, {0, 1, 2}, {0, 1, 2}), - (5, {0, 1, 2}, {0, 1, 2}), - (6, {0, 1, 2}, {0, 1, 2}), - (7, {0, 1, 2}, {0, 1}), - (8, {0, 1}, {0, 1}), - (9, {0, 1, 2}, {0, 1}), - (10, {0, 1}, {0, 1}), - (11, {0, 1, 2}, {0, 1}), - (13, {0, 1}, {0, 1}), - (17, {0, 1}, {0, 1}), - (19, {0, 1}, {0, 1}), - (23, {0}, {0, 1}), - ] - for n_mul, allowed_cnt, allowed_diff in multiples: - name_cnt = f"mul_{n_mul}_count" - name_diff = f"mul_{n_mul}_prev_diff" - if not _enabled(name_cnt, default=True): - continue - cnt = len([b for b in ball if b % n_mul == 0]) - p_cnt = len([b for b in p_ball if b % n_mul == 0]) - # sum=152 케이스는 train 분포상 존재하며, 일부 out-of-sample에서 과도한 탈락을 유발할 수 있어 제한적으로 완화 - if not (sum6 == 152 and n_mul == 8 and cnt == 2) and not _allowed_value(name_cnt, cnt, fallback_allowed=allowed_cnt): - if _fail(f"{n_mul}의배수: {cnt}"): - return filter_set - if not (sum6 == 152 and n_mul == 6 and abs(cnt - p_cnt) == 2) and not _allowed_abs_diff(name_diff, abs(cnt - p_cnt), fallback_allowed=allowed_diff): - if _fail(f"{n_mul}의배수 전주차: {abs(cnt - p_cnt)}"): - return filter_set - - # 13) 소수/복소수 - if _enabled("prime_count", default=True): - pn = len(set(ball) & set(self.primeNumber)) - if not _allowed_value("prime_count", pn, fallback_allowed=set([1, 2, 3])): - if _fail("소수: {}".format(pn)): - return filter_set - if _enabled("composite_count", default=True): - cn = len(set(ball) & set(self.compositeNumber)) - p_cn = len(set(p_ball) & set(self.compositeNumber)) - if not _allowed_value("composite_count", cn, fallback_allowed=set([3, 4, 5])): - if _fail("복소수: {}".format(cn)): - return filter_set - if not _allowed_abs_diff("composite_prev_diff", abs(cn - p_cn), fallback_allowed=set([0, 1, 2, 3])): - if _fail("복소수 전주차: {}".format(abs(cn - p_cn))): - return filter_set - - # 14) 홀짝(짝수) - even_cnt = len([b for b in ball if b % 2 == 0]) - p_even_cnt = len([b for b in p_ball if b % 2 == 0]) - if not _allowed_value("even_count", even_cnt, fallback_allowed=set([2, 3, 4])): - if _fail("짝수 (0,2,4): {}".format(even_cnt)): - return filter_set - if not _allowed_abs_diff("even_prev_diff", abs(even_cnt - p_even_cnt), fallback_allowed=set([0, 1, 2])): - if _fail("짝수 (0,2,4) 전주차: {}".format(abs(even_cnt - p_even_cnt))): - return filter_set - - # 16) 전회차 수/좌우수 - if _enabled("previous_neighbors", default=True): - if self.filterPreviousNumber(ball, no): - if _fail("이전회차 수/좌우수"): - return filter_set - - # 17) 10구간 수 + 전주차 - section10 = self.getNumberOfAppearancesInSection10(ball) - p_section10 = self.getNumberOfAppearancesInSection10(p_ball) - if not _allowed_value("section10_count", section10, fallback_allowed={3, 4, 5}): - if _fail(f"같은 10구간대만 출현: {section10}"): - return filter_set - if not _allowed_abs_diff("section10_prev_diff", abs(section10 - p_section10), fallback_allowed={0, 1, 2}): - if _fail(f"같은 10구간대만 출현 전주차: {abs(section10 - p_section10)}"): - return filter_set - - # 18) 최근 N주 교집합 + 전주차 - weeks = [ - (8, {3, 4, 5, 6}, {0, 1, 2, 3}), - (12, {3, 4, 5, 6}, {0, 1, 2}), - (16, {4, 5, 6}, {0, 1}), - (20, {5, 6}, {0, 1}), - ] - for w, allowed_cnt, allowed_diff in weeks: - name_cnt = f"weeks_{w}_count" - name_diff = f"weeks_{w}_prev_diff" - if not _enabled(name_cnt, default=True): - continue - cnt = self.getWeeksFrequency(ball, df, no, week=w) - p_cnt = self.getWeeksFrequency(p_ball, df, no, week=w) - if not _allowed_value(name_cnt, cnt, fallback_allowed=allowed_cnt): - if _fail(f"{w} weeks"): - return filter_set - if not _allowed_abs_diff(name_diff, abs(cnt - p_cnt), fallback_allowed=allowed_diff): - if _fail(f"{w} weeks 전주차"): - return filter_set - - # 20) 이전 7회차 모두 포함 - if _enabled("all_in_previous7", default=True): - if self.filterAllPreivous7(ball, no): - if _fail("이전 17차"): - return filter_set - - # 21) 연속수 - if _enabled("max_continuous_len_3", default=True): - continous_ball = self.getContinusNumber(ball) - if 3 < continous_ball: - if _fail("연속볼"): - return filter_set - - return filter_set - - def filter(self, ball, no, until_end=False, df=None, filter_ball=None): - filter_type = self.extract_final_candidates(ball=ball, no=no, until_end=until_end, df=df) - - return filter_type \ No newline at end of file diff --git a/filter_model_3.py b/filter_model_3.py deleted file mode 100644 index a541ffe..0000000 --- a/filter_model_3.py +++ /dev/null @@ -1,94 +0,0 @@ -""" -filter_model_3.py - -OR-composed BallFilter: -- A candidate ball is ACCEPTED if it passes EITHER filter_model_1 OR filter_model_2. -- A candidate ball is REJECTED only if it fails BOTH. - -This keeps the same public interface used across the project: - BallFilter(lottoHistoryFileName, ruleset_path=..., ruleset=...) - .filter(ball, no, until_end=False, df=None, filter_ball=None) -> set[str] - .extract_final_candidates(ball, no=None, until_end=False, df=None) -> set[str] - -Notes: -- The underlying filters return a non-empty set of failure reasons when rejected. -- Callers treat "len(result) == 0" as PASS. -""" - -from __future__ import annotations - -from typing import Any, Dict, Optional - -import filter_model_1 as fm1 -import filter_model_2 as fm2 - - -class BallFilter: - """ - OR composition of filter_model_1.BallFilter and filter_model_2.BallFilter. - - - If model1 PASSES OR model2 PASSES -> return empty set() - - If both FAIL -> return union of reasons (prefixed for debugging) - """ - - def __init__( - self, - lottoHistoryFileName: Optional[str] = None, - # Backward compatible single ruleset knobs (applied to both if specific ones not provided) - ruleset_path: Optional[str] = None, - ruleset: Optional[Dict[str, Any]] = None, - # Optional per-model overrides - ruleset_path_1: Optional[str] = None, - ruleset_path_2: Optional[str] = None, - ruleset_1: Optional[Dict[str, Any]] = None, - ruleset_2: Optional[Dict[str, Any]] = None, - ): - rp1 = ruleset_path_1 if ruleset_path_1 is not None else ruleset_path - rp2 = ruleset_path_2 if ruleset_path_2 is not None else ruleset_path - r1 = ruleset_1 if ruleset_1 is not None else ruleset - r2 = ruleset_2 if ruleset_2 is not None else ruleset - - self.m1 = fm1.BallFilter(lottoHistoryFileName, ruleset_path=rp1, ruleset=r1) - self.m2 = fm2.BallFilter(lottoHistoryFileName, ruleset_path=rp2, ruleset=r2) - - # - # Delegate common helper methods (both models expose the same API) - # - def getBall(self, no): - return self.m1.getBall(no) - - def getLastNo(self, YMD): - return self.m1.getLastNo(YMD) - - def getNextNo(self, YMD): - return self.m1.getNextNo(YMD) - - def getYMD(self, no): - return self.m1.getYMD(no) - - def _prefixed(self, prefix: str, reasons: set) -> set: - # keep stable, readable debug strings - return {f"{prefix}{r}" for r in reasons} - - def extract_final_candidates(self, ball, no=None, until_end: bool = False, df=None): - """ - OR-pass semantics: - - If either model returns empty set -> PASS (return empty set) - - Else -> FAIL (return union of reasons) - """ - r1 = self.m1.extract_final_candidates(ball=ball, no=no, until_end=until_end, df=df) - if len(r1) == 0: - return set() - r2 = self.m2.extract_final_candidates(ball=ball, no=no, until_end=until_end, df=df) - if len(r2) == 0: - return set() - # both failed - return self._prefixed("m1:", set(r1)) | self._prefixed("m2:", set(r2)) - - def filter(self, ball, no, until_end: bool = False, df=None, filter_ball=None): - """ - Keep signature compatible with existing callers. - - filter_ball is ignored here (callers typically pre-filter before calling .filter()). - """ - return self.extract_final_candidates(ball=ball, no=no, until_end=until_end, df=df) - diff --git a/practice_0.py b/practice_0.py deleted file mode 100644 index afd2d8c..0000000 --- a/practice_0.py +++ /dev/null @@ -1,1081 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -로또 6/45: 1~N회차(기본 1208) 과거 1등(6개 번호) 기록을 기반으로, -다음 회차(기본 1209)에 대해 “점수 기반 확률(추정)”이 높은 순서대로 상위 K(기본 20) 단일 번호를 추천한다. - -⚠️ 중요 고지(프로그램 출력에도 포함): -- 로또는 원칙적으로 독립·무작위 추첨이므로 과거 데이터로 실제 당첨확률을 증가시킨다고 보장할 수 없다. -- 본 프로그램의 “확률”은 통계적 추정/휴리스틱 점수이며, 실험/학습 목적이다. - -요구사항 핵심(요약): -- 입력: resources/lotto_history.txt / resources/lotto_history.json (둘 중 하나만 있어도 동작, 둘 다 있으면 교차검증 후 통합) -- 회차별 6개 번호(1~45, 중복 없음)만 확실히 추출하면 됨(보너스 번호는 무시) -- 피처 A: 베타-이항 posterior_mean -- 피처 B: 최근성 가중 출현율(EWMA; 지수감쇠) -- 피처 C: 마지막 출현 이후 갭 기반 약한 페널티(“안 나온지 오래”를 유리하게 가정하지 않도록 중립 수렴) -- 피처 D: 스케일링(z-score) 후 가중합 점수 -- 롤링 백테스트 그리드서치로 (wA,wB,wC), half_life, gscale 튜닝 -- 출력: 표 + JSON - -설계/구현 계획(의사코드): -1) 데이터 로드 - - txt/json 각각 로드 시도(없으면 None) - - txt: 정규식으로 정수 추출 → (회차, 번호6) 구성(보너스 등 추가 숫자는 무시/방어) - - json: 가능한 경우 (drwNo, drwtNo1..6) 우선, 아니면 numbers 배열, 그래도 아니면 방어적으로 숫자 추출 -2) 정규화/정합성 - - normalize_draw(numbers): 1..45 범위만, 중복 제거, 6개면 정렬 후 채택 - - 회차 중복/누락/이상치 로그 - - 둘 다 있으면 동일 회차 교차검증(번호 불일치 개수 경고), 충돌 시 우선순위(더 많이/더 신뢰되는 쪽)로 통합 -3) 행렬화 - - 회차 오름차순 정렬된 draws를 만들고, X[t,i]=번호(i+1)가 t회차에 포함되면 1 (t=0..N-1) -4) 피처 계산(훈련 길이 t에 대해) - - A: posterior_mean = (count + alpha) / (t + alpha + beta) - - B: EWMA: - r = exp(-ln2/half_life) - numer_t = sum_{k=0..t-1} r^k * x_{t-1-k} - denom_t = sum_{k=0..t-1} r^k - ewma_freq = numer_t/denom_t - - C: gap_score: - gap = current_draw_no - last_seen_draw_no (없으면 큰 값) - gap_score = -exp(-gap/gscale) # gap 작을수록(최근 출현) 페널티 강함, gap 커질수록 0으로 중립 수렴 - - D: z-score로 A,B,C 각각 스케일링 후 score = wA*zA + wB*zB + wC*zC -5) 백테스트 & 튜닝(롤링) - - warmup..(N-1)까지: train[0:t)로 score 산출 → topK 번호 추천 → 실제 draw[t]와 교집합(hit) 계산 - - 그리드: - weights: step 0.1, 합=1 - half_life: [50,100,150,200,300,400] (+사용자 입력 포함) - gscale: [10,20,30,50] (+사용자 입력 포함) - - 지표: - overall mean hit@K, overall P(hit>=1) - recent window(마지막 valid_last 예측) mean hit@K, P(hit>=1) - objective = 0.7*recent_mean_hit + 0.3*overall_mean_hit (타이브레이크로 hit>=1) -6) 최종 추천 - - best params로 전체(1..N) 학습 후 next_draw=N+1 추천 topK 출력(표+JSON) -""" - -from __future__ import annotations - -import argparse -import json -import math -import os -import re -import sys -from dataclasses import dataclass -from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple - - -try: - import numpy as np # type: ignore -except Exception: - np = None # type: ignore - - -NUM_MIN = 1 -NUM_MAX = 45 -NUM_COUNT = 45 - - -def eprint(*args: Any, **kwargs: Any) -> None: - print(*args, file=sys.stderr, **kwargs) - - -def normalize_draw(numbers: Sequence[int]) -> Optional[Tuple[int, ...]]: - """번호 리스트에서 1..45 범위의 중복 없는 6개를 추출/정규화(정렬)한다.""" - clean = [int(x) for x in numbers if NUM_MIN <= int(x) <= NUM_MAX] - uniq = sorted(set(clean)) - if len(uniq) != 6: - return None - return tuple(uniq) - - -def _extract_ints(s: str) -> List[int]: - return [int(x) for x in re.findall(r"\d+", s)] - - -def load_history_txt(path: str) -> Dict[int, Tuple[int, ...]]: - """ - txt는 포맷이 확정되지 않았으므로 방어적으로 파싱한다. - - 기본: 라인별로 정수 추출 후, 첫 번째 정수를 회차로 보고 이후 숫자 중 6개를 번호로 구성(보너스 등 추가는 무시) - - 실패 시: 전체 정수 스트림을 블록(8,7) 단위로 재시도 - """ - if not os.path.exists(path): - raise FileNotFoundError(path) - - by_round: Dict[int, Tuple[int, ...]] = {} - bad_lines = 0 - - with open(path, "r", encoding="utf-8", errors="ignore") as f: - lines = f.readlines() - - for line in lines: - ints = _extract_ints(line) - if len(ints) < 7: - continue - rnd = ints[0] - rest = ints[1:] - # 흔한 케이스: 회차 + 6개 + 보너스(7개) - candidates = [] - if len(rest) >= 6: - candidates.append(rest[:6]) - candidates.append(rest[-6:]) - candidates.append(list(dict.fromkeys([x for x in rest if NUM_MIN <= x <= NUM_MAX]))[:6]) - draw = None - for cand in candidates: - draw = normalize_draw(cand) - if draw is not None: - break - if draw is None: - bad_lines += 1 - continue - if rnd in by_round and by_round[rnd] != draw: - eprint(f"[WARN][txt] 회차 {rnd} 중복/불일치: {by_round[rnd]} vs {draw} (첫 값 유지)") - continue - by_round[rnd] = draw - - # 라인 기반 파싱이 거의 실패하면 전체 스트림 기반으로 복구 시도 - if len(by_round) < max(50, len(lines) // 10): - ints_all: List[int] = [] - for line in lines: - ints_all.extend(_extract_ints(line)) - - def try_block(block_size: int) -> Dict[int, Tuple[int, ...]]: - out: Dict[int, Tuple[int, ...]] = {} - if block_size <= 0 or len(ints_all) < block_size: - return out - if len(ints_all) % block_size != 0: - # 나머지가 있어도 앞부분만 시도 - limit = len(ints_all) - (len(ints_all) % block_size) - else: - limit = len(ints_all) - ok = 0 - for i in range(0, limit, block_size): - block = ints_all[i : i + block_size] - if len(block) < 7: - continue - rnd = block[0] - rest = block[1:] - if len(rest) < 6: - continue - draw = normalize_draw(rest[:6]) - if draw is None: - continue - out[rnd] = draw - ok += 1 - return out - - recovered8 = try_block(8) # (회차 + 6 + 보너스) 가능성 - recovered7 = try_block(7) # (회차 + 6) 가능성 - recovered = recovered8 if len(recovered8) >= len(recovered7) else recovered7 - if len(recovered) > len(by_round): - eprint(f"[INFO][txt] 라인 파싱이 약함 → 스트림 기반 복구 사용: {len(recovered)}회차") - by_round = recovered - - if bad_lines: - eprint(f"[WARN][txt] 파싱 실패 라인 수: {bad_lines}") - - return by_round - - -def _coerce_json_obj_to_round_and_numbers(obj: Any) -> Optional[Tuple[int, List[int]]]: - """JSON 오브젝트에서 (회차, 번호후보들)을 최대한 방어적으로 추출한다.""" - if isinstance(obj, dict): - # 1) 대표 구조: drwNo + drwtNo1..6 - if "drwNo" in obj: - try: - rnd = int(obj["drwNo"]) - except Exception: - rnd = None # type: ignore - nums: List[int] = [] - for k in ("drwtNo1", "drwtNo2", "drwtNo3", "drwtNo4", "drwtNo5", "drwtNo6"): - if k in obj: - try: - nums.append(int(obj[k])) - except Exception: - pass - if rnd is not None and len(nums) >= 6: - return rnd, nums[:6] - - # 2) 다른 흔한 구조: (round, numbers) - for rk in ("round", "draw", "drawNo", "no", "id"): - if rk in obj: - try: - rnd = int(obj[rk]) - except Exception: - continue - nums = [] - if "numbers" in obj and isinstance(obj["numbers"], (list, tuple)): - for x in obj["numbers"]: - try: - nums.append(int(x)) - except Exception: - pass - # dict 내부에 숫자들이 흩어져 있어도 방어적으로 수집 - if len(nums) < 6: - nums = [] - for v in obj.values(): - if isinstance(v, (int, float)) and NUM_MIN <= int(v) <= NUM_MAX: - nums.append(int(v)) - if len(nums) >= 6: - return rnd, nums[:6] - - # 3) 최후: dict를 문자열로 만들고 숫자 추출(회차/번호 분리 어려움 → 실패 처리) - return None - - if isinstance(obj, (list, tuple)): - # list 자체가 [회차, n1..] 형태일 수도 - ints = [] - for x in obj: - if isinstance(x, (int, float)): - ints.append(int(x)) - elif isinstance(x, str): - ints.extend(_extract_ints(x)) - if len(ints) >= 7: - return ints[0], ints[1:7] - - return None - - -def load_history_json(path: str) -> Dict[int, Tuple[int, ...]]: - """ - json은 다음을 우선 시도: - - 라인별 JSON(dict) 파싱 (현재 리소스는 라인-델리미티드 dict 형태) - - 전체 파일 JSON 파싱 (array/object)도 방어적으로 처리 - """ - if not os.path.exists(path): - raise FileNotFoundError(path) - - by_round: Dict[int, Tuple[int, ...]] = {} - bad = 0 - - def ingest_obj(obj: Any) -> None: - nonlocal bad, by_round - got = _coerce_json_obj_to_round_and_numbers(obj) - if not got: - bad += 1 - return - rnd, nums = got - draw = normalize_draw(nums) - if draw is None: - bad += 1 - return - if rnd in by_round and by_round[rnd] != draw: - eprint(f"[WARN][json] 회차 {rnd} 중복/불일치: {by_round[rnd]} vs {draw} (첫 값 유지)") - return - by_round[rnd] = draw - - with open(path, "r", encoding="utf-8", errors="ignore") as f: - text = f.read() - - # 1) 라인별 JSON 우선 (여기선 거의 확실) - lines = [ln.strip() for ln in text.splitlines() if ln.strip()] - line_success = 0 - for ln in lines: - # 단일 JSON object 라인일 가능성 - if not (ln.startswith("{") and ln.endswith("}")): - continue - try: - obj = json.loads(ln) - ingest_obj(obj) - line_success += 1 - except Exception: - continue - - # 2) 라인 방식이 거의 실패하면 전체 JSON 파싱 - if line_success < max(50, len(lines) // 10): - try: - obj = json.loads(text) - if isinstance(obj, list): - for item in obj: - ingest_obj(item) - elif isinstance(obj, dict): - # dict 안에 list가 들어있을 수도 - if "data" in obj and isinstance(obj["data"], list): - for item in obj["data"]: - ingest_obj(item) - else: - # 값들 중 dict/list를 훑기 - for v in obj.values(): - if isinstance(v, (list, dict)): - ingest_obj(v) - except Exception: - pass - - if bad: - eprint(f"[WARN][json] 파싱 실패/무시 레코드 수(추정): {bad}") - - return by_round - - -def cross_validate_and_merge( - txt_data: Optional[Dict[int, Tuple[int, ...]]], - json_data: Optional[Dict[int, Tuple[int, ...]]], -) -> Dict[int, Tuple[int, ...]]: - """둘 다 있을 경우 교차검증 후 통합. 충돌 시 더 많은 레코드를 가진 쪽을 우선.""" - if not txt_data and not json_data: - return {} - if txt_data and not json_data: - return dict(txt_data) - if json_data and not txt_data: - return dict(json_data) - - assert txt_data is not None and json_data is not None - - common = sorted(set(txt_data) & set(json_data)) - mismatch = 0 - for rnd in common: - if txt_data[rnd] != json_data[rnd]: - mismatch += 1 - if mismatch <= 20: - eprint(f"[WARN] 교차검증 불일치 회차 {rnd}: txt={txt_data[rnd]} json={json_data[rnd]}") - if mismatch: - eprint(f"[WARN] 교차검증: 공통 {len(common)}개 중 불일치 {mismatch}개") - else: - eprint(f"[INFO] 교차검증: 공통 {len(common)}개 모두 일치") - - base = json_data if len(json_data) >= len(txt_data) else txt_data - other = txt_data if base is json_data else json_data - merged: Dict[int, Tuple[int, ...]] = dict(base) - - added = 0 - conflict = 0 - for rnd, draw in other.items(): - if rnd not in merged: - merged[rnd] = draw - added += 1 - else: - if merged[rnd] != draw: - conflict += 1 - # 충돌은 base 유지 - if added: - eprint(f"[INFO] 통합: 누락 회차 추가 {added}개") - if conflict: - eprint(f"[WARN] 통합: 충돌 {conflict}개(우선 데이터 유지)") - - return merged - - -def sanitize_and_sort_draws( - merged: Dict[int, Tuple[int, ...]], - min_round: int = 1, - max_round: Optional[int] = None, -) -> List[Tuple[int, Tuple[int, ...]]]: - """회차 정렬 + 범위/이상치 제거.""" - items = [] - dropped = 0 - for rnd, draw in merged.items(): - if rnd < min_round: - dropped += 1 - continue - if max_round is not None and rnd > max_round: - dropped += 1 - continue - if draw is None or len(draw) != 6: - dropped += 1 - continue - if any((x < NUM_MIN or x > NUM_MAX) for x in draw) or len(set(draw)) != 6: - dropped += 1 - continue - items.append((int(rnd), tuple(sorted(draw)))) - items.sort(key=lambda x: x[0]) - if dropped: - eprint(f"[WARN] 이상치/범위 밖/형식 불량 제거: {dropped}개") - return items - - -def build_indicator_matrix(draws: List[Tuple[int, Tuple[int, ...]]]) -> Tuple["np.ndarray", "np.ndarray"]: - """draws -> (draw_nos, X) where X[t,i] in {0,1} for number i+1.""" - if np is None: - raise RuntimeError("numpy가 필요합니다(성능/구현 단순화를 위해). requirements.txt를 확인하세요.") - draw_nos = np.array([rnd for rnd, _ in draws], dtype=np.int32) - X = np.zeros((len(draws), NUM_COUNT), dtype=np.int8) - for t, (_, nums) in enumerate(draws): - for n in nums: - X[t, n - 1] = 1 - return draw_nos, X - - -def zscore(x: "np.ndarray", eps: float = 1e-12) -> "np.ndarray": - mu = float(np.mean(x)) - sd = float(np.std(x)) - if sd < eps: - return x * 0.0 - return (x - mu) / sd - - -def zscore_rowwise(M: "np.ndarray", eps: float = 1e-12) -> "np.ndarray": - """행(row) 단위 z-score. M: (T,45) -> (T,45)""" - mu = np.mean(M, axis=1, keepdims=True) - sd = np.std(M, axis=1, keepdims=True) - out = (M - mu).astype(np.float64, copy=False) - # np.where는 양쪽 branch를 평가해서 RuntimeWarning이 날 수 있으므로 np.divide(where=...) 사용 - z = np.zeros_like(out, dtype=np.float64) - np.divide(out, sd, out=z, where=sd >= eps) - return z - - -@dataclass(frozen=True) -class ScoringParams: - alpha: float = 1.0 - beta: float = 1.0 - half_life: int = 200 - gscale: float = 20.0 - wA: float = 0.5 - wB: float = 0.4 - wC: float = 0.1 - - def normalized_weights(self) -> "ScoringParams": - s = self.wA + self.wB + self.wC - if s <= 0: - return self - return ScoringParams( - alpha=self.alpha, - beta=self.beta, - half_life=self.half_life, - gscale=self.gscale, - wA=self.wA / s, - wB=self.wB / s, - wC=self.wC / s, - ) - - -def precompute_last_seen(X: "np.ndarray") -> "np.ndarray": - """last_seen_end[t,i] = t개 훈련(0..t-1 포함) 이후, 번호 i의 마지막 등장 index (없으면 -1). shape (N+1,45).""" - N = X.shape[0] - last_seen_end = np.full((N + 1, NUM_COUNT), -1, dtype=np.int32) - last = np.full((NUM_COUNT,), -1, dtype=np.int32) - last_seen_end[0] = last - for idx in range(N): - hits = np.nonzero(X[idx])[0] - if hits.size: - last[hits] = idx - # idx번째 draw까지 포함한 상태가 train_size=idx+1 - last_seen_end[idx + 1] = last - return last_seen_end - - -def precompute_ewma_numer_denoms(X: "np.ndarray", half_life: int) -> Tuple["np.ndarray", "np.ndarray"]: - """ - EWMA numerator/denominator를 훈련 길이별로 미리 계산. - - numer_end[t,i] = t개 훈련(0..t-1)의 weighted sum - - denom_end[t] = sum_{k=0..t-1} r^k - """ - N = X.shape[0] - hl = max(1, int(half_life)) - lam = math.log(2.0) / float(hl) - r = math.exp(-lam) # per-step decay - numer_end = np.zeros((N + 1, NUM_COUNT), dtype=np.float64) - denom_end = np.zeros((N + 1,), dtype=np.float64) - for t in range(1, N + 1): - numer_end[t] = X[t - 1].astype(np.float64) + r * numer_end[t - 1] - denom_end[t] = 1.0 + r * denom_end[t - 1] - denom_end[0] = 0.0 - return numer_end, denom_end - - -def compute_features_at_train_size( - *, - train_size: int, - draw_nos: "np.ndarray", - X: "np.ndarray", - cum_counts: "np.ndarray", - last_seen_end: "np.ndarray", - ewma_numer_end: "np.ndarray", - ewma_denom_end: "np.ndarray", - params: ScoringParams, -) -> Dict[str, "np.ndarray"]: - """ - train_size=t인 시점의 피처를 45개 번호에 대해 계산. - 반환: - posterior_mean (A), ewma_freq (B), gap (raw), gap_score (C raw), zA,zB,zC, score - """ - t = int(train_size) - if t <= 0: - raise ValueError("train_size must be >= 1") - # counts over first t draws - counts = cum_counts[t - 1].astype(np.float64) - - alpha = float(params.alpha) - beta = float(params.beta) - posterior_mean = (counts + alpha) / (float(t) + alpha + beta) - - denom = float(ewma_denom_end[t]) - if denom <= 0: - ewma_freq = np.zeros((NUM_COUNT,), dtype=np.float64) - else: - ewma_freq = ewma_numer_end[t] / denom - - # gap based on draw numbers (회차) to match "마지막 출현 이후 회차 수" - current_draw_no = int(draw_nos[t - 1]) - last_idx = last_seen_end[t].astype(np.int32) - gap = np.zeros((NUM_COUNT,), dtype=np.float64) - seen_mask = last_idx >= 0 - if np.any(seen_mask): - last_draw = draw_nos[last_idx.clip(min=0)] - gap[seen_mask] = (current_draw_no - last_draw[seen_mask]).astype(np.float64) - # never seen -> large gap (중립 수렴용) - gap[~seen_mask] = float(current_draw_no + 999) - - gscale = float(params.gscale) - if gscale <= 0: - gscale = 20.0 - # 최근(갭 짧음)일수록 -1에 가깝게, 오래되면 0으로 수렴 (U자 가정 금지) - gap_score = -np.exp(-gap / gscale) - - zA = zscore(posterior_mean) - zB = zscore(ewma_freq) - zC = zscore(gap_score) - w = params.normalized_weights() - score = w.wA * zA + w.wB * zB + w.wC * zC - - return { - "posterior_mean": posterior_mean, - "ewma_freq": ewma_freq, - "gap": gap, - "gap_score": gap_score, - "zA": zA, - "zB": zB, - "zC": zC, - "score": score, - } - - -@dataclass -class BacktestResult: - params: ScoringParams - topk: int - warmup: int - n_eval: int - overall_mean_hit: float - overall_hit1_rate: float - recent_mean_hit: float - recent_hit1_rate: float - objective: float - - -def _iter_weight_grid(step: float = 0.1) -> Iterable[Tuple[float, float, float]]: - # wA, wB in [0..1], wC=1-wA-wB, step grid - s = float(step) - if s <= 0: - s = 0.1 - # to avoid float drift, operate on integer ticks - ticks = int(round(1.0 / s)) - for a in range(ticks + 1): - for b in range(ticks + 1 - a): - c = ticks - a - b - yield a / ticks, b / ticks, c / ticks - - -def backtest_and_tune( - draws: List[Tuple[int, Tuple[int, ...]]], - *, - topk: int = 20, - warmup: int = 300, - valid_last: int = 200, - alpha: float = 1.0, - beta: float = 1.0, - half_life_grid: Sequence[int] = (50, 100, 150, 200, 300, 400), - gscale_grid: Sequence[float] = (10.0, 20.0, 30.0, 50.0), - weight_step: float = 0.1, -) -> BacktestResult: - if np is None: - raise RuntimeError("numpy가 필요합니다.") - if len(draws) < warmup + 2: - raise ValueError(f"데이터가 너무 적습니다: {len(draws)}회차 (warmup={warmup})") - - draw_nos, X = build_indicator_matrix(draws) - N = int(X.shape[0]) - topk = max(1, min(int(topk), NUM_COUNT)) - - # 누적 카운트 (N,45) - cum_counts = np.cumsum(X, axis=0, dtype=np.int32) - # counts_end[t] = 0..t-1까지의 카운트 (shape N+1,45) - counts_end = np.zeros((N + 1, NUM_COUNT), dtype=np.int32) - counts_end[1:] = cum_counts - - last_seen_end = precompute_last_seen(X) - - # gap_end[t,i] = train_size=t 시점의 갭(회차 기준). (shape N+1,45) - gap_end = np.zeros((N + 1, NUM_COUNT), dtype=np.float64) - gap_end[0] = 0.0 - for t in range(1, N + 1): - current_draw_no = int(draw_nos[t - 1]) - last_idx = last_seen_end[t] - seen = last_idx >= 0 - gap = np.empty((NUM_COUNT,), dtype=np.float64) - if np.any(seen): - gap[seen] = (current_draw_no - draw_nos[last_idx[seen]]).astype(np.float64) - gap[~seen] = float(current_draw_no + 999) - gap_end[t] = gap - - # 평가 구간: train_size=t로 target index=t 예측 - start_t = max(1, int(warmup)) - end_t = N - 1 - n_eval = end_t - start_t + 1 - if n_eval <= 0: - raise ValueError("평가 구간이 비었습니다. warmup을 줄이세요.") - - recent_len = max(1, min(int(valid_last), n_eval)) - recent_start_t = end_t - recent_len + 1 - - # 실제 정답 마스크 - actual_mask = X.astype(np.int8) - - # 그리드 정리 - hl_list = sorted(set(int(x) for x in half_life_grid if int(x) > 0)) - gs_list = sorted(set(float(x) for x in gscale_grid if float(x) > 0)) - weight_list = list(_iter_weight_grid(step=weight_step)) - W = np.array(weight_list, dtype=np.float64).T # (3,M) - M = int(W.shape[1]) - - eprint( - f"[INFO] 튜닝 시작(가속): weights={M} half_life={len(hl_list)} gscale={len(gs_list)} " - f"→ 총 {M*len(hl_list)*len(gs_list)} 조합, eval={n_eval} (recent={recent_len})" - ) - - # A는 half_life/gscale과 무관 (alpha,beta 고정) - t_vec = np.arange(N + 1, dtype=np.float64).reshape(-1, 1) # (N+1,1) - A_end = (counts_end.astype(np.float64) + float(alpha)) / (t_vec + float(alpha) + float(beta)) - zA_end = zscore_rowwise(A_end) - - best: Optional[BacktestResult] = None - - nums = np.arange(1, NUM_COUNT + 1, dtype=np.int32) # tie-break key - - for hl in hl_list: - ewma_numer_end, ewma_denom_end = precompute_ewma_numer_denoms(X, hl) - denom = ewma_denom_end.reshape(-1, 1) - B_end = np.zeros_like(ewma_numer_end, dtype=np.float64) - np.divide(ewma_numer_end, denom, out=B_end, where=denom > 0) - zB_end = zscore_rowwise(B_end) - - for gs in gs_list: - gap_score_end = -np.exp(-gap_end / float(gs)) - zC_end = zscore_rowwise(gap_score_end) - - # 누적 hit/hit1 (M개 weight에 대해 벡터) - total_hit = np.zeros((M,), dtype=np.int32) - total_hit1 = np.zeros((M,), dtype=np.int32) - recent_hit = np.zeros((M,), dtype=np.int32) - recent_hit1 = np.zeros((M,), dtype=np.int32) - - for t in range(start_t, end_t + 1): - # z-features at train_size=t : (45,3) - Z = np.stack([zA_end[t], zB_end[t], zC_end[t]], axis=1) # (45,3) - scores = Z @ W # (45,M) - - # topK indices per column (order not needed for hit 계산) - picks = np.argpartition(-scores, topk - 1, axis=0)[:topk, :] # (topk,M) - hits = actual_mask[t][picks].sum(axis=0).astype(np.int32) # (M,) - - total_hit += hits - total_hit1 += (hits >= 1).astype(np.int32) - if t >= recent_start_t: - recent_hit += hits - recent_hit1 += (hits >= 1).astype(np.int32) - - overall_mean_hit = total_hit.astype(np.float64) / float(n_eval) - overall_hit1_rate = total_hit1.astype(np.float64) / float(n_eval) - recent_mean_hit = recent_hit.astype(np.float64) / float(recent_len) - recent_hit1_rate = recent_hit1.astype(np.float64) / float(recent_len) - objective = 0.7 * recent_mean_hit + 0.3 * overall_mean_hit - - # (hl,gs)에서 best weight 선택: objective, recent_hit1, overall_mean, overall_hit1 - # numpy.lexsort는 마지막 키가 1순위(오름차순) → 음수로 내림차순 효과 - order = np.lexsort( - ( - -overall_hit1_rate, - -overall_mean_hit, - -recent_hit1_rate, - -objective, - ) - ) - best_w_idx = int(order[0]) - wA, wB, wC = float(W[0, best_w_idx]), float(W[1, best_w_idx]), float(W[2, best_w_idx]) - params = ScoringParams(alpha=alpha, beta=beta, half_life=hl, gscale=gs, wA=wA, wB=wB, wC=wC).normalized_weights() - - cand = BacktestResult( - params=params, - topk=topk, - warmup=warmup, - n_eval=n_eval, - overall_mean_hit=float(overall_mean_hit[best_w_idx]), - overall_hit1_rate=float(overall_hit1_rate[best_w_idx]), - recent_mean_hit=float(recent_mean_hit[best_w_idx]), - recent_hit1_rate=float(recent_hit1_rate[best_w_idx]), - objective=float(objective[best_w_idx]), - ) - - if best is None: - best = cand - else: - if (cand.objective > best.objective + 1e-12) or ( - abs(cand.objective - best.objective) <= 1e-12 - and ( - cand.recent_hit1_rate > best.recent_hit1_rate + 1e-12 - or ( - abs(cand.recent_hit1_rate - best.recent_hit1_rate) <= 1e-12 - and ( - cand.overall_mean_hit > best.overall_mean_hit + 1e-12 - or ( - abs(cand.overall_mean_hit - best.overall_mean_hit) <= 1e-12 - and cand.overall_hit1_rate > best.overall_hit1_rate + 1e-12 - ) - ) - ) - ) - ): - best = cand - - assert best is not None - return best - - -def _reason_string(zA: float, zB: float, gap: float, gap_score: float, params: ScoringParams) -> str: - parts: List[str] = [] - if zB >= 0.7: - parts.append("최근성↑") - elif zB <= -0.7: - parts.append("최근성↓") - if zA >= 0.7: - parts.append("장기빈도↑") - elif zA <= -0.7: - parts.append("장기빈도↓") - # gap_score는 음수(페널티), 0에 가까울수록 중립 - if gap_score <= -0.7: - parts.append("최근출현페널티↑") - elif gap_score >= -0.1: - parts.append("갭영향↓(중립)") - else: - parts.append("갭영향(약)") - # 숫자로 한 번 더 - parts.append(f"gap={int(round(gap))}") - return ", ".join(parts) - - -def recommend( - draws: List[Tuple[int, Tuple[int, ...]]], - *, - best: BacktestResult, - target_draw_no: int, - topk: int, -) -> Tuple[List[Dict[str, Any]], Dict[str, Any]]: - if np is None: - raise RuntimeError("numpy가 필요합니다.") - - draw_nos, X = build_indicator_matrix(draws) - N = int(X.shape[0]) - cum_counts = np.cumsum(X, axis=0, dtype=np.int32) - last_seen_end = precompute_last_seen(X) - ewma_numer_end, ewma_denom_end = precompute_ewma_numer_denoms(X, best.params.half_life) - - feats = compute_features_at_train_size( - train_size=N, - draw_nos=draw_nos, - X=X, - cum_counts=cum_counts, - last_seen_end=last_seen_end, - ewma_numer_end=ewma_numer_end, - ewma_denom_end=ewma_denom_end, - params=best.params, - ) - score = feats["score"] - nums = np.arange(1, NUM_COUNT + 1, dtype=np.int32) - order = np.lexsort((nums, -score)) - order = order[:topk] - - recs: List[Dict[str, Any]] = [] - for rank, idx in enumerate(order, start=1): - n = int(idx + 1) - rec = { - "rank": rank, - "number": n, - "score": float(score[idx]), - "posterior_mean": float(feats["posterior_mean"][idx]), - "ewma_freq": float(feats["ewma_freq"][idx]), - "gap": float(feats["gap"][idx]), - "reason": _reason_string( - float(feats["zA"][idx]), - float(feats["zB"][idx]), - float(feats["gap"][idx]), - float(feats["gap_score"][idx]), - best.params, - ), - "feature_z": { - "A": float(feats["zA"][idx]), - "B": float(feats["zB"][idx]), - "C": float(feats["zC"][idx]), - }, - "weights": {"wA": best.params.wA, "wB": best.params.wB, "wC": best.params.wC}, - } - recs.append(rec) - - out_json = { - "draw": int(target_draw_no), - "topk": int(topk), - "disclaimer": [ - "로또는 원칙적으로 독립·무작위 추첨이므로 과거 데이터로 실제 당첨확률을 증가시킨다고 보장할 수 없습니다.", - "본 프로그램의 '확률'은 통계적 추정/휴리스틱 점수이며, 실험/학습 목적입니다.", - ], - "best_params": { - "alpha": best.params.alpha, - "beta": best.params.beta, - "half_life": best.params.half_life, - "gscale": best.params.gscale, - "wA": best.params.wA, - "wB": best.params.wB, - "wC": best.params.wC, - }, - "backtest": { - "warmup": best.warmup, - "n_eval": best.n_eval, - "overall_mean_hit": best.overall_mean_hit, - "overall_hit>=1_rate": best.overall_hit1_rate, - "recent_mean_hit": best.recent_mean_hit, - "recent_hit>=1_rate": best.recent_hit1_rate, - "objective": best.objective, - }, - "recommendations": recs, - } - return recs, out_json - - -def print_table(recs: List[Dict[str, Any]]) -> None: - # 콘솔 표: rank, number, score, posterior_mean, ewma_freq, last_gap, reason - headers = ["rank", "number", "score", "posterior_mean", "ewma_freq", "gap", "reason"] - rows = [] - for r in recs: - rows.append([ - str(r["rank"]), - str(r["number"]), - f'{r["score"]:+.6f}', - f'{r["posterior_mean"]:.6f}', - f'{r["ewma_freq"]:.6f}', - f'{r["gap"]:.0f}', - str(r["reason"]), - ]) - widths = [len(h) for h in headers] - for row in rows: - for i, cell in enumerate(row): - widths[i] = max(widths[i], len(cell)) - - def fmt_row(row: Sequence[str]) -> str: - return " | ".join(row[i].ljust(widths[i]) for i in range(len(headers))) - - sep = "-+-".join("-" * w for w in widths) - print(fmt_row(headers)) - print(sep) - for row in rows: - print(fmt_row(row)) - - -def main() -> None: - parser = argparse.ArgumentParser(description="로또 6/45 점수 기반(휴리스틱) 단일 번호 TopK 추천") - parser.add_argument("--topk", type=int, default=20, help="추천할 단일 번호 개수 (기본 20)") - parser.add_argument("--half_life", type=int, default=200, help="EWMA half-life (튜닝 그리드에 포함, 기본 200)") - parser.add_argument("--seed", type=int, default=42, help="재현성용 seed (현재는 타이브레이크에 사용하지 않음, 기본 42)") - parser.add_argument("--warmup", type=int, default=300, help="롤링 검증 워밍업(학습 최소 회차 수), 기본 300") - parser.add_argument("--valid_last", type=int, default=200, help="최근 성능 가중 평가에 쓰는 마지막 예측 개수, 기본 200") - parser.add_argument("--alpha", type=float, default=1.0, help="Beta prior alpha (기본 1)") - parser.add_argument("--beta", type=float, default=1.0, help="Beta prior beta (기본 1)") - parser.add_argument( - "--prior", - type=str, - default="uniform", - choices=["uniform", "near_6_45"], - help="prior 선택: uniform=Beta(1,1), near_6_45=Beta(6,39)", - ) - parser.add_argument( - "--max_round", - type=int, - default=1208, - help="사용할 최대 회차 (기본 1208). 데이터가 더 많으면 잘라서 사용.", - ) - parser.add_argument( - "--no_tune", - action="store_true", - help="튜닝(그리드서치) 비활성화: 기본 파라미터로만 추천", - ) - parser.add_argument( - "--half_life_grid", - type=str, - default="50,100,150,200,300,400", - help="튜닝 half_life 후보(쉼표구분). 기본 '50,100,150,200,300,400'", - ) - parser.add_argument( - "--gscale_grid", - type=str, - default="10,20,30,50", - help="튜닝 gscale 후보(쉼표구분). 기본 '10,20,30,50'", - ) - parser.add_argument( - "--weight_step", - type=float, - default=0.1, - help="가중치 그리드 step (합=1, 기본 0.1)", - ) - args = parser.parse_args() - - # seed은 현재 타이브레이크에 랜덤을 쓰지 않지만, 확장 대비로 고정 - try: - import random - - random.seed(int(args.seed)) - except Exception: - pass - - print("⚠️ 중요 고지") - print("- 로또는 원칙적으로 독립·무작위 추첨이므로 과거 데이터로 실제 당첨확률을 증가시킨다고 보장할 수 없습니다.") - print("- 본 프로그램의 '확률'은 통계적 추정/휴리스틱 점수이며, 실험/학습 목적입니다.") - print() - - # prior shortcut - alpha = float(args.alpha) - beta = float(args.beta) - if args.prior == "near_6_45": - alpha, beta = 6.0, 39.0 - eprint("[INFO] prior=near_6_45 → Beta(6,39)") - else: - eprint("[INFO] prior=uniform → Beta(alpha,beta) (기본 1,1)") - - base_dir = os.path.dirname(os.path.abspath(__file__)) - txt_path = os.path.join(base_dir, "resources", "lotto_history.txt") - json_path = os.path.join(base_dir, "resources", "lotto_history.json") - - txt_data = None - json_data = None - try: - txt_data = load_history_txt(txt_path) - eprint(f"[INFO] txt 로드: {len(txt_data)}회차") - except FileNotFoundError: - eprint(f"[INFO] txt 없음: {txt_path}") - except Exception as ex: - eprint(f"[WARN] txt 로드 실패: {ex}") - - try: - json_data = load_history_json(json_path) - eprint(f"[INFO] json 로드: {len(json_data)}회차") - except FileNotFoundError: - eprint(f"[INFO] json 없음: {json_path}") - except Exception as ex: - eprint(f"[WARN] json 로드 실패: {ex}") - - merged = cross_validate_and_merge(txt_data, json_data) - if not merged: - eprint("[ERROR] 입력 데이터가 없습니다. resources/lotto_history.txt 또는 .json 중 하나가 필요합니다.") - sys.exit(2) - - max_round = int(args.max_round) if args.max_round else None - draws = sanitize_and_sort_draws(merged, min_round=1, max_round=max_round) - if not draws: - eprint("[ERROR] 유효한 회차/번호가 없습니다.") - sys.exit(2) - - # 연속성/누락 간단 체크 - round_list = [rnd for rnd, _ in draws] - if round_list[0] != 1: - eprint(f"[WARN] 시작 회차가 1이 아님: {round_list[0]}") - if max_round is not None and round_list[-1] != max_round: - eprint(f"[WARN] 최대 회차가 {max_round}가 아님: {round_list[-1]}") - missing = [] - if round_list: - expected = set(range(round_list[0], round_list[-1] + 1)) - missing = sorted(expected - set(round_list)) - if missing: - eprint(f"[WARN] 누락 회차 수: {len(missing)} (예: {missing[:10]}{'...' if len(missing)>10 else ''})") - - last_round = round_list[-1] - target_draw = last_round + 1 - if max_round is not None: - # 사용자가 max_round=1208로 고정했으면 target은 1209가 되도록 맞춤 - if last_round != max_round: - eprint(f"[WARN] max_round={max_round}로 잘랐지만 실제 마지막 회차={last_round}. target={target_draw}") - - # 튜닝 그리드 파라미터 구성 - def parse_num_list(s: str, typ: Any) -> List[Any]: - out = [] - for tok in (s or "").split(","): - tok = tok.strip() - if not tok: - continue - try: - out.append(typ(tok)) - except Exception: - pass - return out - - half_life_grid = parse_num_list(args.half_life_grid, int) - if int(args.half_life) not in set(int(x) for x in half_life_grid): - half_life_grid.append(int(args.half_life)) - half_life_grid = sorted(set(int(x) for x in half_life_grid if int(x) > 0)) - - gscale_grid = parse_num_list(args.gscale_grid, float) - gscale_grid = sorted(set(float(x) for x in gscale_grid if float(x) > 0)) - if not gscale_grid: - gscale_grid = [20.0] - - topk = int(args.topk) - warmup = int(args.warmup) - valid_last = int(args.valid_last) - - if np is None: - eprint("[ERROR] numpy가 설치되어 있지 않아 실행할 수 없습니다. (성능/구현 단순화를 위해 numpy를 사용합니다)") - sys.exit(2) - - if args.no_tune: - # 튜닝 없이 기본 값 사용 - best = BacktestResult( - params=ScoringParams( - alpha=alpha, - beta=beta, - half_life=int(args.half_life), - gscale=float(gscale_grid[0]), - wA=0.5, - wB=0.4, - wC=0.1, - ).normalized_weights(), - topk=topk, - warmup=warmup, - n_eval=0, - overall_mean_hit=float("nan"), - overall_hit1_rate=float("nan"), - recent_mean_hit=float("nan"), - recent_hit1_rate=float("nan"), - objective=float("nan"), - ) - eprint("[INFO] --no_tune: 기본 파라미터로 추천만 수행") - else: - best = backtest_and_tune( - draws, - topk=topk, - warmup=warmup, - valid_last=valid_last, - alpha=alpha, - beta=beta, - half_life_grid=half_life_grid, - gscale_grid=gscale_grid, - weight_step=float(args.weight_step), - ) - eprint("[INFO] 튜닝 완료") - eprint( - f"[BEST] objective={best.objective:.6f} " - f"recent_mean_hit={best.recent_mean_hit:.4f} recent_hit>=1={best.recent_hit1_rate:.4f} | " - f"overall_mean_hit={best.overall_mean_hit:.4f} overall_hit>=1={best.overall_hit1_rate:.4f} | " - f"half_life={best.params.half_life} gscale={best.params.gscale} " - f"wA={best.params.wA:.1f} wB={best.params.wB:.1f} wC={best.params.wC:.1f} " - f"alpha={best.params.alpha} beta={best.params.beta}" - ) - - recs, out_json = recommend(draws, best=best, target_draw_no=target_draw, topk=topk) - - print(f"## 1209회차 추천(단일 번호 Top{topk})" if target_draw == 1209 else f"## {target_draw}회차 추천(단일 번호 Top{topk})") - print_table(recs) - print() - print("## JSON") - print(json.dumps(out_json, ensure_ascii=False, indent=2, sort_keys=False)) - - -if __name__ == "__main__": - main() - diff --git a/practice_1.py b/practice_1.py deleted file mode 100644 index 64c9697..0000000 --- a/practice_1.py +++ /dev/null @@ -1,179 +0,0 @@ -# 웹 호출 라이브러리를 호출합니다. -import time -import requests -from DataCrawler import DataCrawler - -import json -import os -import pandas as pd -import itertools -from datetime import datetime, timedelta -from TelegramBot import TelegramBot - -from filter_model_1 import BallFilter - -class Practice: - - bot = None - preprocessor = None - predictor = None - - extract_count = None - - def __init__(self, resources_path): - self.bot = TelegramBot() - - return - - # 로또 당첨 데이터를 수집해서 파일로 저장합니다. - # lottoHistoryFile: 로또 당첨 데이터를 저장할 파일 - def craw(self, lottoHistoryFile, drwNo=None): - - ball = None - if drwNo != None: - # 로또 데이터를 저장할 파일을 선언합니다. - jsonFp = open(lottoHistoryFile + ".json", 'a', encoding="utf-8") - textFp = open(lottoHistoryFile + ".txt", 'a', encoding="utf-8") - - url = 'https://dhlottery.co.kr/common.do?method=getLottoNumber&drwNo=' + str(drwNo) - # URL을 호출합니다. - res = requests.post(url) - # 호출한 결과에 대해서 Json 포맷을 가져옵니다. - result = res.json() - - if result['returnValue'] != 'success': - return None - - # 가져온 Json 포맷을 파일로 저장합니다. - jsonFp.write(json.dumps(result, ensure_ascii=False) + "\n") - textFp.write("%d,%d,%d,%d,%d,%d,%d,%d\n" % (drwNo, result['drwtNo1'], result['drwtNo2'], result['drwtNo3'], result['drwtNo4'], result['drwtNo5'], result['drwtNo6'], result['bnusNo'])) - print("%d,%d,%d,%d,%d,%d,%d,%d" % (drwNo, result['drwtNo1'], result['drwtNo2'], result['drwtNo3'], result['drwtNo4'], result['drwtNo5'], result['drwtNo6'], result['bnusNo'])) - - ball = [result['drwtNo1'], result['drwtNo2'], result['drwtNo3'], result['drwtNo4'], result['drwtNo5'], result['drwtNo6'], result['bnusNo']] - else: - # 로또 데이터를 저장할 파일을 선언합니다. - jsonFp = open(lottoHistoryFile + ".json", 'w', encoding="utf-8") - textFp = open(lottoHistoryFile + ".txt", 'w', encoding="utf-8") - - # 1회차부터 지정된 회차까지 로또 당첨 번호를 수집합니다. - idx = 1 - while True: - # 1회차부터 지정된 회차까지의 URL을 생성합니다. - url = 'https://dhlottery.co.kr/common.do?method=getLottoNumber&drwNo=' + str(idx) - # URL을 호출합니다. - res = requests.post(url) - # 호출한 결과에 대해서 Json 포맷을 가져옵니다. - result = res.json() - if result['returnValue'] != 'success': - break - # 가져온 Json 포맷을 파일로 저장합니다. - jsonFp.write(json.dumps(result, ensure_ascii=False) + "\n") - textFp.write("%d,%d,%d,%d,%d,%d,%d,%d\n" % (idx, result['drwtNo1'], result['drwtNo2'], result['drwtNo3'], result['drwtNo4'], result['drwtNo5'], result['drwtNo6'], result['bnusNo'])) - print("%d,%d,%d,%d,%d,%d,%d,%d" % (idx, result['drwtNo1'], result['drwtNo2'], result['drwtNo3'], result['drwtNo4'], result['drwtNo5'], result['drwtNo6'], result['bnusNo'])) - ball = [result['drwtNo1'], result['drwtNo2'], result['drwtNo3'], result['drwtNo4'], result['drwtNo5'], result['drwtNo6'], result['bnusNo']] - idx += 1 - time.sleep(0.5) - # 저장한 파일을 종료합니다. - jsonFp.close() - textFp.close() - - return ball - - def predict1(self, result_json): - result_json.append([6,7,10,11,20,45]) - return - - def predict2(self, resources_path, ymd, result_json): - - candidates = [i for i in range(1, 46)] - - lottoHistoryFileName = os.path.join(resources_path, 'lotto_history.json') - ballFilter = BallFilter(lottoHistoryFileName) - no = ballFilter.getNextNo(ymd) - print("회차: {}".format(no)) - - lottoHistoryFileName = os.path.join(resources_path, 'lotto_history.txt') - df_ball = pd.read_csv(lottoHistoryFileName, header=None) - df_ball.columns = ['no', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'bn'] - - #filter_ball=[1,2,4,6,10,11,11,17,18,20,21,22,23,24,26,27,28,30,31,32,33,34,37,38,39,40,42,44] - nCr = list(itertools.combinations(candidates, 6)) - for idx, ball in enumerate(nCr): - - if idx % 1000000 == 0: - print(" - {} processed...".format(idx)) - - ball = list(ball) - - filter_type = ballFilter.filter(ball=ball, no=no, until_end=False, df=df_ball) - filter_size = len(filter_type) - - if 0 < filter_size: - continue - - result_json.append(ball) - - p_ball = df_ball[df_ball['no'] == no - 1].values.tolist()[0] - p_no = p_ball[0] - p_ball = p_ball[1:7] - - return p_no, p_ball - -if __name__ == '__main__': - - PROJECT_HOME = '.' - resources_path = os.path.join(PROJECT_HOME, 'resources') - - today = datetime.today() - if today.weekday() == 5: - if today.hour > 20: - this_weekend = today + timedelta(days=(12 - today.weekday())) - else: - this_weekend = today + timedelta(days=(5 - today.weekday())) - elif today.weekday() == 6: - this_weekend = today + timedelta(days=(12 - today.weekday())) - else: - this_weekend = today + timedelta(days=(5 - today.weekday())) - - last_weekend = (this_weekend - timedelta(days=7)).strftime('%Y%m%d') - ymd = this_weekend.strftime('%Y%m%d') - - print("ymd: {}".format(ymd)) - - # 로또 예측 - practice = Practice(resources_path) - - # 데이터 수집 - lottoHistoryFile = PROJECT_HOME + '/resources/lotto_history' - lottoHistoryFileName = lottoHistoryFile + '.json' - with open(lottoHistoryFileName, "r", encoding='utf-8') as f: - for line in f: - if line != '\n': - last_json = json.loads(line) - - #ball = practice.craw(lottoHistoryFile, drwNo=last_json['drwNo'] + 1) - - result_json = {ymd: []} - - # 매주 고정 - practice.predict1(result_json[ymd]) - # 필터 기반 예측 - p_no, p_ball = practice.predict2(resources_path, ymd, result_json[ymd]) - - p_str = "[지난주] {}\n - {} 회차, {}\n[금주] {}\n - {} 회차\n[모델#25]\n".format(last_weekend, p_no, str(p_ball), ymd, (p_no + 1)) - for i, ball in enumerate(result_json[ymd]): - p_str += " {}. {}\n".format((i+1), str(ball)) - if (i+1) % 100 == 0: - practice.bot.sendMsg("{}".format(p_str)) - p_str = '' - - if len(result_json[ymd]) % 100 != 0: - practice.bot.sendMsg("{}".format(p_str)) - - size = len(result_json[ymd]) - print("size: {}".format(size)) - - # https://youtu.be/QjBsui8Ob14?si=4dC3q8p0Yu5ZWK1K - # https://www.youtube.com/watch?v=YwiHaa1KNwA - - print("done...") \ No newline at end of file diff --git a/practice_2.py b/practice_2.py deleted file mode 100644 index f643702..0000000 --- a/practice_2.py +++ /dev/null @@ -1,179 +0,0 @@ -# 웹 호출 라이브러리를 호출합니다. -import time -import requests -from DataCrawler import DataCrawler - -import json -import os -import pandas as pd -import itertools -from datetime import datetime, timedelta -from TelegramBot import TelegramBot - -from filter_model_2 import BallFilter - -class Practice: - - bot = None - preprocessor = None - predictor = None - - extract_count = None - - def __init__(self, resources_path): - self.bot = TelegramBot() - - return - - # 로또 당첨 데이터를 수집해서 파일로 저장합니다. - # lottoHistoryFile: 로또 당첨 데이터를 저장할 파일 - def craw(self, lottoHistoryFile, drwNo=None): - - ball = None - if drwNo != None: - # 로또 데이터를 저장할 파일을 선언합니다. - jsonFp = open(lottoHistoryFile + ".json", 'a', encoding="utf-8") - textFp = open(lottoHistoryFile + ".txt", 'a', encoding="utf-8") - - url = 'https://dhlottery.co.kr/common.do?method=getLottoNumber&drwNo=' + str(drwNo) - # URL을 호출합니다. - res = requests.post(url) - # 호출한 결과에 대해서 Json 포맷을 가져옵니다. - result = res.json() - - if result['returnValue'] != 'success': - return None - - # 가져온 Json 포맷을 파일로 저장합니다. - jsonFp.write(json.dumps(result, ensure_ascii=False) + "\n") - textFp.write("%d,%d,%d,%d,%d,%d,%d,%d\n" % (drwNo, result['drwtNo1'], result['drwtNo2'], result['drwtNo3'], result['drwtNo4'], result['drwtNo5'], result['drwtNo6'], result['bnusNo'])) - print("%d,%d,%d,%d,%d,%d,%d,%d" % (drwNo, result['drwtNo1'], result['drwtNo2'], result['drwtNo3'], result['drwtNo4'], result['drwtNo5'], result['drwtNo6'], result['bnusNo'])) - - ball = [result['drwtNo1'], result['drwtNo2'], result['drwtNo3'], result['drwtNo4'], result['drwtNo5'], result['drwtNo6'], result['bnusNo']] - else: - # 로또 데이터를 저장할 파일을 선언합니다. - jsonFp = open(lottoHistoryFile + ".json", 'w', encoding="utf-8") - textFp = open(lottoHistoryFile + ".txt", 'w', encoding="utf-8") - - # 1회차부터 지정된 회차까지 로또 당첨 번호를 수집합니다. - idx = 1 - while True: - # 1회차부터 지정된 회차까지의 URL을 생성합니다. - url = 'https://dhlottery.co.kr/common.do?method=getLottoNumber&drwNo=' + str(idx) - # URL을 호출합니다. - res = requests.post(url) - # 호출한 결과에 대해서 Json 포맷을 가져옵니다. - result = res.json() - if result['returnValue'] != 'success': - break - # 가져온 Json 포맷을 파일로 저장합니다. - jsonFp.write(json.dumps(result, ensure_ascii=False) + "\n") - textFp.write("%d,%d,%d,%d,%d,%d,%d,%d\n" % (idx, result['drwtNo1'], result['drwtNo2'], result['drwtNo3'], result['drwtNo4'], result['drwtNo5'], result['drwtNo6'], result['bnusNo'])) - print("%d,%d,%d,%d,%d,%d,%d,%d" % (idx, result['drwtNo1'], result['drwtNo2'], result['drwtNo3'], result['drwtNo4'], result['drwtNo5'], result['drwtNo6'], result['bnusNo'])) - ball = [result['drwtNo1'], result['drwtNo2'], result['drwtNo3'], result['drwtNo4'], result['drwtNo5'], result['drwtNo6'], result['bnusNo']] - idx += 1 - time.sleep(0.5) - # 저장한 파일을 종료합니다. - jsonFp.close() - textFp.close() - - return ball - - def predict1(self, result_json): - result_json.append([6,7,10,11,20,45]) - return - - def predict2(self, resources_path, ymd, result_json): - - candidates = [i for i in range(1, 46)] - - lottoHistoryFileName = os.path.join(resources_path, 'lotto_history.json') - ballFilter = BallFilter(lottoHistoryFileName) - no = ballFilter.getNextNo(ymd) - print("회차: {}".format(no)) - - lottoHistoryFileName = os.path.join(resources_path, 'lotto_history.txt') - df_ball = pd.read_csv(lottoHistoryFileName, header=None) - df_ball.columns = ['no', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'bn'] - - #filter_ball=[1,2,4,6,10,11,11,17,18,20,21,22,23,24,26,27,28,30,31,32,33,34,37,38,39,40,42,44] - nCr = list(itertools.combinations(candidates, 6)) - for idx, ball in enumerate(nCr): - - if idx % 1000000 == 0: - print(" - {} processed...".format(idx)) - - ball = list(ball) - - filter_type = ballFilter.filter(ball=ball, no=no, until_end=False, df=df_ball) - filter_size = len(filter_type) - - if 0 < filter_size: - continue - - result_json.append(ball) - - p_ball = df_ball[df_ball['no'] == no - 1].values.tolist()[0] - p_no = p_ball[0] - p_ball = p_ball[1:7] - - return p_no, p_ball - -if __name__ == '__main__': - - PROJECT_HOME = '.' - resources_path = os.path.join(PROJECT_HOME, 'resources') - - today = datetime.today() - if today.weekday() == 5: - if today.hour > 20: - this_weekend = today + timedelta(days=(12 - today.weekday())) - else: - this_weekend = today + timedelta(days=(5 - today.weekday())) - elif today.weekday() == 6: - this_weekend = today + timedelta(days=(12 - today.weekday())) - else: - this_weekend = today + timedelta(days=(5 - today.weekday())) - - last_weekend = (this_weekend - timedelta(days=7)).strftime('%Y%m%d') - ymd = this_weekend.strftime('%Y%m%d') - - print("ymd: {}".format(ymd)) - - # 로또 예측 - practice = Practice(resources_path) - - # 데이터 수집 - lottoHistoryFile = PROJECT_HOME + '/resources/lotto_history' - lottoHistoryFileName = lottoHistoryFile + '.json' - with open(lottoHistoryFileName, "r", encoding='utf-8') as f: - for line in f: - if line != '\n': - last_json = json.loads(line) - - #ball = practice.craw(lottoHistoryFile, drwNo=last_json['drwNo'] + 1) - - result_json = {ymd: []} - - # 매주 고정 - practice.predict1(result_json[ymd]) - # 필터 기반 예측 - p_no, p_ball = practice.predict2(resources_path, ymd, result_json[ymd]) - - p_str = "[지난주] {}\n - {} 회차, {}\n[금주] {}\n - {} 회차\n[모델#25]\n".format(last_weekend, p_no, str(p_ball), ymd, (p_no + 1)) - for i, ball in enumerate(result_json[ymd]): - p_str += " {}. {}\n".format((i+1), str(ball)) - if (i+1) % 100 == 0: - practice.bot.sendMsg("{}".format(p_str)) - p_str = '' - - if len(result_json[ymd]) % 100 != 0: - practice.bot.sendMsg("{}".format(p_str)) - - size = len(result_json[ymd]) - print("size: {}".format(size)) - - # https://youtu.be/QjBsui8Ob14?si=4dC3q8p0Yu5ZWK1K - # https://www.youtube.com/watch?v=YwiHaa1KNwA - - print("done...") \ No newline at end of file diff --git a/practice_3.py b/practice_3.py deleted file mode 100644 index 99362c3..0000000 --- a/practice_3.py +++ /dev/null @@ -1,546 +0,0 @@ -# 웹 호출 라이브러리를 호출합니다. -import time -import requests -from DataCrawler import DataCrawler - -import json -import os -import copy -import pandas as pd -import itertools -from datetime import datetime, timedelta -from TelegramBot import TelegramBot - -from filter_model_3 import BallFilter - -class Practice: - - bot = None - preprocessor = None - predictor = None - - extract_count = None - TARGET_MIN_SURVIVORS = 30 - TARGET_MAX_SURVIVORS = 150 - PREDICT_TIMEOUT_SECONDS = 180 - - def __init__(self, resources_path): - self.bot = TelegramBot() - self.resources_path = resources_path - - return - - # 로또 당첨 데이터를 수집해서 파일로 저장합니다. - # lottoHistoryFile: 로또 당첨 데이터를 저장할 파일 - def craw(self, lottoHistoryFile, drwNo=None): - - ball = None - if drwNo != None: - # 로또 데이터를 저장할 파일을 선언합니다. - jsonFp = open(lottoHistoryFile + ".json", 'a', encoding="utf-8") - textFp = open(lottoHistoryFile + ".txt", 'a', encoding="utf-8") - - url = 'https://dhlottery.co.kr/common.do?method=getLottoNumber&drwNo=' + str(drwNo) - # URL을 호출합니다. - res = requests.post(url) - # 호출한 결과에 대해서 Json 포맷을 가져옵니다. - result = res.json() - - if result['returnValue'] != 'success': - return None - - # 가져온 Json 포맷을 파일로 저장합니다. - jsonFp.write(json.dumps(result, ensure_ascii=False) + "\n") - textFp.write("%d,%d,%d,%d,%d,%d,%d,%d\n" % (drwNo, result['drwtNo1'], result['drwtNo2'], result['drwtNo3'], result['drwtNo4'], result['drwtNo5'], result['drwtNo6'], result['bnusNo'])) - print("%d,%d,%d,%d,%d,%d,%d,%d" % (drwNo, result['drwtNo1'], result['drwtNo2'], result['drwtNo3'], result['drwtNo4'], result['drwtNo5'], result['drwtNo6'], result['bnusNo'])) - - ball = [result['drwtNo1'], result['drwtNo2'], result['drwtNo3'], result['drwtNo4'], result['drwtNo5'], result['drwtNo6'], result['bnusNo']] - else: - # 로또 데이터를 저장할 파일을 선언합니다. - jsonFp = open(lottoHistoryFile + ".json", 'w', encoding="utf-8") - textFp = open(lottoHistoryFile + ".txt", 'w', encoding="utf-8") - - # 1회차부터 지정된 회차까지 로또 당첨 번호를 수집합니다. - idx = 1 - while True: - # 1회차부터 지정된 회차까지의 URL을 생성합니다. - url = 'https://dhlottery.co.kr/common.do?method=getLottoNumber&drwNo=' + str(idx) - # URL을 호출합니다. - res = requests.post(url) - # 호출한 결과에 대해서 Json 포맷을 가져옵니다. - result = res.json() - if result['returnValue'] != 'success': - break - # 가져온 Json 포맷을 파일로 저장합니다. - jsonFp.write(json.dumps(result, ensure_ascii=False) + "\n") - textFp.write("%d,%d,%d,%d,%d,%d,%d,%d\n" % (idx, result['drwtNo1'], result['drwtNo2'], result['drwtNo3'], result['drwtNo4'], result['drwtNo5'], result['drwtNo6'], result['bnusNo'])) - print("%d,%d,%d,%d,%d,%d,%d,%d" % (idx, result['drwtNo1'], result['drwtNo2'], result['drwtNo3'], result['drwtNo4'], result['drwtNo5'], result['drwtNo6'], result['bnusNo'])) - ball = [result['drwtNo1'], result['drwtNo2'], result['drwtNo3'], result['drwtNo4'], result['drwtNo5'], result['drwtNo6'], result['bnusNo']] - idx += 1 - time.sleep(0.5) - # 저장한 파일을 종료합니다. - jsonFp.close() - textFp.close() - - return ball - - def predict1(self, result_json): - result_json.append([6, 7, 10, 11, 20, 45]) - result_json.append([2, 7, 17, 28, 35, 39]) - result_json.append([6, 10, 19, 25, 33, 35]) - result_json.append([3, 17, 20, 24, 35, 45]) - result_json.append([5, 15, 18, 29, 36, 41]) - result_json.append([6, 15, 20, 23, 37, 43]) - result_json.append([8, 15, 19, 23, 38, 41]) - result_json.append([5, 11, 19, 24, 40, 45]) - result_json.append([9, 16, 18, 23, 35, 43]) - result_json.append([7, 13, 19, 28, 33, 44]) - result_json.append([7, 11, 18, 29, 37, 42]) - print("회차(predict1)") - return - - def predict2(self, resources_path, ymd, result_json): - - candidates = [i for i in range(1, 46)] - - lottoHistoryFileName = os.path.join(resources_path, 'lotto_history.json') - ballFilter = BallFilter(lottoHistoryFileName) - no = ballFilter.getNextNo(ymd) - print("회차(predict2): {}".format(no)) - - lottoHistoryFileName = os.path.join(resources_path, 'lotto_history.txt') - df_ball = pd.read_csv(lottoHistoryFileName, header=None) - df_ball.columns = ['no', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'bn'] - - #filter_ball=[1,2,4,6,10,11,11,17,18,20,21,22,23,24,26,27,28,30,31,32,33,34,37,38,39,40,42,44] - nCr = list(itertools.combinations(candidates, 6)) - for idx, ball in enumerate(nCr): - - if idx % 1000000 == 0: - print(" - {} processed...".format(idx)) - - ball = list(ball) - - filter_type = ballFilter.filter(ball=ball, no=no, until_end=False, df=df_ball) - filter_size = len(filter_type) - - if 0 < filter_size: - continue - - result_json.append(ball) - - p_ball = df_ball[df_ball['no'] == no - 1].values.tolist()[0] - p_no = p_ball[0] - p_ball = p_ball[1:7] - - return p_no, p_ball - - def predict3(self, resources_path, ymd, result_json): - candidates = [i for i in range(1, 46)] - lottoHistoryFileName = os.path.join(resources_path, 'lotto_history.json') - no = BallFilter(lottoHistoryFileName).getNextNo(ymd) - print("회차(predict3): {}".format(no)) - predict_start_ts = time.time() - deadline_ts = predict_start_ts + self.PREDICT_TIMEOUT_SECONDS - - lottoHistoryFileName = os.path.join(resources_path, 'lotto_history.txt') - df_ball = pd.read_csv(lottoHistoryFileName, header=None) - df_ball.columns = ['no', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'bn'] - - p_ball = df_ball[df_ball['no'] == no - 1].values.tolist()[0] - p_no = p_ball[0] - p_ball = sorted(p_ball[1:7]) - - base_ruleset = self._get_base_ruleset() - tighten_rulesets = [ - self._build_ruleset( - base_ruleset=base_ruleset, - enabled_overrides={ - "paper_patterns": True, - "ban_triples_legacy": True, - "all_in_previous7": True, - "previous_neighbors": True, - }, - allowed_overrides={ - "ac_value": [8, 9], - "uniq_last_digit_count": [4, 5], - "even_count": [2, 3, 4], - }, - ), - self._build_ruleset( - base_ruleset=base_ruleset, - enabled_overrides={ - "paper_patterns": True, - "ban_triples_legacy": True, - "all_in_previous7": True, - "previous_neighbors": True, - }, - allowed_overrides={ - "ac_value": [8, 9], - "uniq_last_digit_count": [4, 5], - "even_count": [2, 3, 4], - "sum": [112, 114, 121, 123, 126, 127, 131, 132, 138, 146, 148], - "sum_prev_diff": [13, 14, 17, 18, 26, 28, 29, 30, 32, 39, 40], - }, - ), - ] - relax_rulesets = [ - self._build_ruleset( - base_ruleset=base_ruleset, - enabled_overrides={ - "paper_patterns": False, - "ban_triples_legacy": False, - }, - ), - self._build_ruleset( - base_ruleset=base_ruleset, - enabled_overrides={ - "paper_patterns": False, - "ban_triples_legacy": False, - "previous_neighbors": False, - "all_in_previous7": False, - }, - ), - self._build_ruleset( - base_ruleset=base_ruleset, - enabled_overrides={ - "paper_patterns": False, - "ban_triples_legacy": False, - "previous_neighbors": False, - "all_in_previous7": False, - "weeks_8_count": False, - "weeks_12_count": False, - "weeks_16_count": False, - "weeks_20_count": False, - }, - ), - ] - - min_survivors = self.TARGET_MIN_SURVIVORS - max_survivors = self.TARGET_MAX_SURVIVORS - chosen = [] - stage_name = "base" - - current_info = self._collect_candidates( - candidates=candidates, - no=no, - df_ball=df_ball, - ruleset=base_ruleset, - stop_when_gt=max_survivors, - stage_name="base", - predict_start_ts=predict_start_ts, - deadline_ts=deadline_ts, - ) - current = current_info["candidates"] - if current_info["timed_out"]: - chosen = self._finalize_on_timeout(current, p_ball, min_survivors, max_survivors) - stage_name = "base_timeout_fallback" - print("predict3 stage: {}, survivors: {}".format(stage_name, len(chosen))) - for ball in chosen: - result_json.append(ball) - return p_no, p_ball - - if min_survivors <= len(current) <= max_survivors: - chosen = current - elif len(current) > max_survivors: - chosen = current - stage_name = "base_overflow" - for idx, rs in enumerate(tighten_rulesets, start=1): - t_info = self._collect_candidates( - candidates=candidates, - no=no, - df_ball=df_ball, - ruleset=rs, - stop_when_gt=max_survivors, - stage_name="tighten_{}".format(idx), - predict_start_ts=predict_start_ts, - deadline_ts=deadline_ts, - ) - t = t_info["candidates"] - if t_info["timed_out"]: - chosen = self._finalize_on_timeout(t, p_ball, min_survivors, max_survivors) - stage_name = "tighten_{}_timeout_fallback".format(idx) - break - if min_survivors <= len(t) <= max_survivors: - chosen = t - stage_name = "tighten_{}".format(idx) - break - if len(t) <= max_survivors: - chosen = t - stage_name = "tighten_{}".format(idx) - - if len(chosen) > max_survivors: - full_info = self._collect_candidates( - candidates=candidates, - no=no, - df_ball=df_ball, - ruleset=tighten_rulesets[-1], - stop_when_gt=None, - stage_name="tighten_full_rank", - predict_start_ts=predict_start_ts, - deadline_ts=deadline_ts, - ) - full_for_ranking = full_info["candidates"] - if full_info["timed_out"]: - chosen = self._finalize_on_timeout(full_for_ranking, p_ball, min_survivors, max_survivors) - stage_name = "tighten_rank_timeout_fallback" - else: - chosen = self._rank_and_trim(full_for_ranking, p_ball, max_survivors) - stage_name = "tighten_rank_trim" - else: - chosen = current - stage_name = "base_underflow" - for idx, rs in enumerate(relax_rulesets, start=1): - r_info = self._collect_candidates( - candidates=candidates, - no=no, - df_ball=df_ball, - ruleset=rs, - stop_when_gt=None, - stop_when_gte=min_survivors, - stage_name="relax_{}".format(idx), - predict_start_ts=predict_start_ts, - deadline_ts=deadline_ts, - ) - r = r_info["candidates"] - chosen = r - stage_name = "relax_{}".format(idx) - if r_info["timed_out"]: - chosen = self._finalize_on_timeout(r, p_ball, min_survivors, max_survivors) - stage_name = "relax_{}_timeout_fallback".format(idx) - break - if len(r) >= min_survivors: - break - - if len(chosen) == 0: - stage_name = "relax_zero_fallback" - chosen = self._fallback_candidates_from_prev(p_ball, min_survivors) - elif len(chosen) < min_survivors: - stage_name = "{}_fill".format(stage_name) - fill = self._fallback_candidates_from_prev( - p_ball, - min_survivors - len(chosen), - exclude=set(tuple(x) for x in chosen), - ) - chosen.extend(fill) - - print("predict3 stage: {}, survivors: {}".format(stage_name, len(chosen))) - for ball in chosen: - result_json.append(ball) - return p_no, p_ball - - def _get_base_ruleset(self): - history_json = os.path.join(self.resources_path, "lotto_history.json") - base_filter = BallFilter(history_json) - return copy.deepcopy(base_filter.m1.ruleset) - - def _build_ruleset(self, base_ruleset, enabled_overrides=None, allowed_overrides=None): - ruleset = copy.deepcopy(base_ruleset) - ruleset.setdefault("filters", {}) - enabled_overrides = enabled_overrides or {} - allowed_overrides = allowed_overrides or {} - for key, value in enabled_overrides.items(): - ruleset["filters"].setdefault(key, {}) - ruleset["filters"][key]["enabled"] = bool(value) - for key, values in allowed_overrides.items(): - ruleset["filters"].setdefault(key, {}) - ruleset["filters"][key]["enabled"] = True - ruleset["filters"][key]["allowed"] = list(values) - return ruleset - - def _collect_candidates( - self, - candidates, - no, - df_ball, - ruleset, - stop_when_gt=None, - stop_when_gte=None, - stage_name="base", - predict_start_ts=None, - deadline_ts=None, - ): - lottoHistoryFileName = os.path.join(self.resources_path, "lotto_history.json") - ballFilter = BallFilter(lottoHistoryFileName, ruleset=ruleset) - result = [] - last_idx = 0 - for idx, ball in enumerate(itertools.combinations(candidates, 6), start=1): - last_idx = idx - if deadline_ts is not None and deadline_ts <= time.time(): - elapsed = (time.time() - predict_start_ts) if predict_start_ts is not None else 0.0 - print(" - [{}] timeout after {:,} processed (elapsed: {:.1f}s, survivors: {:,})".format(stage_name, idx, elapsed, len(result))) - return {"candidates": result, "timed_out": True, "processed": idx} - if idx % 1000000 == 0: - elapsed = (time.time() - predict_start_ts) if predict_start_ts is not None else 0.0 - print(" - [{}] {:,} processed... (elapsed: {:.1f}s, survivors: {:,})".format(stage_name, idx, elapsed, len(result))) - b = list(ball) - if len(ballFilter.filter(ball=b, no=no, until_end=False, df=df_ball)) == 0: - result.append(b) - if stop_when_gt is not None and len(result) > stop_when_gt: - return {"candidates": result, "timed_out": False, "processed": idx} - if stop_when_gte is not None and len(result) >= stop_when_gte: - return {"candidates": result, "timed_out": False, "processed": idx} - return {"candidates": result, "timed_out": False, "processed": last_idx} - - def _finalize_on_timeout(self, partial_candidates, prev_ball, min_survivors, max_survivors): - chosen = list(partial_candidates) - if len(chosen) > max_survivors: - chosen = self._rank_and_trim(chosen, prev_ball, max_survivors) - elif len(chosen) < min_survivors: - fill = self._fallback_candidates_from_prev( - prev_ball, - min_survivors - len(chosen), - exclude=set(tuple(x) for x in chosen), - ) - chosen.extend(fill) - return chosen - - def _rank_and_trim(self, candidates, prev_ball, limit): - scored = [(self._score_candidate(ball, prev_ball), ball) for ball in candidates] - scored.sort(key=lambda x: x[0]) - return [ball for _, ball in scored[:limit]] - - def _score_candidate(self, ball, prev_ball): - sum_diff = abs(sum(ball) - sum(prev_ball)) - even_cnt = len([x for x in ball if x % 2 == 0]) - uniq_last = len(set([x % 10 for x in ball])) - contiguous_penalty = 0 - s = sorted(ball) - for i in range(1, len(s)): - if s[i] - s[i - 1] == 1: - contiguous_penalty += 1 - score = 0 - score += sum_diff - score += abs(even_cnt - 3) * 2 - score += abs(uniq_last - 5) * 2 - score += contiguous_penalty - return score - - def _fallback_candidates_from_prev(self, prev_ball, need_count, exclude=None): - exclude = exclude or set() - seed = sorted(prev_ball) - out = [] - delta_patterns = [ - (0, 0, 0, 0, 0, 0), - (-1, 0, 0, 0, 0, 1), - (0, -1, 0, 0, 1, 0), - (0, 0, -1, 1, 0, 0), - (-2, 0, 0, 0, 0, 2), - (0, -2, 0, 0, 2, 0), - (0, 0, -2, 2, 0, 0), - (-1, -1, 0, 0, 1, 1), - (1, 0, -1, 0, 0, 0), - (0, 1, 0, -1, 0, 0), - (1, -1, 1, -1, 1, -1), - (-1, 1, -1, 1, -1, 1), - ] - shift = 0 - while len(out) < need_count and shift <= 8: - for delta in delta_patterns: - cand = [seed[i] + delta[i] for i in range(6)] - cand = [min(45, max(1, v + shift)) for v in cand] - cand = sorted(cand) - if len(set(cand)) != 6: - continue - t = tuple(cand) - if t in exclude: - continue - exclude.add(t) - out.append(cand) - if len(out) >= need_count: - break - shift += 1 - return out - - def _merge_unique_balls(self, base_balls, extra_balls): - seen = set(tuple(sorted(x)) for x in base_balls) - for ball in extra_balls: - key = tuple(sorted(ball)) - if key not in seen: - base_balls.append(list(ball)) - seen.add(key) - return base_balls - - def _sorted_unique_balls(self, balls): - """ - Normalize (sort within ball), de-duplicate, then sort lexicographically. - Returns List[List[int]]. - """ - uniq = {} - for b in balls: - key = tuple(sorted(b)) - uniq[key] = list(key) - return [list(t) for t in sorted(uniq.keys())] - -if __name__ == '__main__': - - PROJECT_HOME = '.' - resources_path = os.path.join(PROJECT_HOME, 'resources') - - today = datetime.today() - if today.weekday() == 5: - if today.hour > 20: - this_weekend = today + timedelta(days=(12 - today.weekday())) - else: - this_weekend = today + timedelta(days=(5 - today.weekday())) - elif today.weekday() == 6: - this_weekend = today + timedelta(days=(12 - today.weekday())) - else: - this_weekend = today + timedelta(days=(5 - today.weekday())) - - last_weekend = (this_weekend - timedelta(days=7)).strftime('%Y%m%d') - ymd = this_weekend.strftime('%Y%m%d') - - print("ymd: {}".format(ymd)) - - # 로또 예측 - practice = Practice(resources_path) - - # 데이터 수집 - lottoHistoryFile = PROJECT_HOME + '/resources/lotto_history' - lottoHistoryFileName = lottoHistoryFile + '.json' - with open(lottoHistoryFileName, "r", encoding='utf-8') as f: - for line in f: - if line != '\n': - last_json = json.loads(line) - - #ball = practice.craw(lottoHistoryFile, drwNo=last_json['drwNo'] + 1) - - result_json = {ymd: []} - - # 매주 고정 - practice.predict1(result_json[ymd]) - # 필터 기반 예측(기존/신규): 결과는 합친 후 정렬해서 predict1 결과 뒤에 붙인다. - predict2_json = [] - p_no, p_ball = practice.predict2(resources_path, ymd, predict2_json) - - predict3_json = [] - p_no3, p_ball3 = practice.predict3(resources_path, ymd, predict3_json) - - merged_predict = [] - practice._merge_unique_balls(merged_predict, predict2_json) - practice._merge_unique_balls(merged_predict, predict3_json) - merged_predict = practice._sorted_unique_balls(merged_predict) - - # predict1 결과에 merged_predict를 정렬된 순서로 append(중복 제거) - practice._merge_unique_balls(result_json[ymd], merged_predict) - if p_no3 == p_no: - p_ball = p_ball3 - - p_str = "[지난주] {}\n - {} 회차, {}\n[금주] {}\n - {} 회차\n[모델#25]\n".format(last_weekend, p_no, str(p_ball), ymd, (p_no + 1)) - for i, ball in enumerate(result_json[ymd]): - p_str += " {}. {}\n".format((i+1), str(ball)) - if (i+1) % 100 == 0: - practice.bot.sendMsg("{}".format(p_str)) - p_str = '' - - if len(result_json[ymd]) % 100 != 0: - practice.bot.sendMsg("{}".format(p_str)) - - size = len(result_json[ymd]) - print("size: {}".format(size)) - - # https://youtu.be/QjBsui8Ob14?si=4dC3q8p0Yu5ZWK1K - # https://www.youtube.com/watch?v=YwiHaa1KNwA - - print("done...") \ No newline at end of file diff --git a/practice_3_FilterTest.py b/practice_3_FilterTest.py deleted file mode 100644 index edd667f..0000000 --- a/practice_3_FilterTest.py +++ /dev/null @@ -1,216 +0,0 @@ -import os -import pandas as pd -import itertools -from filter_model_3 import BallFilter -import time -import datetime - -class FilterTest: - - ballFilter = None - - def __init__(self, resources_path): - lottoHistoryFileName = os.path.join(resources_path, 'lotto_history.json') - self.ballFilter = BallFilter(lottoHistoryFileName) - - return - - def find_filter_method(self, df_ball, filter_ball=None): - win_count = 0 - - no_filter_ball = {} - - printLog = True - filter_dic = {} - filter_dic_len = {} - filter_dic_1 = {} - filter_dic_2 = {} - for i in range(len(df_ball)-1, 19, -1): - - no = df_ball['no'].iloc[i] - answer = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = answer[1:7] - - filter_type = self.ballFilter.filter(ball=answer, no=no, until_end=True, df=df_ball) - filter_type = list(filter_type) - size = len(filter_type) - - if size == 0: - win_count += 1 - no_filter_ball[no] = answer - print("\t", no) - elif size == 1: - key = filter_type[0] - if key not in filter_dic_1: - filter_dic_1[key] = 1 - else: - filter_dic_1[key] += 1 - - if printLog: - print("\t", no, filter_type) - elif size == 2: - key = ','.join(filter_type) - if key not in filter_dic_2: - filter_dic_2[key] = 1 - else: - filter_dic_2[key] += 1 - - if printLog: - print("\t", no, filter_type) - else: - if printLog: - print("\t", no, filter_type) - - # 회차별 필터개수가 적은 것을 정렬하기 위함 - if size not in filter_dic_len: - filter_dic_len[size] = [] - filter_dic_len[size].append(filter_type) - - for f_t in filter_type: - if f_t not in filter_dic: - filter_dic[f_t] = 1 - else: - filter_dic[f_t] += 1 - - print("\n\t[필터 개수가 적은 것부터 최적화를 위함]") - sorted_filter_dic_len = sorted(filter_dic_len.keys()) - for filter_count in sorted_filter_dic_len: - for filter_type in filter_dic_len[filter_count]: - print("\t\t>{} > {}".format(filter_count, filter_type)) - - print("\n\t[걸러진 유일 필터]") - sorted_filter_dic_1 = sorted(filter_dic_1.items(), key=lambda x: x[1], reverse=True) - for i in range(len(sorted_filter_dic_1)): - print("\t\t>", sorted_filter_dic_1[i][0], "->", sorted_filter_dic_1[i][1]) - - print("\n\t[2개 필터에 걸린 경우]") - sorted_filter_dic_2 = sorted(filter_dic_2.items(), key=lambda x: x[1], reverse=True) - for i in range(len(sorted_filter_dic_2)): - print("\t\t>", sorted_filter_dic_2[i][0], "->", sorted_filter_dic_2[i][1]) - - print("\n\t[Filter 유형 별 걸린 개수]") - sorted_filter_dic = sorted(filter_dic.items(), key=lambda x: x[1], reverse=True) - for i in range(len(sorted_filter_dic)): - print("\t\t>", sorted_filter_dic[i][0], "->", sorted_filter_dic[i][1]) - - print("\n\t# 필터에 걸리지 않고 당첨된 회차") - print("\tcount: {:,} / total: {:,}".format(len(no_filter_ball), len(df_ball))) - for no in no_filter_ball: - print("\t\t>", no, no_filter_ball[no]) - print("\tcount: {:,} / total: {:,}".format(len(no_filter_ball), len(df_ball))) - - return win_count - - def find_final_candidates(self, no, df_ball, filter_ball=None): - final_candidates = [] - - generation_balls = list(range(1, 46)) - - nCr = list(itertools.combinations(generation_balls, 6)) - for idx, ball in enumerate(nCr): - - if idx % 1000000 == 0: - print(" - {} processed...".format(idx)) - - if filter_ball is not None and 0 < len(set(ball) & set(filter_ball)): - continue - - filter_type = self.ballFilter.filter(ball=ball, no=no, until_end=False, df=df_ball) - filter_size = len(filter_type) - - if filter_size: - continue - - final_candidates.append(ball) - - return final_candidates - - def check_filter_method(self, df_ball, p_win_count, filter_ball=None): - - win_count = 0 - for i in range(len(df_ball)-1, 0, -1): - - no = df_ball['no'].iloc[i] - answer = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = answer[1:7] - - if filter_ball is not None and len(set(answer) & set(filter_ball)): - continue - - filter_type = self.ballFilter.extract_final_candidates(answer, no=no, until_end=True, df=df_ball) - - if len(filter_type) == 0: - win_count += 1 - print("\t\t>{}. {}".format(no, answer)) - - print("\n\t> {} / {} p_win_count, {} total".format( win_count, p_win_count, len(df_ball)-1) ) - - return - - def validate(self, df_ball, nos=None): - win_history = {} - - for no in nos: - print(no, "processing...") - answer = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = answer[1:7] - - generation_balls = list(range(1, 46)) - nCr = list(itertools.combinations(generation_balls, 6)) - for idx, ball in enumerate(nCr): - if idx % 1000000 == 0: - print(" - {} processed...".format(idx)) - ball = list(ball) - filter_type = self.ballFilter.extract_final_candidates(ball, no=no, until_end=True, df=df_ball) - if 0 == len(filter_type) and len(set(answer)&set(ball))==6: - win_history[no] = answer - print("win.. no: {}, answer: {}".format(no, str(answer))) - break - - return win_history - - -if __name__ == '__main__': - - resources_path = 'resources' - - lottoHistoryFileName = os.path.join(resources_path, 'lotto_history.txt') - df_ball = pd.read_csv(lottoHistoryFileName, header=None) - df_ball.columns = ['no', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'bn'] - - filter_ball=[] - filterTest = FilterTest(resources_path) - - print("STEP #1. 필터 방법 추출") - start = time.time() - win_count = filterTest.find_filter_method(df_ball, filter_ball) - process_time = datetime.timedelta(seconds=time.time() - start) - print("process_time: ", process_time) - - """ - print("\n\n") - no = df_ball['no'].values[-1] - ball = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = ball[1:7] - - print("STEP #0. 최종 후보 선정") - start = time.time() - final_candidates = filterTest.find_final_candidates(no, df_ball, filter_ball=None) - process_time = datetime.timedelta(seconds=time.time() - start) - print("process_time: ", process_time) - - print(" > size: {}".format(len(final_candidates))) - file_name = os.path.join(resources_path, 'final_candidates.biz_a1.txt') - with open(file_name, 'w+') as outFp: - for ball in final_candidates: - ball_str = [str(b) for b in answer] - outFp.write("{}\n".format(','.join(ball_str))) - - print('{}회, 정답: {}\n'.format(no, str(answer))) - """ - - #print("\n\n") - #print("STEP #2. 당첨 회수 확인") - #filterTest.check_filter_method(df_ball, win_count) - - # 오리지널 버전 (자질 파일에 고정): 당첨 22개 \ No newline at end of file diff --git a/practice_3_new.py b/practice_3_new.py deleted file mode 100644 index b24aa01..0000000 --- a/practice_3_new.py +++ /dev/null @@ -1,490 +0,0 @@ -# 웹 호출 라이브러리를 호출합니다. -import time -import requests - -import json -import os -import copy -import pandas as pd -import itertools -from datetime import datetime, timedelta -from TelegramBot import TelegramBot - -from filter_model_3 import BallFilter - -class Practice: - - bot = None - preprocessor = None - predictor = None - - extract_count = None - TARGET_MIN_SURVIVORS = 30 - TARGET_MAX_SURVIVORS = 150 - PREDICT_TIMEOUT_SECONDS = 180 - - def __init__(self, resources_path): - self.bot = TelegramBot() - self.resources_path = resources_path - - return - - # 로또 당첨 데이터를 수집해서 파일로 저장합니다. - # lottoHistoryFile: 로또 당첨 데이터를 저장할 파일 - def craw(self, lottoHistoryFile, drwNo=None): - - ball = None - if drwNo != None: - # 로또 데이터를 저장할 파일을 선언합니다. - jsonFp = open(lottoHistoryFile + ".json", 'a', encoding="utf-8") - textFp = open(lottoHistoryFile + ".txt", 'a', encoding="utf-8") - - url = 'https://dhlottery.co.kr/common.do?method=getLottoNumber&drwNo=' + str(drwNo) - # URL을 호출합니다. - res = requests.post(url) - # 호출한 결과에 대해서 Json 포맷을 가져옵니다. - result = res.json() - - if result['returnValue'] != 'success': - return None - - # 가져온 Json 포맷을 파일로 저장합니다. - jsonFp.write(json.dumps(result, ensure_ascii=False) + "\n") - textFp.write("%d,%d,%d,%d,%d,%d,%d,%d\n" % (drwNo, result['drwtNo1'], result['drwtNo2'], result['drwtNo3'], result['drwtNo4'], result['drwtNo5'], result['drwtNo6'], result['bnusNo'])) - print("%d,%d,%d,%d,%d,%d,%d,%d" % (drwNo, result['drwtNo1'], result['drwtNo2'], result['drwtNo3'], result['drwtNo4'], result['drwtNo5'], result['drwtNo6'], result['bnusNo'])) - - ball = [result['drwtNo1'], result['drwtNo2'], result['drwtNo3'], result['drwtNo4'], result['drwtNo5'], result['drwtNo6'], result['bnusNo']] - else: - # 로또 데이터를 저장할 파일을 선언합니다. - jsonFp = open(lottoHistoryFile + ".json", 'w', encoding="utf-8") - textFp = open(lottoHistoryFile + ".txt", 'w', encoding="utf-8") - - # 1회차부터 지정된 회차까지 로또 당첨 번호를 수집합니다. - idx = 1 - while True: - # 1회차부터 지정된 회차까지의 URL을 생성합니다. - url = 'https://dhlottery.co.kr/common.do?method=getLottoNumber&drwNo=' + str(idx) - # URL을 호출합니다. - res = requests.post(url) - # 호출한 결과에 대해서 Json 포맷을 가져옵니다. - result = res.json() - if result['returnValue'] != 'success': - break - # 가져온 Json 포맷을 파일로 저장합니다. - jsonFp.write(json.dumps(result, ensure_ascii=False) + "\n") - textFp.write("%d,%d,%d,%d,%d,%d,%d,%d\n" % (idx, result['drwtNo1'], result['drwtNo2'], result['drwtNo3'], result['drwtNo4'], result['drwtNo5'], result['drwtNo6'], result['bnusNo'])) - print("%d,%d,%d,%d,%d,%d,%d,%d" % (idx, result['drwtNo1'], result['drwtNo2'], result['drwtNo3'], result['drwtNo4'], result['drwtNo5'], result['drwtNo6'], result['bnusNo'])) - ball = [result['drwtNo1'], result['drwtNo2'], result['drwtNo3'], result['drwtNo4'], result['drwtNo5'], result['drwtNo6'], result['bnusNo']] - idx += 1 - time.sleep(0.5) - # 저장한 파일을 종료합니다. - jsonFp.close() - textFp.close() - - return ball - - def predict1(self, result_json): - result_json.append([6, 7, 10, 11, 20, 45]) - result_json.append([2, 7, 17, 28, 35, 39]) - result_json.append([6, 10, 19, 25, 33, 35]) - result_json.append([3, 17, 20, 24, 35, 45]) - result_json.append([5, 15, 18, 29, 36, 41]) - result_json.append([6, 15, 20, 23, 37, 43]) - result_json.append([8, 15, 19, 23, 38, 41]) - result_json.append([5, 11, 19, 24, 40, 45]) - result_json.append([9, 16, 18, 23, 35, 43]) - result_json.append([7, 13, 19, 28, 33, 44]) - result_json.append([7, 11, 18, 29, 37, 42]) - return - - def predict2(self, resources_path, ymd, result_json): - candidates = [i for i in range(1, 46)] - lottoHistoryFileName = os.path.join(resources_path, 'lotto_history.json') - no = BallFilter(lottoHistoryFileName).getNextNo(ymd) - print("회차: {}".format(no)) - predict_start_ts = time.time() - deadline_ts = predict_start_ts + self.PREDICT_TIMEOUT_SECONDS - - lottoHistoryFileName = os.path.join(resources_path, 'lotto_history.txt') - df_ball = pd.read_csv(lottoHistoryFileName, header=None) - df_ball.columns = ['no', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'bn'] - - p_ball = df_ball[df_ball['no'] == no - 1].values.tolist()[0] - p_no = p_ball[0] - p_ball = sorted(p_ball[1:7]) - - # 기본/강화/완화 단계별 ruleset - base_ruleset = self._get_base_ruleset() - tighten_rulesets = [ - self._build_ruleset( - base_ruleset=base_ruleset, - enabled_overrides={ - "paper_patterns": True, - "ban_triples_legacy": True, - "all_in_previous7": True, - "previous_neighbors": True, - }, - allowed_overrides={ - "ac_value": [8, 9], - "uniq_last_digit_count": [4, 5], - "even_count": [2, 3, 4], - }, - ), - self._build_ruleset( - base_ruleset=base_ruleset, - enabled_overrides={ - "paper_patterns": True, - "ban_triples_legacy": True, - "all_in_previous7": True, - "previous_neighbors": True, - }, - allowed_overrides={ - "ac_value": [8, 9], - "uniq_last_digit_count": [4, 5], - "even_count": [2, 3, 4], - "sum": [112, 114, 121, 123, 126, 127, 131, 132, 138, 146, 148], - "sum_prev_diff": [13, 14, 17, 18, 26, 28, 29, 30, 32, 39, 40], - }, - ), - ] - relax_rulesets = [ - self._build_ruleset( - base_ruleset=base_ruleset, - enabled_overrides={ - "paper_patterns": False, - "ban_triples_legacy": False, - }, - ), - self._build_ruleset( - base_ruleset=base_ruleset, - enabled_overrides={ - "paper_patterns": False, - "ban_triples_legacy": False, - "previous_neighbors": False, - "all_in_previous7": False, - }, - ), - self._build_ruleset( - base_ruleset=base_ruleset, - enabled_overrides={ - "paper_patterns": False, - "ban_triples_legacy": False, - "previous_neighbors": False, - "all_in_previous7": False, - "weeks_8_count": False, - "weeks_12_count": False, - "weeks_16_count": False, - "weeks_20_count": False, - }, - ), - ] - - min_survivors = self.TARGET_MIN_SURVIVORS - max_survivors = self.TARGET_MAX_SURVIVORS - chosen = [] - stage_name = "base" - - current_info = self._collect_candidates( - candidates=candidates, - no=no, - df_ball=df_ball, - ruleset=base_ruleset, - stop_when_gt=max_survivors, - stage_name="base", - predict_start_ts=predict_start_ts, - deadline_ts=deadline_ts, - ) - current = current_info["candidates"] - if current_info["timed_out"]: - chosen = self._finalize_on_timeout(current, p_ball, min_survivors, max_survivors) - stage_name = "base_timeout_fallback" - print("candidate_stage: {}, survivors: {}".format(stage_name, len(chosen))) - for ball in chosen: - result_json.append(ball) - return p_no, p_ball - - if min_survivors <= len(current) <= max_survivors: - chosen = current - elif len(current) > max_survivors: - chosen = current - stage_name = "base_overflow" - for idx, rs in enumerate(tighten_rulesets, start=1): - t_info = self._collect_candidates( - candidates=candidates, - no=no, - df_ball=df_ball, - ruleset=rs, - stop_when_gt=max_survivors, - stage_name="tighten_{}".format(idx), - predict_start_ts=predict_start_ts, - deadline_ts=deadline_ts, - ) - t = t_info["candidates"] - if t_info["timed_out"]: - chosen = self._finalize_on_timeout(t, p_ball, min_survivors, max_survivors) - stage_name = "tighten_{}_timeout_fallback".format(idx) - break - if min_survivors <= len(t) <= max_survivors: - chosen = t - stage_name = "tighten_{}".format(idx) - break - if len(t) <= max_survivors: - chosen = t - stage_name = "tighten_{}".format(idx) - if len(chosen) > max_survivors: - # 상한 가드 강제 적용: 품질 점수 상위 N개만 사용 - full_info = self._collect_candidates( - candidates=candidates, - no=no, - df_ball=df_ball, - ruleset=tighten_rulesets[-1], - stop_when_gt=None, - stage_name="tighten_full_rank", - predict_start_ts=predict_start_ts, - deadline_ts=deadline_ts, - ) - full_for_ranking = full_info["candidates"] - if full_info["timed_out"]: - chosen = self._finalize_on_timeout(full_for_ranking, p_ball, min_survivors, max_survivors) - stage_name = "tighten_rank_timeout_fallback" - else: - chosen = self._rank_and_trim(full_for_ranking, p_ball, max_survivors) - stage_name = "tighten_rank_trim" - else: - chosen = current - stage_name = "base_underflow" - for idx, rs in enumerate(relax_rulesets, start=1): - # relax는 하한(min_survivors)만 채우면 충분하므로 조기 종료 - r_info = self._collect_candidates( - candidates=candidates, - no=no, - df_ball=df_ball, - ruleset=rs, - stop_when_gt=None, - stop_when_gte=min_survivors, - stage_name="relax_{}".format(idx), - predict_start_ts=predict_start_ts, - deadline_ts=deadline_ts, - ) - r = r_info["candidates"] - chosen = r - stage_name = "relax_{}".format(idx) - if r_info["timed_out"]: - chosen = self._finalize_on_timeout(r, p_ball, min_survivors, max_survivors) - stage_name = "relax_{}_timeout_fallback".format(idx) - break - if len(r) >= min_survivors: - break - - if len(chosen) == 0: - # 0개 생존 방지: 가장 완화된 규칙에서도 0개면 직전 결과와 유사한 조합으로 최소 개수 확보 - stage_name = "relax_zero_fallback" - chosen = self._fallback_candidates_from_prev(p_ball, min_survivors) - elif len(chosen) < min_survivors: - # 하한 가드: 부족분은 완화 후보/고정 후보 기반으로 보강 - stage_name = "{}_fill".format(stage_name) - fill = self._fallback_candidates_from_prev(p_ball, min_survivors - len(chosen), exclude=set(tuple(x) for x in chosen)) - chosen.extend(fill) - - print("candidate_stage: {}, survivors: {}".format(stage_name, len(chosen))) - for ball in chosen: - result_json.append(ball) - return p_no, p_ball - - def _get_base_ruleset(self): - history_json = os.path.join(self.resources_path, "lotto_history.json") - base_filter = BallFilter(history_json) - return copy.deepcopy(base_filter.m1.ruleset) - - def _build_ruleset(self, base_ruleset, enabled_overrides=None, allowed_overrides=None): - ruleset = copy.deepcopy(base_ruleset) - ruleset.setdefault("filters", {}) - enabled_overrides = enabled_overrides or {} - allowed_overrides = allowed_overrides or {} - for key, value in enabled_overrides.items(): - ruleset["filters"].setdefault(key, {}) - ruleset["filters"][key]["enabled"] = bool(value) - for key, values in allowed_overrides.items(): - ruleset["filters"].setdefault(key, {}) - ruleset["filters"][key]["enabled"] = True - ruleset["filters"][key]["allowed"] = list(values) - return ruleset - - def _collect_candidates( - self, - candidates, - no, - df_ball, - ruleset, - stop_when_gt=None, - stop_when_gte=None, - stage_name="base", - predict_start_ts=None, - deadline_ts=None, - ): - lottoHistoryFileName = os.path.join(self.resources_path, "lotto_history.json") - ballFilter = BallFilter(lottoHistoryFileName, ruleset=ruleset) - result = [] - last_idx = 0 - for idx, ball in enumerate(itertools.combinations(candidates, 6), start=1): - last_idx = idx - if deadline_ts is not None and deadline_ts <= time.time(): - elapsed = (time.time() - predict_start_ts) if predict_start_ts is not None else 0.0 - print(" - [{}] timeout after {:,} processed (elapsed: {:.1f}s, survivors: {:,})".format(stage_name, idx, elapsed, len(result))) - return { - "candidates": result, - "timed_out": True, - "processed": idx, - } - if idx % 1000000 == 0: - elapsed = (time.time() - predict_start_ts) if predict_start_ts is not None else 0.0 - print(" - [{}] {:,} processed... (elapsed: {:.1f}s, survivors: {:,})".format(stage_name, idx, elapsed, len(result))) - b = list(ball) - if len(ballFilter.filter(ball=b, no=no, until_end=False, df=df_ball)) == 0: - result.append(b) - if stop_when_gt is not None and len(result) > stop_when_gt: - return { - "candidates": result, - "timed_out": False, - "processed": idx, - } - if stop_when_gte is not None and len(result) >= stop_when_gte: - return { - "candidates": result, - "timed_out": False, - "processed": idx, - } - return { - "candidates": result, - "timed_out": False, - "processed": last_idx, - } - - def _finalize_on_timeout(self, partial_candidates, prev_ball, min_survivors, max_survivors): - chosen = list(partial_candidates) - if len(chosen) > max_survivors: - chosen = self._rank_and_trim(chosen, prev_ball, max_survivors) - elif len(chosen) < min_survivors: - fill = self._fallback_candidates_from_prev( - prev_ball, - min_survivors - len(chosen), - exclude=set(tuple(x) for x in chosen), - ) - chosen.extend(fill) - return chosen - - def _rank_and_trim(self, candidates, prev_ball, limit): - scored = [(self._score_candidate(ball, prev_ball), ball) for ball in candidates] - scored.sort(key=lambda x: x[0]) - return [ball for _, ball in scored[:limit]] - - def _score_candidate(self, ball, prev_ball): - sum_diff = abs(sum(ball) - sum(prev_ball)) - even_cnt = len([x for x in ball if x % 2 == 0]) - uniq_last = len(set([x % 10 for x in ball])) - contiguous_penalty = 0 - s = sorted(ball) - for i in range(1, len(s)): - if s[i] - s[i - 1] == 1: - contiguous_penalty += 1 - score = 0 - score += sum_diff - score += abs(even_cnt - 3) * 2 - score += abs(uniq_last - 5) * 2 - score += contiguous_penalty - return score - - def _fallback_candidates_from_prev(self, prev_ball, need_count, exclude=None): - exclude = exclude or set() - seed = sorted(prev_ball) - out = [] - delta_patterns = [ - (0, 0, 0, 0, 0, 0), - (-1, 0, 0, 0, 0, 1), - (0, -1, 0, 0, 1, 0), - (0, 0, -1, 1, 0, 0), - (-2, 0, 0, 0, 0, 2), - (0, -2, 0, 0, 2, 0), - (0, 0, -2, 2, 0, 0), - (-1, -1, 0, 0, 1, 1), - (1, 0, -1, 0, 0, 0), - (0, 1, 0, -1, 0, 0), - (1, -1, 1, -1, 1, -1), - (-1, 1, -1, 1, -1, 1), - ] - shift = 0 - while len(out) < need_count and shift <= 8: - for delta in delta_patterns: - cand = [seed[i] + delta[i] for i in range(6)] - cand = [min(45, max(1, v + shift)) for v in cand] - cand = sorted(cand) - if len(set(cand)) != 6: - continue - t = tuple(cand) - if t in exclude: - continue - exclude.add(t) - out.append(cand) - if len(out) >= need_count: - break - shift += 1 - return out - -if __name__ == '__main__': - - PROJECT_HOME = '.' - resources_path = os.path.join(PROJECT_HOME, 'resources') - - today = datetime.today() - if today.weekday() == 5: - if today.hour > 20: - this_weekend = today + timedelta(days=(12 - today.weekday())) - else: - this_weekend = today + timedelta(days=(5 - today.weekday())) - elif today.weekday() == 6: - this_weekend = today + timedelta(days=(12 - today.weekday())) - else: - this_weekend = today + timedelta(days=(5 - today.weekday())) - - last_weekend = (this_weekend - timedelta(days=7)).strftime('%Y%m%d') - ymd = this_weekend.strftime('%Y%m%d') - - print("ymd: {}".format(ymd)) - - # 로또 예측 - practice = Practice(resources_path) - - # 데이터 수집 - lottoHistoryFile = PROJECT_HOME + '/resources/lotto_history' - lottoHistoryFileName = lottoHistoryFile + '.json' - with open(lottoHistoryFileName, "r", encoding='utf-8') as f: - for line in f: - if line != '\n': - last_json = json.loads(line) - - #ball = practice.craw(lottoHistoryFile, drwNo=last_json['drwNo'] + 1) - - result_json = {ymd: []} - - # 매주 고정 - practice.predict1(result_json[ymd]) - # 필터 기반 예측 - p_no, p_ball = practice.predict2(resources_path, ymd, result_json[ymd]) - - p_str = "[지난주] {}\n - {} 회차, {}\n[금주] {}\n - {} 회차\n[모델#25]\n".format(last_weekend, p_no, str(p_ball), ymd, (p_no + 1)) - for i, ball in enumerate(result_json[ymd]): - p_str += " {}. {}\n".format((i+1), str(ball)) - if (i+1) % 100 == 0: - practice.bot.sendMsg("{}".format(p_str)) - p_str = '' - - if len(result_json[ymd]) % 100 != 0: - practice.bot.sendMsg("{}".format(p_str)) - - size = len(result_json[ymd]) - print("size: {}".format(size)) - - # https://youtu.be/QjBsui8Ob14?si=4dC3q8p0Yu5ZWK1K - # https://www.youtube.com/watch?v=YwiHaa1KNwA - - print("done...") \ No newline at end of file diff --git a/practice_3_old.py b/practice_3_old.py deleted file mode 100644 index b980216..0000000 --- a/practice_3_old.py +++ /dev/null @@ -1,189 +0,0 @@ -# 웹 호출 라이브러리를 호출합니다. -import time -import requests -from DataCrawler import DataCrawler - -import json -import os -import pandas as pd -import itertools -from datetime import datetime, timedelta -from TelegramBot import TelegramBot - -from filter_model_3 import BallFilter - -class Practice: - - bot = None - preprocessor = None - predictor = None - - extract_count = None - - def __init__(self, resources_path): - self.bot = TelegramBot() - - return - - # 로또 당첨 데이터를 수집해서 파일로 저장합니다. - # lottoHistoryFile: 로또 당첨 데이터를 저장할 파일 - def craw(self, lottoHistoryFile, drwNo=None): - - ball = None - if drwNo != None: - # 로또 데이터를 저장할 파일을 선언합니다. - jsonFp = open(lottoHistoryFile + ".json", 'a', encoding="utf-8") - textFp = open(lottoHistoryFile + ".txt", 'a', encoding="utf-8") - - url = 'https://dhlottery.co.kr/common.do?method=getLottoNumber&drwNo=' + str(drwNo) - # URL을 호출합니다. - res = requests.post(url) - # 호출한 결과에 대해서 Json 포맷을 가져옵니다. - result = res.json() - - if result['returnValue'] != 'success': - return None - - # 가져온 Json 포맷을 파일로 저장합니다. - jsonFp.write(json.dumps(result, ensure_ascii=False) + "\n") - textFp.write("%d,%d,%d,%d,%d,%d,%d,%d\n" % (drwNo, result['drwtNo1'], result['drwtNo2'], result['drwtNo3'], result['drwtNo4'], result['drwtNo5'], result['drwtNo6'], result['bnusNo'])) - print("%d,%d,%d,%d,%d,%d,%d,%d" % (drwNo, result['drwtNo1'], result['drwtNo2'], result['drwtNo3'], result['drwtNo4'], result['drwtNo5'], result['drwtNo6'], result['bnusNo'])) - - ball = [result['drwtNo1'], result['drwtNo2'], result['drwtNo3'], result['drwtNo4'], result['drwtNo5'], result['drwtNo6'], result['bnusNo']] - else: - # 로또 데이터를 저장할 파일을 선언합니다. - jsonFp = open(lottoHistoryFile + ".json", 'w', encoding="utf-8") - textFp = open(lottoHistoryFile + ".txt", 'w', encoding="utf-8") - - # 1회차부터 지정된 회차까지 로또 당첨 번호를 수집합니다. - idx = 1 - while True: - # 1회차부터 지정된 회차까지의 URL을 생성합니다. - url = 'https://dhlottery.co.kr/common.do?method=getLottoNumber&drwNo=' + str(idx) - # URL을 호출합니다. - res = requests.post(url) - # 호출한 결과에 대해서 Json 포맷을 가져옵니다. - result = res.json() - if result['returnValue'] != 'success': - break - # 가져온 Json 포맷을 파일로 저장합니다. - jsonFp.write(json.dumps(result, ensure_ascii=False) + "\n") - textFp.write("%d,%d,%d,%d,%d,%d,%d,%d\n" % (idx, result['drwtNo1'], result['drwtNo2'], result['drwtNo3'], result['drwtNo4'], result['drwtNo5'], result['drwtNo6'], result['bnusNo'])) - print("%d,%d,%d,%d,%d,%d,%d,%d" % (idx, result['drwtNo1'], result['drwtNo2'], result['drwtNo3'], result['drwtNo4'], result['drwtNo5'], result['drwtNo6'], result['bnusNo'])) - ball = [result['drwtNo1'], result['drwtNo2'], result['drwtNo3'], result['drwtNo4'], result['drwtNo5'], result['drwtNo6'], result['bnusNo']] - idx += 1 - time.sleep(0.5) - # 저장한 파일을 종료합니다. - jsonFp.close() - textFp.close() - - return ball - - def predict1(self, result_json): - result_json.append([6, 7, 10, 11, 20, 45]) - result_json.append([2, 7, 17, 28, 35, 39]) - result_json.append([6, 10, 19, 25, 33, 35]) - result_json.append([3, 17, 20, 24, 35, 45]) - result_json.append([5, 15, 18, 29, 36, 41]) - result_json.append([6, 15, 20, 23, 37, 43]) - result_json.append([8, 15, 19, 23, 38, 41]) - result_json.append([5, 11, 19, 24, 40, 45]) - result_json.append([9, 16, 18, 23, 35, 43]) - result_json.append([7, 13, 19, 28, 33, 44]) - result_json.append([7, 11, 18, 29, 37, 42]) - return - - def predict2(self, resources_path, ymd, result_json): - - candidates = [i for i in range(1, 46)] - - lottoHistoryFileName = os.path.join(resources_path, 'lotto_history.json') - ballFilter = BallFilter(lottoHistoryFileName) - no = ballFilter.getNextNo(ymd) - print("회차: {}".format(no)) - - lottoHistoryFileName = os.path.join(resources_path, 'lotto_history.txt') - df_ball = pd.read_csv(lottoHistoryFileName, header=None) - df_ball.columns = ['no', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'bn'] - - #filter_ball=[1,2,4,6,10,11,11,17,18,20,21,22,23,24,26,27,28,30,31,32,33,34,37,38,39,40,42,44] - nCr = list(itertools.combinations(candidates, 6)) - for idx, ball in enumerate(nCr): - - if idx % 1000000 == 0: - print(" - {} processed...".format(idx)) - - ball = list(ball) - - filter_type = ballFilter.filter(ball=ball, no=no, until_end=False, df=df_ball) - filter_size = len(filter_type) - - if 0 < filter_size: - continue - - result_json.append(ball) - - p_ball = df_ball[df_ball['no'] == no - 1].values.tolist()[0] - p_no = p_ball[0] - p_ball = p_ball[1:7] - - return p_no, p_ball - -if __name__ == '__main__': - - PROJECT_HOME = '.' - resources_path = os.path.join(PROJECT_HOME, 'resources') - - today = datetime.today() - if today.weekday() == 5: - if today.hour > 20: - this_weekend = today + timedelta(days=(12 - today.weekday())) - else: - this_weekend = today + timedelta(days=(5 - today.weekday())) - elif today.weekday() == 6: - this_weekend = today + timedelta(days=(12 - today.weekday())) - else: - this_weekend = today + timedelta(days=(5 - today.weekday())) - - last_weekend = (this_weekend - timedelta(days=7)).strftime('%Y%m%d') - ymd = this_weekend.strftime('%Y%m%d') - - print("ymd: {}".format(ymd)) - - # 로또 예측 - practice = Practice(resources_path) - - # 데이터 수집 - lottoHistoryFile = PROJECT_HOME + '/resources/lotto_history' - lottoHistoryFileName = lottoHistoryFile + '.json' - with open(lottoHistoryFileName, "r", encoding='utf-8') as f: - for line in f: - if line != '\n': - last_json = json.loads(line) - - #ball = practice.craw(lottoHistoryFile, drwNo=last_json['drwNo'] + 1) - - result_json = {ymd: []} - - # 매주 고정 - practice.predict1(result_json[ymd]) - # 필터 기반 예측 - p_no, p_ball = practice.predict2(resources_path, ymd, result_json[ymd]) - - p_str = "[지난주] {}\n - {} 회차, {}\n[금주] {}\n - {} 회차\n[모델#25]\n".format(last_weekend, p_no, str(p_ball), ymd, (p_no + 1)) - for i, ball in enumerate(result_json[ymd]): - p_str += " {}. {}\n".format((i+1), str(ball)) - if (i+1) % 100 == 0: - practice.bot.sendMsg("{}".format(p_str)) - p_str = '' - - if len(result_json[ymd]) % 100 != 0: - practice.bot.sendMsg("{}".format(p_str)) - - size = len(result_json[ymd]) - print("size: {}".format(size)) - - # https://youtu.be/QjBsui8Ob14?si=4dC3q8p0Yu5ZWK1K - # https://www.youtube.com/watch?v=YwiHaa1KNwA - - print("done...") \ No newline at end of file diff --git a/resources/lotto_history.json b/resources/lotto_history.json index b864e84..931614f 100644 --- a/resources/lotto_history.json +++ b/resources/lotto_history.json @@ -1216,3 +1216,4 @@ {"returnValue": "success", "drwNoDate": "2026-03-21", "drwNo": 1216, "drwtNo1": 3, "drwtNo2": 10, "drwtNo3": 14, "drwtNo4": 15, "drwtNo5": 23, "drwtNo6": 24, "bnusNo": 25} {"returnValue": "success", "drwNoDate": "2026-03-28", "drwNo": 1217, "drwtNo1": 8, "drwtNo2": 10, "drwtNo3": 15, "drwtNo4": 20, "drwtNo5": 29, "drwtNo6": 31, "bnusNo": 41} {"returnValue": "success", "drwNoDate": "2026-04-04", "drwNo": 1218, "drwtNo1": 3, "drwtNo2": 28, "drwtNo3": 31, "drwtNo4": 32, "drwtNo5": 42, "drwtNo6": 45, "bnusNo": 25} +{"returnValue": "success", "drwNoDate": "2026-04-11", "drwNo": 1219, "drwtNo1": 1, "drwtNo2": 2, "drwtNo3": 15, "drwtNo4": 28, "drwtNo5": 39, "drwtNo6": 45, "bnusNo": 31} diff --git a/resources/lotto_history.txt b/resources/lotto_history.txt index 5ad2bfd..a214d15 100644 --- a/resources/lotto_history.txt +++ b/resources/lotto_history.txt @@ -1216,3 +1216,4 @@ 1216,3,10,14,15,23,24,25 1217,8,10,15,20,29,31,41 1218,3,28,31,32,42,45,25 +1219,1,2,15,28,39,45,31 diff --git a/review_1.py b/review_1.py deleted file mode 100644 index 5bd8a35..0000000 --- a/review_1.py +++ /dev/null @@ -1,99 +0,0 @@ -import os -import time -import datetime -import pandas as pd -import itertools -from filter_model_1 import BallFilter - -class FilterTestReview: - - ballFilter = None - - def __init__(self, resources_path): - lottoHistoryFileName = os.path.join(resources_path, 'lotto_history.json') - self.ballFilter = BallFilter(lottoHistoryFileName) - - return - - def validate(self, df_ball, nos=None): - - win_history = {} - win_history_size = {} - - for no in nos: - - print("[{} 회차]".format(no)) - balls = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = balls[1:7].copy() # copy()로 복사 - bonus = balls[7] - - final_candidates = [] - win_dic = {1: [], 2: [], 3: [], 4: [], 5: []} - generation_balls = list(range(1, 46)) - nCr = list(itertools.combinations(generation_balls, 6)) - for idx, ball in enumerate(nCr): - - if idx % 1000000 == 0: - print(" - {} processed...".format(idx)) - - ball = list(ball) - - filter_type = self.ballFilter.filter(ball=ball, no=no, until_end=False, df=df_ball) - filter_size = len(filter_type) - - if 0 < filter_size: - continue - - final_candidates.append(ball) - - match = len(set(ball) & set(answer)) - if match == 6: - if no not in win_history: # 중복 방지 - win_history[no] = answer.copy() # copy()로 복사 - if ball not in win_dic[1]: # 같은 조합 중복 방지 - win_dic[1].append(ball.copy()) # copy()로 복사 - - else: - if match == 3: - win_dic[5].append(ball) - elif match == 4: - win_dic[4].append(ball) - elif match == 5: - # 2등 판별: 5개 맞고 보너스 번호 포함 - if bonus in ball: - win_dic[2].append(ball) - else: - win_dic[3].append(ball) - - win_history_size[no] = len(final_candidates) - - print("no: {}, answer: {}, size: {}".format(no, answer, len(final_candidates))) - print(" > 1등: {}, 2등: {}, 3등: {}, 4등: {}, 5등: {}".format(len(win_dic[1]), len(win_dic[2]), len(win_dic[3]), len(win_dic[4]), len(win_dic[5]))) - - return win_history, win_history_size - - -if __name__ == '__main__': - - PROJECT_HOME = '.' - resources_path = os.path.join(PROJECT_HOME, 'resources') - - lottoHistoryFileName = os.path.join(resources_path, 'lotto_history.txt') - df_ball = pd.read_csv(lottoHistoryFileName, header=None) - df_ball.columns = ['no', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'bn'] - - filterTestReview = FilterTestReview(resources_path) - - start = time.time() - #win_history = filterTest.validate(df_ball, nos =[1046,1022,1004,900,869,816,797,696,574,524,523,461,356,324,303,289,147,71], filter_ball = [1,2,4,6,10,11,11,17,18,20,21,22,23,24,26,27,28,30,31,32,33,34,37,38,39,40,42,44]) - win_history, win_history_size = filterTestReview.validate( - df_ball, - #nos=range(1126, 21, -1), - nos=[1057,1046,1022,900,841,816,696,593,574,426,356,324,303,245,147,139,71]) - process_time = datetime.timedelta(seconds=time.time() - start) - print("process_time: ", process_time) - - print("{} 회 당첨".format(len(win_history))) - sorted_win_history = sorted(win_history.keys()) - for i in range(len(sorted_win_history)): - print("\t>{} > {} ({})".format(sorted_win_history[i], win_history[sorted_win_history[i]], win_history_size[sorted_win_history[i]])) \ No newline at end of file diff --git a/review_2.py b/review_2.py deleted file mode 100644 index 985de35..0000000 --- a/review_2.py +++ /dev/null @@ -1,99 +0,0 @@ -import os -import time -import datetime -import pandas as pd -import itertools -from filter_model_2 import BallFilter - -class FilterTestReview: - - ballFilter = None - - def __init__(self, resources_path): - lottoHistoryFileName = os.path.join(resources_path, 'lotto_history.json') - self.ballFilter = BallFilter(lottoHistoryFileName) - - return - - def validate(self, df_ball, nos=None): - - win_history = {} - win_history_size = {} - - for no in nos: - - print("[{} 회차]".format(no)) - balls = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = balls[1:7].copy() # copy()로 복사 - bonus = balls[7] - - final_candidates = [] - win_dic = {1: [], 2: [], 3: [], 4: [], 5: []} - generation_balls = list(range(1, 46)) - nCr = list(itertools.combinations(generation_balls, 6)) - for idx, ball in enumerate(nCr): - - if idx % 1000000 == 0: - print(" - {} processed...".format(idx)) - - ball = list(ball) - - filter_type = self.ballFilter.filter(ball=ball, no=no, until_end=False, df=df_ball) - filter_size = len(filter_type) - - if 0 < filter_size: - continue - - final_candidates.append(ball) - - match = len(set(ball) & set(answer)) - if match == 6: - if no not in win_history: # 중복 방지 - win_history[no] = answer.copy() # copy()로 복사 - if ball not in win_dic[1]: # 같은 조합 중복 방지 - win_dic[1].append(ball.copy()) # copy()로 복사 - - else: - if match == 3: - win_dic[5].append(ball) - elif match == 4: - win_dic[4].append(ball) - elif match == 5: - # 2등 판별: 5개 맞고 보너스 번호 포함 - if bonus in ball: - win_dic[2].append(ball) - else: - win_dic[3].append(ball) - - win_history_size[no] = len(final_candidates) - - print("no: {}, answer: {}, size: {}".format(no, answer, len(final_candidates))) - print(" > 1등: {}, 2등: {}, 3등: {}, 4등: {}, 5등: {}".format(len(win_dic[1]), len(win_dic[2]), len(win_dic[3]), len(win_dic[4]), len(win_dic[5]))) - - return win_history, win_history_size - - -if __name__ == '__main__': - - PROJECT_HOME = '.' - resources_path = os.path.join(PROJECT_HOME, 'resources') - - lottoHistoryFileName = os.path.join(resources_path, 'lotto_history.txt') - df_ball = pd.read_csv(lottoHistoryFileName, header=None) - df_ball.columns = ['no', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'bn'] - - filterTestReview = FilterTestReview(resources_path) - - start = time.time() - #win_history = filterTest.validate(df_ball, nos =[1046,1022,1004,900,869,816,797,696,574,524,523,461,356,324,303,289,147,71], filter_ball = [1,2,4,6,10,11,11,17,18,20,21,22,23,24,26,27,28,30,31,32,33,34,37,38,39,40,42,44]) - win_history, win_history_size = filterTestReview.validate( - df_ball, - #nos=range(1126, 21, -1), - nos=[1057,1046,1022,900,841,816,696,593,574,426,356,324,303,245,147,139,71]) - process_time = datetime.timedelta(seconds=time.time() - start) - print("process_time: ", process_time) - - print("{} 회 당첨".format(len(win_history))) - sorted_win_history = sorted(win_history.keys()) - for i in range(len(sorted_win_history)): - print("\t>{} > {} ({})".format(sorted_win_history[i], win_history[sorted_win_history[i]], win_history_size[sorted_win_history[i]])) \ No newline at end of file diff --git a/review_3.py b/review_3.py deleted file mode 100644 index 0bcf3b1..0000000 --- a/review_3.py +++ /dev/null @@ -1,99 +0,0 @@ -import os -import time -import datetime -import pandas as pd -import itertools -from filter_model_3 import BallFilter - -class FilterTestReview: - - ballFilter = None - - def __init__(self, resources_path): - lottoHistoryFileName = os.path.join(resources_path, 'lotto_history.json') - self.ballFilter = BallFilter(lottoHistoryFileName) - - return - - def validate(self, df_ball, nos=None): - - win_history = {} - win_history_size = {} - - for no in nos: - - print("[{} 회차]".format(no)) - balls = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = balls[1:7].copy() # copy()로 복사 - bonus = balls[7] - - final_candidates = [] - win_dic = {1: [], 2: [], 3: [], 4: [], 5: []} - generation_balls = list(range(1, 46)) - nCr = list(itertools.combinations(generation_balls, 6)) - for idx, ball in enumerate(nCr): - - if idx % 1000000 == 0: - print(" - {} processed...".format(idx)) - - ball = list(ball) - - filter_type = self.ballFilter.filter(ball=ball, no=no, until_end=False, df=df_ball) - filter_size = len(filter_type) - - if 0 < filter_size: - continue - - final_candidates.append(ball) - - match = len(set(ball) & set(answer)) - if match == 6: - if no not in win_history: # 중복 방지 - win_history[no] = answer.copy() # copy()로 복사 - if ball not in win_dic[1]: # 같은 조합 중복 방지 - win_dic[1].append(ball.copy()) # copy()로 복사 - - else: - if match == 3: - win_dic[5].append(ball) - elif match == 4: - win_dic[4].append(ball) - elif match == 5: - # 2등 판별: 5개 맞고 보너스 번호 포함 - if bonus in ball: - win_dic[2].append(ball) - else: - win_dic[3].append(ball) - - win_history_size[no] = len(final_candidates) - - print("no: {}, answer: {}, size: {}".format(no, answer, len(final_candidates))) - print(" > 1등: {}, 2등: {}, 3등: {}, 4등: {}, 5등: {}".format(len(win_dic[1]), len(win_dic[2]), len(win_dic[3]), len(win_dic[4]), len(win_dic[5]))) - - return win_history, win_history_size - - -if __name__ == '__main__': - - PROJECT_HOME = '.' - resources_path = os.path.join(PROJECT_HOME, 'resources') - - lottoHistoryFileName = os.path.join(resources_path, 'lotto_history.txt') - df_ball = pd.read_csv(lottoHistoryFileName, header=None) - df_ball.columns = ['no', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'bn'] - - filterTestReview = FilterTestReview(resources_path) - - start = time.time() - #win_history = filterTest.validate(df_ball, nos =[1046,1022,1004,900,869,816,797,696,574,524,523,461,356,324,303,289,147,71], filter_ball = [1,2,4,6,10,11,11,17,18,20,21,22,23,24,26,27,28,30,31,32,33,34,37,38,39,40,42,44]) - win_history, win_history_size = filterTestReview.validate( - df_ball, - #nos=range(1126, 21, -1), - nos=[1057,1046,1022,900,841,816,696,593,574,426,356,324,303,245,147,139,71]) - process_time = datetime.timedelta(seconds=time.time() - start) - print("process_time: ", process_time) - - print("{} 회 당첨".format(len(win_history))) - sorted_win_history = sorted(win_history.keys()) - for i in range(len(sorted_win_history)): - print("\t>{} > {} ({})".format(sorted_win_history[i], win_history[sorted_win_history[i]], win_history_size[sorted_win_history[i]])) \ No newline at end of file diff --git a/test_1.py b/test_1.py deleted file mode 100644 index f24fcb9..0000000 --- a/test_1.py +++ /dev/null @@ -1,236 +0,0 @@ -import os -import argparse -import pandas as pd -import itertools -from filter_model_1 import BallFilter -import time -import datetime - -class FilterTest: - - ballFilter = None - - def __init__(self, resources_path, ruleset_path=None, history_json="lotto_history.json"): - # test는 이전회차/최근 N주 윈도우 feature가 필수이므로 전체 히스토리(json)를 사용해야 한다. - lottoHistoryFileName = os.path.join(resources_path, history_json) - self.ballFilter = BallFilter(lottoHistoryFileName, ruleset_path=ruleset_path) - - return - - def find_filter_method(self, df_ball, start_no, end_no, filter_ball=None): - win_count = 0 - - no_filter_ball = {} - - printLog = True - filter_dic = {} - filter_dic_len = {} - filter_dic_1 = {} - filter_dic_2 = {} - # df_ball 은 전체 히스토리일 수 있으며, 채점은 [start_no, end_no] 범위만 수행한다. - for i in range(len(df_ball) - 1, -1, -1): - - no = df_ball['no'].iloc[i] - no = int(no) - if no < start_no or end_no < no: - continue - answer = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = answer[1:7] - - filter_type = self.ballFilter.filter(ball=answer, no=no, until_end=True, df=df_ball) - filter_type = list(filter_type) - size = len(filter_type) - - if size == 0: - win_count += 1 - no_filter_ball[no] = answer - print("\t", no) - elif size == 1: - key = filter_type[0] - if key not in filter_dic_1: - filter_dic_1[key] = 1 - else: - filter_dic_1[key] += 1 - - if printLog: - print("\t", no, filter_type) - elif size == 2: - key = ','.join(filter_type) - if key not in filter_dic_2: - filter_dic_2[key] = 1 - else: - filter_dic_2[key] += 1 - - if printLog: - print("\t", no, filter_type) - else: - if printLog: - print("\t", no, filter_type) - - # 회차별 필터개수가 적은 것을 정렬하기 위함 - if size not in filter_dic_len: - filter_dic_len[size] = [] - filter_dic_len[size].append(filter_type) - - for f_t in filter_type: - if f_t not in filter_dic: - filter_dic[f_t] = 1 - else: - filter_dic[f_t] += 1 - - print("\n\t[필터 개수가 적은 것부터 최적화를 위함]") - sorted_filter_dic_len = sorted(filter_dic_len.keys()) - for filter_count in sorted_filter_dic_len: - for filter_type in filter_dic_len[filter_count]: - print("\t\t>{} > {}".format(filter_count, filter_type)) - - print("\n\t[걸러진 유일 필터]") - sorted_filter_dic_1 = sorted(filter_dic_1.items(), key=lambda x: x[1], reverse=True) - for i in range(len(sorted_filter_dic_1)): - print("\t\t>", sorted_filter_dic_1[i][0], "->", sorted_filter_dic_1[i][1]) - - print("\n\t[2개 필터에 걸린 경우]") - sorted_filter_dic_2 = sorted(filter_dic_2.items(), key=lambda x: x[1], reverse=True) - for i in range(len(sorted_filter_dic_2)): - print("\t\t>", sorted_filter_dic_2[i][0], "->", sorted_filter_dic_2[i][1]) - - print("\n\t[Filter 유형 별 걸린 개수]") - sorted_filter_dic = sorted(filter_dic.items(), key=lambda x: x[1], reverse=True) - for i in range(len(sorted_filter_dic)): - print("\t\t>", sorted_filter_dic[i][0], "->", sorted_filter_dic[i][1]) - - print("\n\t# 필터에 걸리지 않고 당첨된 회차") - total = max(0, end_no - start_no + 1) - rate = (100 * len(no_filter_ball) / total) if total else 0.0 - print("\tcount: {:,} / total: {:,} ({:.2})%".format(len(no_filter_ball), total, rate)) - for no in no_filter_ball: - print("\t\t>", no, no_filter_ball[no]) - print("\tcount: {:,} / total: {:,} ({:.2})%".format(len(no_filter_ball), total, rate)) - - return win_count - - def find_final_candidates(self, no, df_ball, filter_ball=None): - final_candidates = [] - - generation_balls = list(range(1, 46)) - - nCr = list(itertools.combinations(generation_balls, 6)) - for idx, ball in enumerate(nCr): - - if idx % 1000000 == 0: - print(" - {} processed...".format(idx)) - - if filter_ball is not None and 0 < len(set(ball) & set(filter_ball)): - continue - - filter_type = self.ballFilter.filter(ball=ball, no=no, until_end=False, df=df_ball) - filter_size = len(filter_type) - - if filter_size: - continue - - final_candidates.append(ball) - - return final_candidates - - def check_filter_method(self, df_ball, p_win_count, filter_ball=None): - - win_count = 0 - for i in range(len(df_ball)-1, 0, -1): - - no = df_ball['no'].iloc[i] - answer = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = answer[1:7] - - if filter_ball is not None and len(set(answer) & set(filter_ball)): - continue - - filter_type = self.ballFilter.extract_final_candidates(answer, no=no, until_end=True, df=df_ball) - - if len(filter_type) == 0: - win_count += 1 - print("\t\t>{}. {}".format(no, answer)) - - print("\n\t> {} / {} p_win_count, {} total".format( win_count, p_win_count, len(df_ball)-1) ) - - return - - def validate(self, df_ball, nos=None): - win_history = {} - - for no in nos: - print(no, "processing...") - answer = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = answer[1:7] - - generation_balls = list(range(1, 46)) - nCr = list(itertools.combinations(generation_balls, 6)) - for idx, ball in enumerate(nCr): - if idx % 1000000 == 0: - print(" - {} processed...".format(idx)) - ball = list(ball) - filter_type = self.ballFilter.extract_final_candidates(ball, no=no, until_end=True, df=df_ball) - if 0 == len(filter_type) and len(set(answer)&set(ball))==6: - win_history[no] = answer - print("win.. no: {}, answer: {}".format(no, str(answer))) - break - - return win_history - - -if __name__ == '__main__': - - parser = argparse.ArgumentParser() - parser.add_argument("--resources", default="resources") - parser.add_argument( - "--ruleset", - default=None, - help="Ruleset JSON path (optional). Default: filter_model_1.py 내장 ruleset 사용", - ) - parser.add_argument("--start-no", type=int, default=1001) - parser.add_argument("--end-no", type=int, default=1204) - args = parser.parse_args() - - resources_path = args.resources - - # 전체 히스토리 txt를 사용해 previous/window feature를 정상 계산하되, 채점은 test 범위만 수행한다. - lottoHistoryFileName = os.path.join(resources_path, 'lotto_history.txt') - df_ball = pd.read_csv(lottoHistoryFileName, header=None) - df_ball.columns = ['no', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'bn'] - - filter_ball=[] - filterTest = FilterTest(resources_path, ruleset_path=args.ruleset, history_json='lotto_history.json') - - print("STEP #1. 필터 방법 추출") - start = time.time() - win_count = filterTest.find_filter_method(df_ball, start_no=args.start_no, end_no=args.end_no, filter_ball=filter_ball) - process_time = datetime.timedelta(seconds=time.time() - start) - print("process_time: ", process_time) - - """ - print("\n\n") - no = df_ball['no'].values[-1] - ball = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = ball[1:7] - - print("STEP #0. 최종 후보 선정") - start = time.time() - final_candidates = filterTest.find_final_candidates(no, df_ball, filter_ball=None) - process_time = datetime.timedelta(seconds=time.time() - start) - print("process_time: ", process_time) - - print(" > size: {}".format(len(final_candidates))) - file_name = os.path.join(resources_path, 'final_candidates.biz_a1.txt') - with open(file_name, 'w+') as outFp: - for ball in final_candidates: - ball_str = [str(b) for b in answer] - outFp.write("{}\n".format(','.join(ball_str))) - - print('{}회, 정답: {}\n'.format(no, str(answer))) - """ - - #print("\n\n") - #print("STEP #2. 당첨 회수 확인") - #filterTest.check_filter_method(df_ball, win_count) - - # 오리지널 버전 (자질 파일에 고정): 당첨 22개 \ No newline at end of file diff --git a/test_2.py b/test_2.py deleted file mode 100644 index d879ddd..0000000 --- a/test_2.py +++ /dev/null @@ -1,236 +0,0 @@ -import os -import argparse -import pandas as pd -import itertools -from filter_model_2 import BallFilter -import time -import datetime - -class FilterTest: - - ballFilter = None - - def __init__(self, resources_path, ruleset_path=None, history_json="lotto_history.json"): - # test는 이전회차/최근 N주 윈도우 feature가 필수이므로 전체 히스토리(json)를 사용해야 한다. - lottoHistoryFileName = os.path.join(resources_path, history_json) - self.ballFilter = BallFilter(lottoHistoryFileName, ruleset_path=ruleset_path) - - return - - def find_filter_method(self, df_ball, start_no, end_no, filter_ball=None): - win_count = 0 - - no_filter_ball = {} - - printLog = True - filter_dic = {} - filter_dic_len = {} - filter_dic_1 = {} - filter_dic_2 = {} - # df_ball 은 전체 히스토리일 수 있으며, 채점은 [start_no, end_no] 범위만 수행한다. - for i in range(len(df_ball) - 1, -1, -1): - - no = df_ball['no'].iloc[i] - no = int(no) - if no < start_no or end_no < no: - continue - answer = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = answer[1:7] - - filter_type = self.ballFilter.filter(ball=answer, no=no, until_end=True, df=df_ball) - filter_type = list(filter_type) - size = len(filter_type) - - if size == 0: - win_count += 1 - no_filter_ball[no] = answer - print("\t", no) - elif size == 1: - key = filter_type[0] - if key not in filter_dic_1: - filter_dic_1[key] = 1 - else: - filter_dic_1[key] += 1 - - if printLog: - print("\t", no, filter_type) - elif size == 2: - key = ','.join(filter_type) - if key not in filter_dic_2: - filter_dic_2[key] = 1 - else: - filter_dic_2[key] += 1 - - if printLog: - print("\t", no, filter_type) - else: - if printLog: - print("\t", no, filter_type) - - # 회차별 필터개수가 적은 것을 정렬하기 위함 - if size not in filter_dic_len: - filter_dic_len[size] = [] - filter_dic_len[size].append(filter_type) - - for f_t in filter_type: - if f_t not in filter_dic: - filter_dic[f_t] = 1 - else: - filter_dic[f_t] += 1 - - print("\n\t[필터 개수가 적은 것부터 최적화를 위함]") - sorted_filter_dic_len = sorted(filter_dic_len.keys()) - for filter_count in sorted_filter_dic_len: - for filter_type in filter_dic_len[filter_count]: - print("\t\t>{} > {}".format(filter_count, filter_type)) - - print("\n\t[걸러진 유일 필터]") - sorted_filter_dic_1 = sorted(filter_dic_1.items(), key=lambda x: x[1], reverse=True) - for i in range(len(sorted_filter_dic_1)): - print("\t\t>", sorted_filter_dic_1[i][0], "->", sorted_filter_dic_1[i][1]) - - print("\n\t[2개 필터에 걸린 경우]") - sorted_filter_dic_2 = sorted(filter_dic_2.items(), key=lambda x: x[1], reverse=True) - for i in range(len(sorted_filter_dic_2)): - print("\t\t>", sorted_filter_dic_2[i][0], "->", sorted_filter_dic_2[i][1]) - - print("\n\t[Filter 유형 별 걸린 개수]") - sorted_filter_dic = sorted(filter_dic.items(), key=lambda x: x[1], reverse=True) - for i in range(len(sorted_filter_dic)): - print("\t\t>", sorted_filter_dic[i][0], "->", sorted_filter_dic[i][1]) - - print("\n\t# 필터에 걸리지 않고 당첨된 회차") - total = max(0, end_no - start_no + 1) - rate = (100 * len(no_filter_ball) / total) if total else 0.0 - print("\tcount: {:,} / total: {:,} ({:.2})%".format(len(no_filter_ball), total, rate)) - for no in no_filter_ball: - print("\t\t>", no, no_filter_ball[no]) - print("\tcount: {:,} / total: {:,} ({:.2})%".format(len(no_filter_ball), total, rate)) - - return win_count - - def find_final_candidates(self, no, df_ball, filter_ball=None): - final_candidates = [] - - generation_balls = list(range(1, 46)) - - nCr = list(itertools.combinations(generation_balls, 6)) - for idx, ball in enumerate(nCr): - - if idx % 1000000 == 0: - print(" - {} processed...".format(idx)) - - if filter_ball is not None and 0 < len(set(ball) & set(filter_ball)): - continue - - filter_type = self.ballFilter.filter(ball=ball, no=no, until_end=False, df=df_ball) - filter_size = len(filter_type) - - if filter_size: - continue - - final_candidates.append(ball) - - return final_candidates - - def check_filter_method(self, df_ball, p_win_count, filter_ball=None): - - win_count = 0 - for i in range(len(df_ball)-1, 0, -1): - - no = df_ball['no'].iloc[i] - answer = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = answer[1:7] - - if filter_ball is not None and len(set(answer) & set(filter_ball)): - continue - - filter_type = self.ballFilter.extract_final_candidates(answer, no=no, until_end=True, df=df_ball) - - if len(filter_type) == 0: - win_count += 1 - print("\t\t>{}. {}".format(no, answer)) - - print("\n\t> {} / {} p_win_count, {} total".format( win_count, p_win_count, len(df_ball)-1) ) - - return - - def validate(self, df_ball, nos=None): - win_history = {} - - for no in nos: - print(no, "processing...") - answer = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = answer[1:7] - - generation_balls = list(range(1, 46)) - nCr = list(itertools.combinations(generation_balls, 6)) - for idx, ball in enumerate(nCr): - if idx % 1000000 == 0: - print(" - {} processed...".format(idx)) - ball = list(ball) - filter_type = self.ballFilter.extract_final_candidates(ball, no=no, until_end=True, df=df_ball) - if 0 == len(filter_type) and len(set(answer)&set(ball))==6: - win_history[no] = answer - print("win.. no: {}, answer: {}".format(no, str(answer))) - break - - return win_history - - -if __name__ == '__main__': - - parser = argparse.ArgumentParser() - parser.add_argument("--resources", default="resources") - parser.add_argument( - "--ruleset", - default=None, - help="Ruleset JSON path (optional). Default: filter_model_1.py 내장 ruleset 사용", - ) - parser.add_argument("--start-no", type=int, default=1001) - parser.add_argument("--end-no", type=int, default=1204) - args = parser.parse_args() - - resources_path = args.resources - - # 전체 히스토리 txt를 사용해 previous/window feature를 정상 계산하되, 채점은 test 범위만 수행한다. - lottoHistoryFileName = os.path.join(resources_path, 'lotto_history.txt') - df_ball = pd.read_csv(lottoHistoryFileName, header=None) - df_ball.columns = ['no', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'bn'] - - filter_ball=[] - filterTest = FilterTest(resources_path, ruleset_path=args.ruleset, history_json='lotto_history.json') - - print("STEP #1. 필터 방법 추출") - start = time.time() - win_count = filterTest.find_filter_method(df_ball, start_no=args.start_no, end_no=args.end_no, filter_ball=filter_ball) - process_time = datetime.timedelta(seconds=time.time() - start) - print("process_time: ", process_time) - - """ - print("\n\n") - no = df_ball['no'].values[-1] - ball = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = ball[1:7] - - print("STEP #0. 최종 후보 선정") - start = time.time() - final_candidates = filterTest.find_final_candidates(no, df_ball, filter_ball=None) - process_time = datetime.timedelta(seconds=time.time() - start) - print("process_time: ", process_time) - - print(" > size: {}".format(len(final_candidates))) - file_name = os.path.join(resources_path, 'final_candidates.biz_a1.txt') - with open(file_name, 'w+') as outFp: - for ball in final_candidates: - ball_str = [str(b) for b in answer] - outFp.write("{}\n".format(','.join(ball_str))) - - print('{}회, 정답: {}\n'.format(no, str(answer))) - """ - - #print("\n\n") - #print("STEP #2. 당첨 회수 확인") - #filterTest.check_filter_method(df_ball, win_count) - - # 오리지널 버전 (자질 파일에 고정): 당첨 22개 \ No newline at end of file diff --git a/test_3.py b/test_3.py deleted file mode 100644 index 686511e..0000000 --- a/test_3.py +++ /dev/null @@ -1,236 +0,0 @@ -import os -import argparse -import pandas as pd -import itertools -from filter_model_3 import BallFilter -import time -import datetime - -class FilterTest: - - ballFilter = None - - def __init__(self, resources_path, ruleset_path=None, history_json="lotto_history.json"): - # test는 이전회차/최근 N주 윈도우 feature가 필수이므로 전체 히스토리(json)를 사용해야 한다. - lottoHistoryFileName = os.path.join(resources_path, history_json) - self.ballFilter = BallFilter(lottoHistoryFileName, ruleset_path=ruleset_path) - - return - - def find_filter_method(self, df_ball, start_no, end_no, filter_ball=None): - win_count = 0 - - no_filter_ball = {} - - printLog = True - filter_dic = {} - filter_dic_len = {} - filter_dic_1 = {} - filter_dic_2 = {} - # df_ball 은 전체 히스토리일 수 있으며, 채점은 [start_no, end_no] 범위만 수행한다. - for i in range(len(df_ball) - 1, -1, -1): - - no = df_ball['no'].iloc[i] - no = int(no) - if no < start_no or end_no < no: - continue - answer = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = answer[1:7] - - filter_type = self.ballFilter.filter(ball=answer, no=no, until_end=True, df=df_ball) - filter_type = list(filter_type) - size = len(filter_type) - - if size == 0: - win_count += 1 - no_filter_ball[no] = answer - print("\t", no) - elif size == 1: - key = filter_type[0] - if key not in filter_dic_1: - filter_dic_1[key] = 1 - else: - filter_dic_1[key] += 1 - - if printLog: - print("\t", no, filter_type) - elif size == 2: - key = ','.join(filter_type) - if key not in filter_dic_2: - filter_dic_2[key] = 1 - else: - filter_dic_2[key] += 1 - - if printLog: - print("\t", no, filter_type) - else: - if printLog: - print("\t", no, filter_type) - - # 회차별 필터개수가 적은 것을 정렬하기 위함 - if size not in filter_dic_len: - filter_dic_len[size] = [] - filter_dic_len[size].append(filter_type) - - for f_t in filter_type: - if f_t not in filter_dic: - filter_dic[f_t] = 1 - else: - filter_dic[f_t] += 1 - - print("\n\t[필터 개수가 적은 것부터 최적화를 위함]") - sorted_filter_dic_len = sorted(filter_dic_len.keys()) - for filter_count in sorted_filter_dic_len: - for filter_type in filter_dic_len[filter_count]: - print("\t\t>{} > {}".format(filter_count, filter_type)) - - print("\n\t[걸러진 유일 필터]") - sorted_filter_dic_1 = sorted(filter_dic_1.items(), key=lambda x: x[1], reverse=True) - for i in range(len(sorted_filter_dic_1)): - print("\t\t>", sorted_filter_dic_1[i][0], "->", sorted_filter_dic_1[i][1]) - - print("\n\t[2개 필터에 걸린 경우]") - sorted_filter_dic_2 = sorted(filter_dic_2.items(), key=lambda x: x[1], reverse=True) - for i in range(len(sorted_filter_dic_2)): - print("\t\t>", sorted_filter_dic_2[i][0], "->", sorted_filter_dic_2[i][1]) - - print("\n\t[Filter 유형 별 걸린 개수]") - sorted_filter_dic = sorted(filter_dic.items(), key=lambda x: x[1], reverse=True) - for i in range(len(sorted_filter_dic)): - print("\t\t>", sorted_filter_dic[i][0], "->", sorted_filter_dic[i][1]) - - print("\n\t# 필터에 걸리지 않고 당첨된 회차") - total = max(0, end_no - start_no + 1) - rate = (100 * len(no_filter_ball) / total) if total else 0.0 - print("\tcount: {:,} / total: {:,} ({:.2})%".format(len(no_filter_ball), total, rate)) - for no in no_filter_ball: - print("\t\t>", no, no_filter_ball[no]) - print("\tcount: {:,} / total: {:,} ({:.2})%".format(len(no_filter_ball), total, rate)) - - return win_count - - def find_final_candidates(self, no, df_ball, filter_ball=None): - final_candidates = [] - - generation_balls = list(range(1, 46)) - - nCr = list(itertools.combinations(generation_balls, 6)) - for idx, ball in enumerate(nCr): - - if idx % 1000000 == 0: - print(" - {} processed...".format(idx)) - - if filter_ball is not None and 0 < len(set(ball) & set(filter_ball)): - continue - - filter_type = self.ballFilter.filter(ball=ball, no=no, until_end=False, df=df_ball) - filter_size = len(filter_type) - - if filter_size: - continue - - final_candidates.append(ball) - - return final_candidates - - def check_filter_method(self, df_ball, p_win_count, filter_ball=None): - - win_count = 0 - for i in range(len(df_ball)-1, 0, -1): - - no = df_ball['no'].iloc[i] - answer = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = answer[1:7] - - if filter_ball is not None and len(set(answer) & set(filter_ball)): - continue - - filter_type = self.ballFilter.extract_final_candidates(answer, no=no, until_end=True, df=df_ball) - - if len(filter_type) == 0: - win_count += 1 - print("\t\t>{}. {}".format(no, answer)) - - print("\n\t> {} / {} p_win_count, {} total".format( win_count, p_win_count, len(df_ball)-1) ) - - return - - def validate(self, df_ball, nos=None): - win_history = {} - - for no in nos: - print(no, "processing...") - answer = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = answer[1:7] - - generation_balls = list(range(1, 46)) - nCr = list(itertools.combinations(generation_balls, 6)) - for idx, ball in enumerate(nCr): - if idx % 1000000 == 0: - print(" - {} processed...".format(idx)) - ball = list(ball) - filter_type = self.ballFilter.extract_final_candidates(ball, no=no, until_end=True, df=df_ball) - if 0 == len(filter_type) and len(set(answer)&set(ball))==6: - win_history[no] = answer - print("win.. no: {}, answer: {}".format(no, str(answer))) - break - - return win_history - - -if __name__ == '__main__': - - parser = argparse.ArgumentParser() - parser.add_argument("--resources", default="resources") - parser.add_argument( - "--ruleset", - default=None, - help="Ruleset JSON path (optional). Default: filter_model_1.py 내장 ruleset 사용", - ) - parser.add_argument("--start-no", type=int, default=1001) - parser.add_argument("--end-no", type=int, default=1204) - args = parser.parse_args() - - resources_path = args.resources - - # 전체 히스토리 txt를 사용해 previous/window feature를 정상 계산하되, 채점은 test 범위만 수행한다. - lottoHistoryFileName = os.path.join(resources_path, 'lotto_history.txt') - df_ball = pd.read_csv(lottoHistoryFileName, header=None) - df_ball.columns = ['no', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'bn'] - - filter_ball=[] - filterTest = FilterTest(resources_path, ruleset_path=args.ruleset, history_json='lotto_history.json') - - print("STEP #1. 필터 방법 추출") - start = time.time() - win_count = filterTest.find_filter_method(df_ball, start_no=args.start_no, end_no=args.end_no, filter_ball=filter_ball) - process_time = datetime.timedelta(seconds=time.time() - start) - print("process_time: ", process_time) - - """ - print("\n\n") - no = df_ball['no'].values[-1] - ball = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = ball[1:7] - - print("STEP #0. 최종 후보 선정") - start = time.time() - final_candidates = filterTest.find_final_candidates(no, df_ball, filter_ball=None) - process_time = datetime.timedelta(seconds=time.time() - start) - print("process_time: ", process_time) - - print(" > size: {}".format(len(final_candidates))) - file_name = os.path.join(resources_path, 'final_candidates.biz_a1.txt') - with open(file_name, 'w+') as outFp: - for ball in final_candidates: - ball_str = [str(b) for b in answer] - outFp.write("{}\n".format(','.join(ball_str))) - - print('{}회, 정답: {}\n'.format(no, str(answer))) - """ - - #print("\n\n") - #print("STEP #2. 당첨 회수 확인") - #filterTest.check_filter_method(df_ball, win_count) - - # 오리지널 버전 (자질 파일에 고정): 당첨 22개 \ No newline at end of file diff --git a/tools/compute_final_filter_params.py b/tools/compute_final_filter_params.py deleted file mode 100644 index fd4af6d..0000000 --- a/tools/compute_final_filter_params.py +++ /dev/null @@ -1,405 +0,0 @@ -#!/usr/bin/env python3 -""" -학습 구간(1~800회) 당첨번호로 final_BallFilter.extract_final_candidates 에 쓸 허용 집합을 계산합니다. -표준 라이브러리 + pandas(df 호환)만 사용합니다. -""" -from __future__ import annotations - -import csv -import re -from collections import defaultdict -from pathlib import Path - -ROOT = Path(__file__).resolve().parents[1] -HISTORY = ROOT / "resources" / "lotto_history.txt" -BALLFILTER_SRC = ROOT / "BallFilter_25.py" -OUT = ROOT / "final_filter_params.py" - -TRAIN_LO = 1 -TRAIN_HI = 800 - -# 학습 분포에서 너무 넓은 합집합(union)을 피하기 위해 고유값 기준 백분위 밴드 후, -# 각 회차 특성값이 밴드 밖이면 해당 값을 다시 포함(학습 당첨 100% 커버). -# 좁을수록 필터가 강해짐. 학습·검증 균형은 이 값과 final_filterTest.py 결과로 조정. -PCT_LO = 8 -PCT_HI = 92 - -PRIME = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43} -COMPOSITE = {4, 6, 8, 9, 10, 12, 14, 15, 16, 18, 20, 21, 22, 24, 25, 26, 27, 28, 30, 32, 33, 34, 35, 36, 38, 39, 40, 42, 44, 45} - - -def load_draws(): - rows = [] - with open(HISTORY, newline="", encoding="utf-8") as f: - for p in csv.reader(f): - if not p: - continue - no = int(p[0]) - balls = sorted(int(x) for x in p[1:7]) - rows.append((no, balls)) - rows.sort(key=lambda x: x[0]) - return {no: b for no, b in rows} - - -def get_ac(ball): - ac = set() - for i in range(5, -1, -1): - for j in range(i - 1, -1, -1): - ac.add(ball[i] - ball[j]) - return len(ac) - (6 - 1) - - -def interval_sum(ball): - return sum(ball[i] - ball[i - 1] for i in range(1, 6)) - - -def first_letter_sum(ball): - acc = [str(b)[0] for b in ball if len(str(b)) == 2] - return sum(int(x) for x in acc) - - -def last_letter_sum(ball): - acc = [str(b)[1] for b in ball if len(str(b)) == 2] + [str(b) for b in ball if len(str(b)) == 1] - return sum(int(x) for x in acc) - - -def uniq_end_digits(ball): - return len({b % 10 for b in ball}) - - -def high_low(ball): - low = sum(1 for b in ball if b < 23) - high = sum(1 for b in ball if 23 < b) - return low, high - - -def section10_count(ball): - section = set() - for b in ball: - section.add(int(b / 10)) - return len(section) - - -def count_mult(ball, m): - return sum(1 for b in ball if b % m == 0) - - -def continus_max(ball): - w = ball - best = 1 - run = 1 - for i in range(1, 6): - if w[i] == w[i - 1] + 1: - run += 1 - best = max(best, run) - else: - run = 1 - return best - - -def weeks_freq(draws_map, answer, no, week): - s = set() - for w in range(1, week + 1): - prev_no = no - w - if prev_no not in draws_map: - continue - for b in draws_map[prev_no]: - s.add(b) - return sum(1 for b in answer if b in s) - - -def pct_band_unique(values, lo=PCT_LO, hi=PCT_HI): - """고유값 정렬 후 백분위 구간에 들어가는 값만 유지. 고유 개수가 적으면 전부 유지.""" - if not values: - return set() - u = sorted(set(values)) - if len(u) <= 6: - return set(u) - n = len(u) - il = int((lo / 100.0) * (n - 1)) - ih = int((hi / 100.0) * (n - 1)) - low_b, high_b = u[il], u[ih] - return {x for x in u if low_b <= x <= high_b} - - -def parse_pair_triple_rules(): - """BallFilter_25.filterPairBall / filterTriplePairBall 에서 규칙 추출.""" - text = BALLFILTER_SRC.read_text(encoding="utf-8") - pairs = [] - for m in re.finditer(r"len\(set_ball & \{([^}]+)\}\) == 2", text): - parts = [int(x.strip()) for x in m.group(1).split(",")] - if len(parts) == 2: - pairs.append(frozenset(parts)) - triples = [] - for m in re.finditer(r"len\(set_ball & \{([^}]+)\}\) == 3", text): - parts = [int(x.strip()) for x in m.group(1).split(",")] - if len(parts) == 3: - triples.append(frozenset(parts)) - return pairs, triples - - -def main(): - draws = load_draws() - pair_rules, triple_rules = parse_pair_triple_rules() - - train_draws = {n: draws[n] for n in range(TRAIN_LO, TRAIN_HI + 1) if n in draws} - - # 블랙리스트: 학습 당첨 6개에 함께 등장한 쌍/삼은 제외(당첨을 막지 않음) - train_pairs_seen = set() - train_triples_seen = set() - for b in train_draws.values(): - for i in range(6): - for j in range(i + 1, 6): - train_pairs_seen.add(frozenset((b[i], b[j]))) - for i in range(6): - for j in range(i + 1, 6): - for k in range(j + 1, 6): - train_triples_seen.add(frozenset((b[i], b[j], b[k]))) - - pair_block = [p for p in pair_rules if p not in train_pairs_seen] - triple_block = [t for t in triple_rules if t not in train_triples_seen] - - sets = defaultdict(set) - flags_prev = {"need_relax_previous": False, "need_relax_prev7": False} - - for no in range(2, TRAIN_HI + 1): - if no not in draws or (no - 1) not in draws: - continue - ball = draws[no] - p_ball = draws[no - 1] - - s = sum(ball) - sets["sum6"].add(s) - sets["sum6_diff"].add(abs(s - sum(p_ball))) - - avg = s // 6 - pavg = sum(p_ball) // 6 - sets["avg6"].add(avg) - sets["avg6_diff"].add(abs(avg - pavg)) - - s3f = ball[0] + ball[1] + ball[2] - ps3f = p_ball[0] + p_ball[1] + p_ball[2] - sets["sum3f"].add(s3f) - sets["sum3f_diff"].add(abs(s3f - ps3f)) - - s3b = ball[3] + ball[4] + ball[5] - ps3b = p_ball[3] + p_ball[4] + p_ball[5] - sets["sum3b"].add(s3b) - sets["sum3b_diff"].add(abs(s3b - ps3b)) - - l, h = high_low(ball) - sets["hl_allowed"].add((l, h)) - - gh = ball[0] + ball[5] - pgh = p_ball[0] + p_ball[5] - sets["go_sum"].add(gh) - sets["go_sum_diff"].add(abs(gh - pgh)) - - iv = interval_sum(ball) - piv = interval_sum(p_ball) - sets["interval"].add(iv) - sets["interval_diff"].add(abs(iv - piv)) - - fl = first_letter_sum(ball) - pfl = first_letter_sum(p_ball) - sets["first_letter"].add(fl) - sets["first_letter_diff"].add(abs(fl - pfl)) - - ll = last_letter_sum(ball) - pll = last_letter_sum(p_ball) - sets["last_letter"].add(ll) - sets["last_letter_diff"].add(abs(ll - pll)) - - sets["b0"].add(ball[0]) - sets["b0_diff"].add(abs(ball[0] - p_ball[0])) - sets["b5"].add(ball[5]) - sets["b5_diff"].add(abs(ball[5] - p_ball[5])) - - sets["uniq_end"].add(uniq_end_digits(ball)) - sets["uniq_end_diff"].add(abs(uniq_end_digits(ball) - uniq_end_digits(p_ball))) - - ac = get_ac(ball) - pac = get_ac(p_ball) - sets["ac"].add(ac) - sets["ac_diff"].add(abs(ac - pac)) - - for m in (3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 17, 19, 23): - sets[f"mul{m}"].add(count_mult(ball, m)) - sets[f"mul{m}_diff"].add(abs(count_mult(ball, m) - count_mult(p_ball, m))) - - pn = len(set(ball) & PRIME) - sets["prime_n"].add(pn) - - cn = len(set(ball) & COMPOSITE) - sets["composite_n"].add(cn) - sets["composite_diff"].add(abs(cn - len(set(p_ball) & COMPOSITE))) - - ev = sum(1 for b in ball if b % 2 == 0) - pev = sum(1 for b in p_ball if b % 2 == 0) - sets["even_n"].add(ev) - sets["even_diff"].add(abs(ev - pev)) - - sc = section10_count(ball) - psc = section10_count(p_ball) - sets["sec10"].add(sc) - sets["sec10_diff"].add(abs(sc - psc)) - - for wk in (8, 12, 16, 20): - ex = weeks_freq(draws, ball, no, wk) - pex = weeks_freq(draws, p_ball, no, wk) - sets[f"w{wk}"].add(ex) - sets[f"w{wk}_diff"].add(abs(ex - pex)) - - sets["continus_max"].add(continus_max(ball)) - - # filterPreviousNumber (원본과 동일) - pb_set = set(p_ball) - bad_prev = True - for i in range(6): - bi = ball[i] - if bi in pb_set or bi - 1 in pb_set or bi + 1 in pb_set: - bad_prev = False - break - if bad_prev: - flags_prev["need_relax_previous"] = True - - # filterAllPreivous7 - pb7 = set() - for i in range(no - 1, no - 8, -1): - if i in draws: - for x in draws[i]: - pb7.add(x) - if len(set(ball) & pb7) == 6: - flags_prev["need_relax_prev7"] = True - - # 백분위로 타이트닝 후 학습 각 회차 특성 보강 - keys_numeric = [ - "sum6", - "sum6_diff", - "avg6", - "avg6_diff", - "sum3f", - "sum3f_diff", - "sum3b", - "sum3b_diff", - "go_sum", - "go_sum_diff", - "interval", - "interval_diff", - "first_letter", - "first_letter_diff", - "last_letter", - "last_letter_diff", - "b0", - "b0_diff", - "b5", - "b5_diff", - "uniq_end", - "uniq_end_diff", - "ac", - "ac_diff", - "prime_n", - "composite_n", - "composite_diff", - "even_n", - "even_diff", - "sec10", - "sec10_diff", - ] - for m in (3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 17, 19, 23): - keys_numeric.extend([f"mul{m}", f"mul{m}_diff"]) - for wk in (8, 12, 16, 20): - keys_numeric.extend([f"w{wk}", f"w{wk}_diff"]) - keys_numeric.append("continus_max") - - for k in keys_numeric: - sets[k] = pct_band_unique(sets[k]) - - # 고저: (0,1)/(1,0) 만 제외하는 기존 로직 유지 + 학습에 나온 (l,h) 항상 허용 - hl_skip = {(l, h) for l in (0, 1) for h in (0, 1)} - - def emit(): - lines = [ - "# -*- coding: utf-8 -*-", - '"""학습 구간 {}~{}회 기준 자동 생성 — tools/compute_final_filter_params.py"""'.format( - TRAIN_LO, TRAIN_HI - ), - "", - "TRAIN_RANGE = ({}, {})".format(TRAIN_LO, TRAIN_HI), - "DISABLE_FILTER_PREVIOUS_NUMBER = {}".format( - str(flags_prev["need_relax_previous"]) - ), - "DISABLE_FILTER_ALL_PREVIOUS_7 = {}".format(str(flags_prev["need_relax_prev7"])), - "", - ] - - def sset(name, key): - v = sets[key] - lines.append("{} = {}".format(name, repr(sorted(v)))) - - sset("ALLOW_SUM6", "sum6") - sset("ALLOW_SUM6_DIFF", "sum6_diff") - sset("ALLOW_AVG6", "avg6") - sset("ALLOW_AVG6_DIFF", "avg6_diff") - sset("ALLOW_SUM3F", "sum3f") - sset("ALLOW_SUM3F_DIFF", "sum3f_diff") - sset("ALLOW_SUM3B", "sum3b") - sset("ALLOW_SUM3B_DIFF", "sum3b_diff") - lines.append("HL_SKIP = {}".format(repr(sorted(hl_skip)))) - lines.append("HL_SEEN = {}".format(repr(sorted(sets['hl_allowed'])))) - sset("ALLOW_GO_SUM", "go_sum") - sset("ALLOW_GO_SUM_DIFF", "go_sum_diff") - sset("ALLOW_INTERVAL", "interval") - sset("ALLOW_INTERVAL_DIFF", "interval_diff") - sset("ALLOW_FIRST_LETTER", "first_letter") - sset("ALLOW_FIRST_LETTER_DIFF", "first_letter_diff") - sset("ALLOW_LAST_LETTER", "last_letter") - sset("ALLOW_LAST_LETTER_DIFF", "last_letter_diff") - sset("ALLOW_B0", "b0") - sset("ALLOW_B0_DIFF", "b0_diff") - sset("ALLOW_B5", "b5") - sset("ALLOW_B5_DIFF", "b5_diff") - sset("ALLOW_UNIQ_END", "uniq_end") - sset("ALLOW_UNIQ_END_DIFF", "uniq_end_diff") - sset("ALLOW_AC", "ac") - sset("ALLOW_AC_DIFF", "ac_diff") - for m in (3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 17, 19, 23): - sset("ALLOW_MUL{}".format(m), "mul{}".format(m)) - sset("ALLOW_MUL{}_DIFF".format(m), "mul{}_diff".format(m)) - sset("ALLOW_PRIME_N", "prime_n") - sset("ALLOW_COMPOSITE_N", "composite_n") - sset("ALLOW_COMPOSITE_DIFF", "composite_diff") - sset("ALLOW_EVEN_N", "even_n") - sset("ALLOW_EVEN_DIFF", "even_diff") - sset("ALLOW_SEC10", "sec10") - sset("ALLOW_SEC10_DIFF", "sec10_diff") - for wk in (8, 12, 16, 20): - sset("ALLOW_W{}".format(wk), "w{}".format(wk)) - sset("ALLOW_W{}_DIFF".format(wk), "w{}_diff".format(wk)) - sset("ALLOW_CONTINUS_MAX", "continus_max") - - lines.append("PAIR_BLOCKLIST = {}".format(repr([sorted(list(x)) for x in pair_block]))) - lines.append("TRIPLE_BLOCKLIST = {}".format(repr([sorted(list(x)) for x in triple_block]))) - lines.extend(["", "# frozenset 캐시", ""]) - allow_names = [] - for line in list(lines): - if line.startswith("ALLOW_") and " = " in line: - name = line.split(" = ")[0] - allow_names.append(name) - for name in allow_names: - short = name.replace("ALLOW_", "", 1) - lines.append("_F_{} = frozenset({})".format(short, name)) - lines.append("_F_HL_SEEN = frozenset(HL_SEEN)") - lines.append("") - return "\n".join(lines) + "\n" - - OUT.write_text(emit(), encoding="utf-8") - print("Wrote", OUT) - print("pair rules:", len(pair_rules), "-> block", len(pair_block)) - print("triple rules:", len(triple_rules), "-> block", len(triple_block)) - print("DISABLE_FILTER_PREVIOUS_NUMBER", flags_prev["need_relax_previous"]) - print("DISABLE_FILTER_ALL_PREVIOUS_7", flags_prev["need_relax_prev7"]) - - -if __name__ == "__main__": - main() diff --git a/train_1.py b/train_1.py deleted file mode 100644 index d4cffeb..0000000 --- a/train_1.py +++ /dev/null @@ -1,231 +0,0 @@ -import os -import argparse -import pandas as pd -import itertools -from filter_model_1 import BallFilter -import time -import datetime - -class FilterTest: - - ballFilter = None - - def __init__(self, resources_path, ruleset_path=None): - lottoHistoryFileName = os.path.join(resources_path, 'lotto_history.json') - self.ballFilter = BallFilter(lottoHistoryFileName, ruleset_path=ruleset_path) - - return - - def find_filter_method(self, df_ball, start_no, end_no): - win_count = 0 - - no_filter_ball = {} - - printLog = True - filter_dic = {} - filter_dic_len = {} - filter_dic_1 = {} - filter_dic_2 = {} - for i in range(len(df_ball)-1, 19, -1): - no = df_ball['no'].iloc[i] - if no < start_no or end_no < no: - continue - - answer = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = answer[1:7] - - filter_type = self.ballFilter.filter(ball=answer, no=no, until_end=True, df=df_ball) - filter_type = list(filter_type) - size = len(filter_type) - - if size == 0: - win_count += 1 - no_filter_ball[no] = answer - print("\t", no) - elif size == 1: - key = filter_type[0] - if key not in filter_dic_1: - filter_dic_1[key] = 1 - else: - filter_dic_1[key] += 1 - - if printLog: - print("\t", no, filter_type) - elif size == 2: - key = ','.join(filter_type) - if key not in filter_dic_2: - filter_dic_2[key] = 1 - else: - filter_dic_2[key] += 1 - - if printLog: - print("\t", no, filter_type) - else: - if printLog: - print("\t", no, filter_type) - - # 회차별 필터개수가 적은 것을 정렬하기 위함 - if size not in filter_dic_len: - filter_dic_len[size] = [] - filter_dic_len[size].append(filter_type) - - for f_t in filter_type: - if f_t not in filter_dic: - filter_dic[f_t] = 1 - else: - filter_dic[f_t] += 1 - - print("\n\t[필터 개수가 적은 것부터 최적화를 위함]") - sorted_filter_dic_len = sorted(filter_dic_len.keys()) - for filter_count in sorted_filter_dic_len: - for filter_type in filter_dic_len[filter_count]: - print("\t\t>{} > {}".format(filter_count, filter_type)) - - print("\n\t[걸러진 유일 필터]") - sorted_filter_dic_1 = sorted(filter_dic_1.items(), key=lambda x: x[1], reverse=True) - for i in range(len(sorted_filter_dic_1)): - print("\t\t>", sorted_filter_dic_1[i][0], "->", sorted_filter_dic_1[i][1]) - - print("\n\t[2개 필터에 걸린 경우]") - sorted_filter_dic_2 = sorted(filter_dic_2.items(), key=lambda x: x[1], reverse=True) - for i in range(len(sorted_filter_dic_2)): - print("\t\t>", sorted_filter_dic_2[i][0], "->", sorted_filter_dic_2[i][1]) - - print("\n\t[Filter 유형 별 걸린 개수]") - sorted_filter_dic = sorted(filter_dic.items(), key=lambda x: x[1], reverse=True) - for i in range(len(sorted_filter_dic)): - print("\t\t>", sorted_filter_dic[i][0], "->", sorted_filter_dic[i][1]) - - print("\n\t# 필터에 걸리지 않고 당첨된 회차") - print("\tcount: {:,} / total: {:,} ({:.2})%".format(len(no_filter_ball), len(df_ball), 100 * len(no_filter_ball) / len(df_ball))) - for no in no_filter_ball: - print("\t\t>", no, no_filter_ball[no]) - print("\tcount: {:,} / total: {:,} ({:.2})%".format(len(no_filter_ball), len(df_ball), 100 * len(no_filter_ball) / len(df_ball))) - - return win_count - - def find_final_candidates(self, no, df_ball, filter_ball=None): - final_candidates = [] - - generation_balls = list(range(1, 46)) - - nCr = list(itertools.combinations(generation_balls, 6)) - for idx, ball in enumerate(nCr): - - if idx % 1000000 == 0: - print(" - {} processed...".format(idx)) - - if filter_ball is not None and 0 < len(set(ball) & set(filter_ball)): - continue - - filter_type = self.ballFilter.filter(ball=ball, no=no, until_end=False, df=df_ball) - filter_size = len(filter_type) - - if filter_size: - continue - - final_candidates.append(ball) - - return final_candidates - - def check_filter_method(self, df_ball, p_win_count, filter_ball=None): - - win_count = 0 - for i in range(len(df_ball)-1, 0, -1): - - no = df_ball['no'].iloc[i] - answer = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = answer[1:7] - - if filter_ball is not None and len(set(answer) & set(filter_ball)): - continue - - filter_type = self.ballFilter.extract_final_candidates(answer, no=no, until_end=True, df=df_ball) - - if len(filter_type) == 0: - win_count += 1 - print("\t\t>{}. {}".format(no, answer)) - - print("\n\t> {} / {} p_win_count, {} total".format( win_count, p_win_count, len(df_ball)-1) ) - - return - - def validate(self, df_ball, nos=None): - win_history = {} - - for no in nos: - print(no, "processing...") - answer = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = answer[1:7] - - generation_balls = list(range(1, 46)) - nCr = list(itertools.combinations(generation_balls, 6)) - for idx, ball in enumerate(nCr): - if idx % 1000000 == 0: - print(" - {} processed...".format(idx)) - ball = list(ball) - filter_type = self.ballFilter.extract_final_candidates(ball, no=no, until_end=True, df=df_ball) - if 0 == len(filter_type) and len(set(answer)&set(ball))==6: - win_history[no] = answer - print("win.. no: {}, answer: {}".format(no, str(answer))) - break - - return win_history - - -if __name__ == '__main__': - - parser = argparse.ArgumentParser() - parser.add_argument("--resources", default="resources") - parser.add_argument( - "--ruleset", - default=None, - help="Ruleset JSON path (e.g. resources/rulesets/Coverage-First-S230a.json). Default: resources/rulesets/default.json if present.", - ) - parser.add_argument("--start-no", type=int, default=1) - parser.add_argument("--end-no", type=int, default=800) - args = parser.parse_args() - - resources_path = args.resources - - # Use full history txt to support previous-draw/window features, but only score [start_no, end_no] - lottoHistoryFileName = os.path.join(resources_path, 'lotto_history.txt') - df_ball = pd.read_csv(lottoHistoryFileName, header=None) - df_ball.columns = ['no', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'bn'] - - filter_ball=[] - filterTest = FilterTest(resources_path, ruleset_path=args.ruleset) - - print("STEP #1. 필터 방법 추출") - start = time.time() - win_count = filterTest.find_filter_method(df_ball, start_no=args.start_no, end_no=args.end_no) - process_time = datetime.timedelta(seconds=time.time() - start) - print("process_time: ", process_time) - - """ - print("\n\n") - no = df_ball['no'].values[-1] - ball = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = ball[1:7] - - print("STEP #0. 최종 후보 선정") - start = time.time() - final_candidates = filterTest.find_final_candidates(no, df_ball, filter_ball=None) - process_time = datetime.timedelta(seconds=time.time() - start) - print("process_time: ", process_time) - - print(" > size: {}".format(len(final_candidates))) - file_name = os.path.join(resources_path, 'final_candidates.biz_a1.txt') - with open(file_name, 'w+') as outFp: - for ball in final_candidates: - ball_str = [str(b) for b in answer] - outFp.write("{}\n".format(','.join(ball_str))) - - print('{}회, 정답: {}\n'.format(no, str(answer))) - """ - - #print("\n\n") - #print("STEP #2. 당첨 회수 확인") - #filterTest.check_filter_method(df_ball, win_count) - - # 오리지널 버전 (자질 파일에 고정): 당첨 22개 \ No newline at end of file diff --git a/train_2.py b/train_2.py deleted file mode 100644 index db66bcf..0000000 --- a/train_2.py +++ /dev/null @@ -1,231 +0,0 @@ -import os -import argparse -import pandas as pd -import itertools -from filter_model_2 import BallFilter -import time -import datetime - -class FilterTest: - - ballFilter = None - - def __init__(self, resources_path, ruleset_path=None): - lottoHistoryFileName = os.path.join(resources_path, 'lotto_history.json') - self.ballFilter = BallFilter(lottoHistoryFileName, ruleset_path=ruleset_path) - - return - - def find_filter_method(self, df_ball, start_no, end_no): - win_count = 0 - - no_filter_ball = {} - - printLog = True - filter_dic = {} - filter_dic_len = {} - filter_dic_1 = {} - filter_dic_2 = {} - for i in range(len(df_ball)-1, 19, -1): - no = df_ball['no'].iloc[i] - if no < start_no or end_no < no: - continue - - answer = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = answer[1:7] - - filter_type = self.ballFilter.filter(ball=answer, no=no, until_end=True, df=df_ball) - filter_type = list(filter_type) - size = len(filter_type) - - if size == 0: - win_count += 1 - no_filter_ball[no] = answer - print("\t", no) - elif size == 1: - key = filter_type[0] - if key not in filter_dic_1: - filter_dic_1[key] = 1 - else: - filter_dic_1[key] += 1 - - if printLog: - print("\t", no, filter_type) - elif size == 2: - key = ','.join(filter_type) - if key not in filter_dic_2: - filter_dic_2[key] = 1 - else: - filter_dic_2[key] += 1 - - if printLog: - print("\t", no, filter_type) - else: - if printLog: - print("\t", no, filter_type) - - # 회차별 필터개수가 적은 것을 정렬하기 위함 - if size not in filter_dic_len: - filter_dic_len[size] = [] - filter_dic_len[size].append(filter_type) - - for f_t in filter_type: - if f_t not in filter_dic: - filter_dic[f_t] = 1 - else: - filter_dic[f_t] += 1 - - print("\n\t[필터 개수가 적은 것부터 최적화를 위함]") - sorted_filter_dic_len = sorted(filter_dic_len.keys()) - for filter_count in sorted_filter_dic_len: - for filter_type in filter_dic_len[filter_count]: - print("\t\t>{} > {}".format(filter_count, filter_type)) - - print("\n\t[걸러진 유일 필터]") - sorted_filter_dic_1 = sorted(filter_dic_1.items(), key=lambda x: x[1], reverse=True) - for i in range(len(sorted_filter_dic_1)): - print("\t\t>", sorted_filter_dic_1[i][0], "->", sorted_filter_dic_1[i][1]) - - print("\n\t[2개 필터에 걸린 경우]") - sorted_filter_dic_2 = sorted(filter_dic_2.items(), key=lambda x: x[1], reverse=True) - for i in range(len(sorted_filter_dic_2)): - print("\t\t>", sorted_filter_dic_2[i][0], "->", sorted_filter_dic_2[i][1]) - - print("\n\t[Filter 유형 별 걸린 개수]") - sorted_filter_dic = sorted(filter_dic.items(), key=lambda x: x[1], reverse=True) - for i in range(len(sorted_filter_dic)): - print("\t\t>", sorted_filter_dic[i][0], "->", sorted_filter_dic[i][1]) - - print("\n\t# 필터에 걸리지 않고 당첨된 회차") - print("\tcount: {:,} / total: {:,} ({:.2})%".format(len(no_filter_ball), len(df_ball), 100 * len(no_filter_ball) / len(df_ball))) - for no in no_filter_ball: - print("\t\t>", no, no_filter_ball[no]) - print("\tcount: {:,} / total: {:,} ({:.2})%".format(len(no_filter_ball), len(df_ball), 100 * len(no_filter_ball) / len(df_ball))) - - return win_count - - def find_final_candidates(self, no, df_ball, filter_ball=None): - final_candidates = [] - - generation_balls = list(range(1, 46)) - - nCr = list(itertools.combinations(generation_balls, 6)) - for idx, ball in enumerate(nCr): - - if idx % 1000000 == 0: - print(" - {} processed...".format(idx)) - - if filter_ball is not None and 0 < len(set(ball) & set(filter_ball)): - continue - - filter_type = self.ballFilter.filter(ball=ball, no=no, until_end=False, df=df_ball) - filter_size = len(filter_type) - - if filter_size: - continue - - final_candidates.append(ball) - - return final_candidates - - def check_filter_method(self, df_ball, p_win_count, filter_ball=None): - - win_count = 0 - for i in range(len(df_ball)-1, 0, -1): - - no = df_ball['no'].iloc[i] - answer = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = answer[1:7] - - if filter_ball is not None and len(set(answer) & set(filter_ball)): - continue - - filter_type = self.ballFilter.extract_final_candidates(answer, no=no, until_end=True, df=df_ball) - - if len(filter_type) == 0: - win_count += 1 - print("\t\t>{}. {}".format(no, answer)) - - print("\n\t> {} / {} p_win_count, {} total".format( win_count, p_win_count, len(df_ball)-1) ) - - return - - def validate(self, df_ball, nos=None): - win_history = {} - - for no in nos: - print(no, "processing...") - answer = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = answer[1:7] - - generation_balls = list(range(1, 46)) - nCr = list(itertools.combinations(generation_balls, 6)) - for idx, ball in enumerate(nCr): - if idx % 1000000 == 0: - print(" - {} processed...".format(idx)) - ball = list(ball) - filter_type = self.ballFilter.extract_final_candidates(ball, no=no, until_end=True, df=df_ball) - if 0 == len(filter_type) and len(set(answer)&set(ball))==6: - win_history[no] = answer - print("win.. no: {}, answer: {}".format(no, str(answer))) - break - - return win_history - - -if __name__ == '__main__': - - parser = argparse.ArgumentParser() - parser.add_argument("--resources", default="resources") - parser.add_argument( - "--ruleset", - default=None, - help="Ruleset JSON path (e.g. resources/rulesets/Coverage-First-S230a.json). Default: resources/rulesets/default.json if present.", - ) - parser.add_argument("--start-no", type=int, default=1) - parser.add_argument("--end-no", type=int, default=800) - args = parser.parse_args() - - resources_path = args.resources - - # Use full history txt to support previous-draw/window features, but only score [start_no, end_no] - lottoHistoryFileName = os.path.join(resources_path, 'lotto_history.txt') - df_ball = pd.read_csv(lottoHistoryFileName, header=None) - df_ball.columns = ['no', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'bn'] - - filter_ball=[] - filterTest = FilterTest(resources_path, ruleset_path=args.ruleset) - - print("STEP #1. 필터 방법 추출") - start = time.time() - win_count = filterTest.find_filter_method(df_ball, start_no=args.start_no, end_no=args.end_no) - process_time = datetime.timedelta(seconds=time.time() - start) - print("process_time: ", process_time) - - """ - print("\n\n") - no = df_ball['no'].values[-1] - ball = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = ball[1:7] - - print("STEP #0. 최종 후보 선정") - start = time.time() - final_candidates = filterTest.find_final_candidates(no, df_ball, filter_ball=None) - process_time = datetime.timedelta(seconds=time.time() - start) - print("process_time: ", process_time) - - print(" > size: {}".format(len(final_candidates))) - file_name = os.path.join(resources_path, 'final_candidates.biz_a1.txt') - with open(file_name, 'w+') as outFp: - for ball in final_candidates: - ball_str = [str(b) for b in answer] - outFp.write("{}\n".format(','.join(ball_str))) - - print('{}회, 정답: {}\n'.format(no, str(answer))) - """ - - #print("\n\n") - #print("STEP #2. 당첨 회수 확인") - #filterTest.check_filter_method(df_ball, win_count) - - # 오리지널 버전 (자질 파일에 고정): 당첨 22개 \ No newline at end of file diff --git a/train_3.py b/train_3.py deleted file mode 100644 index 7e37e3a..0000000 --- a/train_3.py +++ /dev/null @@ -1,231 +0,0 @@ -import os -import argparse -import pandas as pd -import itertools -from filter_model_3 import BallFilter -import time -import datetime - -class FilterTest: - - ballFilter = None - - def __init__(self, resources_path, ruleset_path=None): - lottoHistoryFileName = os.path.join(resources_path, 'lotto_history.json') - self.ballFilter = BallFilter(lottoHistoryFileName, ruleset_path=ruleset_path) - - return - - def find_filter_method(self, df_ball, start_no, end_no): - win_count = 0 - - no_filter_ball = {} - - printLog = True - filter_dic = {} - filter_dic_len = {} - filter_dic_1 = {} - filter_dic_2 = {} - for i in range(len(df_ball)-1, 19, -1): - no = df_ball['no'].iloc[i] - if no < start_no or end_no < no: - continue - - answer = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = answer[1:7] - - filter_type = self.ballFilter.filter(ball=answer, no=no, until_end=True, df=df_ball) - filter_type = list(filter_type) - size = len(filter_type) - - if size == 0: - win_count += 1 - no_filter_ball[no] = answer - print("\t", no) - elif size == 1: - key = filter_type[0] - if key not in filter_dic_1: - filter_dic_1[key] = 1 - else: - filter_dic_1[key] += 1 - - if printLog: - print("\t", no, filter_type) - elif size == 2: - key = ','.join(filter_type) - if key not in filter_dic_2: - filter_dic_2[key] = 1 - else: - filter_dic_2[key] += 1 - - if printLog: - print("\t", no, filter_type) - else: - if printLog: - print("\t", no, filter_type) - - # 회차별 필터개수가 적은 것을 정렬하기 위함 - if size not in filter_dic_len: - filter_dic_len[size] = [] - filter_dic_len[size].append(filter_type) - - for f_t in filter_type: - if f_t not in filter_dic: - filter_dic[f_t] = 1 - else: - filter_dic[f_t] += 1 - - print("\n\t[필터 개수가 적은 것부터 최적화를 위함]") - sorted_filter_dic_len = sorted(filter_dic_len.keys()) - for filter_count in sorted_filter_dic_len: - for filter_type in filter_dic_len[filter_count]: - print("\t\t>{} > {}".format(filter_count, filter_type)) - - print("\n\t[걸러진 유일 필터]") - sorted_filter_dic_1 = sorted(filter_dic_1.items(), key=lambda x: x[1], reverse=True) - for i in range(len(sorted_filter_dic_1)): - print("\t\t>", sorted_filter_dic_1[i][0], "->", sorted_filter_dic_1[i][1]) - - print("\n\t[2개 필터에 걸린 경우]") - sorted_filter_dic_2 = sorted(filter_dic_2.items(), key=lambda x: x[1], reverse=True) - for i in range(len(sorted_filter_dic_2)): - print("\t\t>", sorted_filter_dic_2[i][0], "->", sorted_filter_dic_2[i][1]) - - print("\n\t[Filter 유형 별 걸린 개수]") - sorted_filter_dic = sorted(filter_dic.items(), key=lambda x: x[1], reverse=True) - for i in range(len(sorted_filter_dic)): - print("\t\t>", sorted_filter_dic[i][0], "->", sorted_filter_dic[i][1]) - - print("\n\t# 필터에 걸리지 않고 당첨된 회차") - print("\tcount: {:,} / total: {:,} ({:.2})%".format(len(no_filter_ball), len(df_ball), 100 * len(no_filter_ball) / len(df_ball))) - for no in no_filter_ball: - print("\t\t>", no, no_filter_ball[no]) - print("\tcount: {:,} / total: {:,} ({:.2})%".format(len(no_filter_ball), len(df_ball), 100 * len(no_filter_ball) / len(df_ball))) - - return win_count - - def find_final_candidates(self, no, df_ball, filter_ball=None): - final_candidates = [] - - generation_balls = list(range(1, 46)) - - nCr = list(itertools.combinations(generation_balls, 6)) - for idx, ball in enumerate(nCr): - - if idx % 1000000 == 0: - print(" - {} processed...".format(idx)) - - if filter_ball is not None and 0 < len(set(ball) & set(filter_ball)): - continue - - filter_type = self.ballFilter.filter(ball=ball, no=no, until_end=False, df=df_ball) - filter_size = len(filter_type) - - if filter_size: - continue - - final_candidates.append(ball) - - return final_candidates - - def check_filter_method(self, df_ball, p_win_count, filter_ball=None): - - win_count = 0 - for i in range(len(df_ball)-1, 0, -1): - - no = df_ball['no'].iloc[i] - answer = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = answer[1:7] - - if filter_ball is not None and len(set(answer) & set(filter_ball)): - continue - - filter_type = self.ballFilter.extract_final_candidates(answer, no=no, until_end=True, df=df_ball) - - if len(filter_type) == 0: - win_count += 1 - print("\t\t>{}. {}".format(no, answer)) - - print("\n\t> {} / {} p_win_count, {} total".format( win_count, p_win_count, len(df_ball)-1) ) - - return - - def validate(self, df_ball, nos=None): - win_history = {} - - for no in nos: - print(no, "processing...") - answer = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = answer[1:7] - - generation_balls = list(range(1, 46)) - nCr = list(itertools.combinations(generation_balls, 6)) - for idx, ball in enumerate(nCr): - if idx % 1000000 == 0: - print(" - {} processed...".format(idx)) - ball = list(ball) - filter_type = self.ballFilter.extract_final_candidates(ball, no=no, until_end=True, df=df_ball) - if 0 == len(filter_type) and len(set(answer)&set(ball))==6: - win_history[no] = answer - print("win.. no: {}, answer: {}".format(no, str(answer))) - break - - return win_history - - -if __name__ == '__main__': - - parser = argparse.ArgumentParser() - parser.add_argument("--resources", default="resources") - parser.add_argument( - "--ruleset", - default=None, - help="Ruleset JSON path (e.g. resources/rulesets/Coverage-First-S230a.json). Default: resources/rulesets/default.json if present.", - ) - parser.add_argument("--start-no", type=int, default=1) - parser.add_argument("--end-no", type=int, default=800) - args = parser.parse_args() - - resources_path = args.resources - - # Use full history txt to support previous-draw/window features, but only score [start_no, end_no] - lottoHistoryFileName = os.path.join(resources_path, 'lotto_history.txt') - df_ball = pd.read_csv(lottoHistoryFileName, header=None) - df_ball.columns = ['no', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'bn'] - - filter_ball=[] - filterTest = FilterTest(resources_path, ruleset_path=args.ruleset) - - print("STEP #1. 필터 방법 추출") - start = time.time() - win_count = filterTest.find_filter_method(df_ball, start_no=args.start_no, end_no=args.end_no) - process_time = datetime.timedelta(seconds=time.time() - start) - print("process_time: ", process_time) - - """ - print("\n\n") - no = df_ball['no'].values[-1] - ball = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = ball[1:7] - - print("STEP #0. 최종 후보 선정") - start = time.time() - final_candidates = filterTest.find_final_candidates(no, df_ball, filter_ball=None) - process_time = datetime.timedelta(seconds=time.time() - start) - print("process_time: ", process_time) - - print(" > size: {}".format(len(final_candidates))) - file_name = os.path.join(resources_path, 'final_candidates.biz_a1.txt') - with open(file_name, 'w+') as outFp: - for ball in final_candidates: - ball_str = [str(b) for b in answer] - outFp.write("{}\n".format(','.join(ball_str))) - - print('{}회, 정답: {}\n'.format(no, str(answer))) - """ - - #print("\n\n") - #print("STEP #2. 당첨 회수 확인") - #filterTest.check_filter_method(df_ball, win_count) - - # 오리지널 버전 (자질 파일에 고정): 당첨 22개 \ No newline at end of file diff --git a/valid_1.py b/valid_1.py deleted file mode 100644 index 197e7fa..0000000 --- a/valid_1.py +++ /dev/null @@ -1,234 +0,0 @@ -import os -import argparse -import pandas as pd -import itertools -from filter_model_1 import BallFilter -import time -import datetime - -class FilterTest: - - ballFilter = None - - def __init__(self, resources_path, ruleset_path=None, history_json="lotto_history.json"): - # validation should use full history for previous-draw/window features - lottoHistoryFileName = os.path.join(resources_path, history_json) - self.ballFilter = BallFilter(lottoHistoryFileName, ruleset_path=ruleset_path) - - return - - def find_filter_method(self, df_ball, start_no, end_no): - win_count = 0 - - no_filter_ball = {} - - printLog = True - filter_dic = {} - filter_dic_len = {} - filter_dic_1 = {} - filter_dic_2 = {} - # evaluate only requested range, but allow df_ball to contain full history - for i in range(len(df_ball) - 1, -1, -1): - no = int(df_ball['no'].iloc[i]) - if no < start_no or end_no < no: - continue - answer = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = answer[1:7] - - filter_type = self.ballFilter.filter(ball=answer, no=no, until_end=True, df=df_ball) - filter_type = list(filter_type) - size = len(filter_type) - - if size == 0: - win_count += 1 - no_filter_ball[no] = answer - print("\t", no) - elif size == 1: - key = filter_type[0] - if key not in filter_dic_1: - filter_dic_1[key] = 1 - else: - filter_dic_1[key] += 1 - - if printLog: - print("\t", no, filter_type) - elif size == 2: - key = ','.join(filter_type) - if key not in filter_dic_2: - filter_dic_2[key] = 1 - else: - filter_dic_2[key] += 1 - - if printLog: - print("\t", no, filter_type) - else: - if printLog: - print("\t", no, filter_type) - - # 회차별 필터개수가 적은 것을 정렬하기 위함 - if size not in filter_dic_len: - filter_dic_len[size] = [] - filter_dic_len[size].append(filter_type) - - for f_t in filter_type: - if f_t not in filter_dic: - filter_dic[f_t] = 1 - else: - filter_dic[f_t] += 1 - - print("\n\t[필터 개수가 적은 것부터 최적화를 위함]") - sorted_filter_dic_len = sorted(filter_dic_len.keys()) - for filter_count in sorted_filter_dic_len: - for filter_type in filter_dic_len[filter_count]: - print("\t\t>{} > {}".format(filter_count, filter_type)) - - print("\n\t[걸러진 유일 필터]") - sorted_filter_dic_1 = sorted(filter_dic_1.items(), key=lambda x: x[1], reverse=True) - for i in range(len(sorted_filter_dic_1)): - print("\t\t>", sorted_filter_dic_1[i][0], "->", sorted_filter_dic_1[i][1]) - - print("\n\t[2개 필터에 걸린 경우]") - sorted_filter_dic_2 = sorted(filter_dic_2.items(), key=lambda x: x[1], reverse=True) - for i in range(len(sorted_filter_dic_2)): - print("\t\t>", sorted_filter_dic_2[i][0], "->", sorted_filter_dic_2[i][1]) - - print("\n\t[Filter 유형 별 걸린 개수]") - sorted_filter_dic = sorted(filter_dic.items(), key=lambda x: x[1], reverse=True) - for i in range(len(sorted_filter_dic)): - print("\t\t>", sorted_filter_dic[i][0], "->", sorted_filter_dic[i][1]) - - print("\n\t# 필터에 걸리지 않고 당첨된 회차") - total = max(0, end_no - start_no + 1) - rate = (100 * len(no_filter_ball) / total) if total else 0.0 - print("\tcount: {:,} / total: {:,} ({:.2})%".format(len(no_filter_ball), total, rate)) - for no in no_filter_ball: - print("\t\t>", no, no_filter_ball[no]) - print("\tcount: {:,} / total: {:,} ({:.2})%".format(len(no_filter_ball), total, rate)) - - return win_count - - def find_final_candidates(self, no, df_ball, filter_ball=None): - final_candidates = [] - - generation_balls = list(range(1, 46)) - - nCr = list(itertools.combinations(generation_balls, 6)) - for idx, ball in enumerate(nCr): - - if idx % 1000000 == 0: - print(" - {} processed...".format(idx)) - - if filter_ball is not None and 0 < len(set(ball) & set(filter_ball)): - continue - - filter_type = self.ballFilter.filter(ball=ball, no=no, until_end=False, df=df_ball) - filter_size = len(filter_type) - - if filter_size: - continue - - final_candidates.append(ball) - - return final_candidates - - def check_filter_method(self, df_ball, p_win_count, filter_ball=None): - - win_count = 0 - for i in range(len(df_ball)-1, 0, -1): - - no = df_ball['no'].iloc[i] - answer = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = answer[1:7] - - if filter_ball is not None and len(set(answer) & set(filter_ball)): - continue - - filter_type = self.ballFilter.extract_final_candidates(answer, no=no, until_end=True, df=df_ball) - - if len(filter_type) == 0: - win_count += 1 - print("\t\t>{}. {}".format(no, answer)) - - print("\n\t> {} / {} p_win_count, {} total".format( win_count, p_win_count, len(df_ball)-1) ) - - return - - def validate(self, df_ball, nos=None): - win_history = {} - - for no in nos: - print(no, "processing...") - answer = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = answer[1:7] - - generation_balls = list(range(1, 46)) - nCr = list(itertools.combinations(generation_balls, 6)) - for idx, ball in enumerate(nCr): - if idx % 1000000 == 0: - print(" - {} processed...".format(idx)) - ball = list(ball) - filter_type = self.ballFilter.extract_final_candidates(ball, no=no, until_end=True, df=df_ball) - if 0 == len(filter_type) and len(set(answer)&set(ball))==6: - win_history[no] = answer - print("win.. no: {}, answer: {}".format(no, str(answer))) - break - - return win_history - - -if __name__ == '__main__': - - parser = argparse.ArgumentParser() - parser.add_argument("--resources", default="resources") - parser.add_argument( - "--ruleset", - default=None, - help="Ruleset JSON path (e.g. resources/rulesets/Coverage-First-S230a.json). Default: resources/rulesets/default.json if present.", - ) - parser.add_argument("--start-no", type=int, default=801) - parser.add_argument("--end-no", type=int, default=1000) - args = parser.parse_args() - - resources_path = args.resources - - # Use full history txt to support previous-draw/window features, but only score [start_no, end_no] - lottoHistoryFileName = os.path.join(resources_path, 'lotto_history.txt') - df_ball = pd.read_csv(lottoHistoryFileName, header=None) - df_ball.columns = ['no', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'bn'] - - filter_ball=[] - filterTest = FilterTest(resources_path, ruleset_path=args.ruleset, history_json='lotto_history.json') - - print("STEP #1. 필터 방법 추출") - start = time.time() - win_count = filterTest.find_filter_method(df_ball, start_no=args.start_no, end_no=args.end_no) - process_time = datetime.timedelta(seconds=time.time() - start) - print("process_time: ", process_time) - - """ - print("\n\n") - no = df_ball['no'].values[-1] - ball = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = ball[1:7] - - print("STEP #0. 최종 후보 선정") - start = time.time() - final_candidates = filterTest.find_final_candidates(no, df_ball, filter_ball=None) - process_time = datetime.timedelta(seconds=time.time() - start) - print("process_time: ", process_time) - - print(" > size: {}".format(len(final_candidates))) - file_name = os.path.join(resources_path, 'final_candidates.biz_a1.txt') - with open(file_name, 'w+') as outFp: - for ball in final_candidates: - ball_str = [str(b) for b in answer] - outFp.write("{}\n".format(','.join(ball_str))) - - print('{}회, 정답: {}\n'.format(no, str(answer))) - """ - - #print("\n\n") - #print("STEP #2. 당첨 회수 확인") - #filterTest.check_filter_method(df_ball, win_count) - - # 오리지널 버전 (자질 파일에 고정): 당첨 22개 \ No newline at end of file diff --git a/valid_2.py b/valid_2.py deleted file mode 100644 index f9cb4fa..0000000 --- a/valid_2.py +++ /dev/null @@ -1,234 +0,0 @@ -import os -import argparse -import pandas as pd -import itertools -from filter_model_2 import BallFilter -import time -import datetime - -class FilterTest: - - ballFilter = None - - def __init__(self, resources_path, ruleset_path=None, history_json="lotto_history.json"): - # validation should use full history for previous-draw/window features - lottoHistoryFileName = os.path.join(resources_path, history_json) - self.ballFilter = BallFilter(lottoHistoryFileName, ruleset_path=ruleset_path) - - return - - def find_filter_method(self, df_ball, start_no, end_no): - win_count = 0 - - no_filter_ball = {} - - printLog = True - filter_dic = {} - filter_dic_len = {} - filter_dic_1 = {} - filter_dic_2 = {} - # evaluate only requested range, but allow df_ball to contain full history - for i in range(len(df_ball) - 1, -1, -1): - no = int(df_ball['no'].iloc[i]) - if no < start_no or end_no < no: - continue - answer = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = answer[1:7] - - filter_type = self.ballFilter.filter(ball=answer, no=no, until_end=True, df=df_ball) - filter_type = list(filter_type) - size = len(filter_type) - - if size == 0: - win_count += 1 - no_filter_ball[no] = answer - print("\t", no) - elif size == 1: - key = filter_type[0] - if key not in filter_dic_1: - filter_dic_1[key] = 1 - else: - filter_dic_1[key] += 1 - - if printLog: - print("\t", no, filter_type) - elif size == 2: - key = ','.join(filter_type) - if key not in filter_dic_2: - filter_dic_2[key] = 1 - else: - filter_dic_2[key] += 1 - - if printLog: - print("\t", no, filter_type) - else: - if printLog: - print("\t", no, filter_type) - - # 회차별 필터개수가 적은 것을 정렬하기 위함 - if size not in filter_dic_len: - filter_dic_len[size] = [] - filter_dic_len[size].append(filter_type) - - for f_t in filter_type: - if f_t not in filter_dic: - filter_dic[f_t] = 1 - else: - filter_dic[f_t] += 1 - - print("\n\t[필터 개수가 적은 것부터 최적화를 위함]") - sorted_filter_dic_len = sorted(filter_dic_len.keys()) - for filter_count in sorted_filter_dic_len: - for filter_type in filter_dic_len[filter_count]: - print("\t\t>{} > {}".format(filter_count, filter_type)) - - print("\n\t[걸러진 유일 필터]") - sorted_filter_dic_1 = sorted(filter_dic_1.items(), key=lambda x: x[1], reverse=True) - for i in range(len(sorted_filter_dic_1)): - print("\t\t>", sorted_filter_dic_1[i][0], "->", sorted_filter_dic_1[i][1]) - - print("\n\t[2개 필터에 걸린 경우]") - sorted_filter_dic_2 = sorted(filter_dic_2.items(), key=lambda x: x[1], reverse=True) - for i in range(len(sorted_filter_dic_2)): - print("\t\t>", sorted_filter_dic_2[i][0], "->", sorted_filter_dic_2[i][1]) - - print("\n\t[Filter 유형 별 걸린 개수]") - sorted_filter_dic = sorted(filter_dic.items(), key=lambda x: x[1], reverse=True) - for i in range(len(sorted_filter_dic)): - print("\t\t>", sorted_filter_dic[i][0], "->", sorted_filter_dic[i][1]) - - print("\n\t# 필터에 걸리지 않고 당첨된 회차") - total = max(0, end_no - start_no + 1) - rate = (100 * len(no_filter_ball) / total) if total else 0.0 - print("\tcount: {:,} / total: {:,} ({:.2})%".format(len(no_filter_ball), total, rate)) - for no in no_filter_ball: - print("\t\t>", no, no_filter_ball[no]) - print("\tcount: {:,} / total: {:,} ({:.2})%".format(len(no_filter_ball), total, rate)) - - return win_count - - def find_final_candidates(self, no, df_ball, filter_ball=None): - final_candidates = [] - - generation_balls = list(range(1, 46)) - - nCr = list(itertools.combinations(generation_balls, 6)) - for idx, ball in enumerate(nCr): - - if idx % 1000000 == 0: - print(" - {} processed...".format(idx)) - - if filter_ball is not None and 0 < len(set(ball) & set(filter_ball)): - continue - - filter_type = self.ballFilter.filter(ball=ball, no=no, until_end=False, df=df_ball) - filter_size = len(filter_type) - - if filter_size: - continue - - final_candidates.append(ball) - - return final_candidates - - def check_filter_method(self, df_ball, p_win_count, filter_ball=None): - - win_count = 0 - for i in range(len(df_ball)-1, 0, -1): - - no = df_ball['no'].iloc[i] - answer = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = answer[1:7] - - if filter_ball is not None and len(set(answer) & set(filter_ball)): - continue - - filter_type = self.ballFilter.extract_final_candidates(answer, no=no, until_end=True, df=df_ball) - - if len(filter_type) == 0: - win_count += 1 - print("\t\t>{}. {}".format(no, answer)) - - print("\n\t> {} / {} p_win_count, {} total".format( win_count, p_win_count, len(df_ball)-1) ) - - return - - def validate(self, df_ball, nos=None): - win_history = {} - - for no in nos: - print(no, "processing...") - answer = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = answer[1:7] - - generation_balls = list(range(1, 46)) - nCr = list(itertools.combinations(generation_balls, 6)) - for idx, ball in enumerate(nCr): - if idx % 1000000 == 0: - print(" - {} processed...".format(idx)) - ball = list(ball) - filter_type = self.ballFilter.extract_final_candidates(ball, no=no, until_end=True, df=df_ball) - if 0 == len(filter_type) and len(set(answer)&set(ball))==6: - win_history[no] = answer - print("win.. no: {}, answer: {}".format(no, str(answer))) - break - - return win_history - - -if __name__ == '__main__': - - parser = argparse.ArgumentParser() - parser.add_argument("--resources", default="resources") - parser.add_argument( - "--ruleset", - default=None, - help="Ruleset JSON path (e.g. resources/rulesets/Coverage-First-S230a.json). Default: resources/rulesets/default.json if present.", - ) - parser.add_argument("--start-no", type=int, default=801) - parser.add_argument("--end-no", type=int, default=1000) - args = parser.parse_args() - - resources_path = args.resources - - # Use full history txt to support previous-draw/window features, but only score [start_no, end_no] - lottoHistoryFileName = os.path.join(resources_path, 'lotto_history.txt') - df_ball = pd.read_csv(lottoHistoryFileName, header=None) - df_ball.columns = ['no', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'bn'] - - filter_ball=[] - filterTest = FilterTest(resources_path, ruleset_path=args.ruleset, history_json='lotto_history.json') - - print("STEP #1. 필터 방법 추출") - start = time.time() - win_count = filterTest.find_filter_method(df_ball, start_no=args.start_no, end_no=args.end_no) - process_time = datetime.timedelta(seconds=time.time() - start) - print("process_time: ", process_time) - - """ - print("\n\n") - no = df_ball['no'].values[-1] - ball = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = ball[1:7] - - print("STEP #0. 최종 후보 선정") - start = time.time() - final_candidates = filterTest.find_final_candidates(no, df_ball, filter_ball=None) - process_time = datetime.timedelta(seconds=time.time() - start) - print("process_time: ", process_time) - - print(" > size: {}".format(len(final_candidates))) - file_name = os.path.join(resources_path, 'final_candidates.biz_a1.txt') - with open(file_name, 'w+') as outFp: - for ball in final_candidates: - ball_str = [str(b) for b in answer] - outFp.write("{}\n".format(','.join(ball_str))) - - print('{}회, 정답: {}\n'.format(no, str(answer))) - """ - - #print("\n\n") - #print("STEP #2. 당첨 회수 확인") - #filterTest.check_filter_method(df_ball, win_count) - - # 오리지널 버전 (자질 파일에 고정): 당첨 22개 \ No newline at end of file diff --git a/valid_3.py b/valid_3.py deleted file mode 100644 index 5a2c5ad..0000000 --- a/valid_3.py +++ /dev/null @@ -1,234 +0,0 @@ -import os -import argparse -import pandas as pd -import itertools -from filter_model_3 import BallFilter -import time -import datetime - -class FilterTest: - - ballFilter = None - - def __init__(self, resources_path, ruleset_path=None, history_json="lotto_history.json"): - # validation should use full history for previous-draw/window features - lottoHistoryFileName = os.path.join(resources_path, history_json) - self.ballFilter = BallFilter(lottoHistoryFileName, ruleset_path=ruleset_path) - - return - - def find_filter_method(self, df_ball, start_no, end_no): - win_count = 0 - - no_filter_ball = {} - - printLog = True - filter_dic = {} - filter_dic_len = {} - filter_dic_1 = {} - filter_dic_2 = {} - # evaluate only requested range, but allow df_ball to contain full history - for i in range(len(df_ball) - 1, -1, -1): - no = int(df_ball['no'].iloc[i]) - if no < start_no or end_no < no: - continue - answer = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = answer[1:7] - - filter_type = self.ballFilter.filter(ball=answer, no=no, until_end=True, df=df_ball) - filter_type = list(filter_type) - size = len(filter_type) - - if size == 0: - win_count += 1 - no_filter_ball[no] = answer - print("\t", no) - elif size == 1: - key = filter_type[0] - if key not in filter_dic_1: - filter_dic_1[key] = 1 - else: - filter_dic_1[key] += 1 - - if printLog: - print("\t", no, filter_type) - elif size == 2: - key = ','.join(filter_type) - if key not in filter_dic_2: - filter_dic_2[key] = 1 - else: - filter_dic_2[key] += 1 - - if printLog: - print("\t", no, filter_type) - else: - if printLog: - print("\t", no, filter_type) - - # 회차별 필터개수가 적은 것을 정렬하기 위함 - if size not in filter_dic_len: - filter_dic_len[size] = [] - filter_dic_len[size].append(filter_type) - - for f_t in filter_type: - if f_t not in filter_dic: - filter_dic[f_t] = 1 - else: - filter_dic[f_t] += 1 - - print("\n\t[필터 개수가 적은 것부터 최적화를 위함]") - sorted_filter_dic_len = sorted(filter_dic_len.keys()) - for filter_count in sorted_filter_dic_len: - for filter_type in filter_dic_len[filter_count]: - print("\t\t>{} > {}".format(filter_count, filter_type)) - - print("\n\t[걸러진 유일 필터]") - sorted_filter_dic_1 = sorted(filter_dic_1.items(), key=lambda x: x[1], reverse=True) - for i in range(len(sorted_filter_dic_1)): - print("\t\t>", sorted_filter_dic_1[i][0], "->", sorted_filter_dic_1[i][1]) - - print("\n\t[2개 필터에 걸린 경우]") - sorted_filter_dic_2 = sorted(filter_dic_2.items(), key=lambda x: x[1], reverse=True) - for i in range(len(sorted_filter_dic_2)): - print("\t\t>", sorted_filter_dic_2[i][0], "->", sorted_filter_dic_2[i][1]) - - print("\n\t[Filter 유형 별 걸린 개수]") - sorted_filter_dic = sorted(filter_dic.items(), key=lambda x: x[1], reverse=True) - for i in range(len(sorted_filter_dic)): - print("\t\t>", sorted_filter_dic[i][0], "->", sorted_filter_dic[i][1]) - - print("\n\t# 필터에 걸리지 않고 당첨된 회차") - total = max(0, end_no - start_no + 1) - rate = (100 * len(no_filter_ball) / total) if total else 0.0 - print("\tcount: {:,} / total: {:,} ({:.2})%".format(len(no_filter_ball), total, rate)) - for no in no_filter_ball: - print("\t\t>", no, no_filter_ball[no]) - print("\tcount: {:,} / total: {:,} ({:.2})%".format(len(no_filter_ball), total, rate)) - - return win_count - - def find_final_candidates(self, no, df_ball, filter_ball=None): - final_candidates = [] - - generation_balls = list(range(1, 46)) - - nCr = list(itertools.combinations(generation_balls, 6)) - for idx, ball in enumerate(nCr): - - if idx % 1000000 == 0: - print(" - {} processed...".format(idx)) - - if filter_ball is not None and 0 < len(set(ball) & set(filter_ball)): - continue - - filter_type = self.ballFilter.filter(ball=ball, no=no, until_end=False, df=df_ball) - filter_size = len(filter_type) - - if filter_size: - continue - - final_candidates.append(ball) - - return final_candidates - - def check_filter_method(self, df_ball, p_win_count, filter_ball=None): - - win_count = 0 - for i in range(len(df_ball)-1, 0, -1): - - no = df_ball['no'].iloc[i] - answer = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = answer[1:7] - - if filter_ball is not None and len(set(answer) & set(filter_ball)): - continue - - filter_type = self.ballFilter.extract_final_candidates(answer, no=no, until_end=True, df=df_ball) - - if len(filter_type) == 0: - win_count += 1 - print("\t\t>{}. {}".format(no, answer)) - - print("\n\t> {} / {} p_win_count, {} total".format( win_count, p_win_count, len(df_ball)-1) ) - - return - - def validate(self, df_ball, nos=None): - win_history = {} - - for no in nos: - print(no, "processing...") - answer = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = answer[1:7] - - generation_balls = list(range(1, 46)) - nCr = list(itertools.combinations(generation_balls, 6)) - for idx, ball in enumerate(nCr): - if idx % 1000000 == 0: - print(" - {} processed...".format(idx)) - ball = list(ball) - filter_type = self.ballFilter.extract_final_candidates(ball, no=no, until_end=True, df=df_ball) - if 0 == len(filter_type) and len(set(answer)&set(ball))==6: - win_history[no] = answer - print("win.. no: {}, answer: {}".format(no, str(answer))) - break - - return win_history - - -if __name__ == '__main__': - - parser = argparse.ArgumentParser() - parser.add_argument("--resources", default="resources") - parser.add_argument( - "--ruleset", - default=None, - help="Ruleset JSON path (e.g. resources/rulesets/Coverage-First-S230a.json). Default: resources/rulesets/default.json if present.", - ) - parser.add_argument("--start-no", type=int, default=801) - parser.add_argument("--end-no", type=int, default=1000) - args = parser.parse_args() - - resources_path = args.resources - - # Use full history txt to support previous-draw/window features, but only score [start_no, end_no] - lottoHistoryFileName = os.path.join(resources_path, 'lotto_history.txt') - df_ball = pd.read_csv(lottoHistoryFileName, header=None) - df_ball.columns = ['no', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'bn'] - - filter_ball=[] - filterTest = FilterTest(resources_path, ruleset_path=args.ruleset, history_json='lotto_history.json') - - print("STEP #1. 필터 방법 추출") - start = time.time() - win_count = filterTest.find_filter_method(df_ball, start_no=args.start_no, end_no=args.end_no) - process_time = datetime.timedelta(seconds=time.time() - start) - print("process_time: ", process_time) - - """ - print("\n\n") - no = df_ball['no'].values[-1] - ball = df_ball[df_ball['no'] == no].values.tolist()[0] - answer = ball[1:7] - - print("STEP #0. 최종 후보 선정") - start = time.time() - final_candidates = filterTest.find_final_candidates(no, df_ball, filter_ball=None) - process_time = datetime.timedelta(seconds=time.time() - start) - print("process_time: ", process_time) - - print(" > size: {}".format(len(final_candidates))) - file_name = os.path.join(resources_path, 'final_candidates.biz_a1.txt') - with open(file_name, 'w+') as outFp: - for ball in final_candidates: - ball_str = [str(b) for b in answer] - outFp.write("{}\n".format(','.join(ball_str))) - - print('{}회, 정답: {}\n'.format(no, str(answer))) - """ - - #print("\n\n") - #print("STEP #2. 당첨 회수 확인") - #filterTest.check_filter_method(df_ball, win_count) - - # 오리지널 버전 (자질 파일에 고정): 당첨 22개 \ No newline at end of file