diff --git a/README.md b/README.md index 6a32c1a..a14b5a3 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,22 @@ # 실행 순서 +## final_BallFilter · `final_filterTest.py` (miniconda **ncue**) + +임계값은 `tools/compute_final_filter_params.py`가 학습 구간(1~800회) 분포에서 생성하며, 결과는 `final_filter_params.py`에 기록됩니다. + +```bash +conda activate ncue +python tools/compute_final_filter_params.py +python final_filterTest.py +``` + +conda 경로를 쓰기 어려우면 프로젝트의 `scripts/run_with_ncue.sh`로 동일하게 실행할 수 있습니다. + +```bash +./scripts/run_with_ncue.sh tools/compute_final_filter_params.py +./scripts/run_with_ncue.sh final_filterTest.py +``` + * FilterFeature.py를 실행한다. * lotto_history.json을 읽어서 all_filter_[1-100].[cluster,csv,feature] 파일을 생성한다. diff --git a/final_BallFilter.py b/final_BallFilter.py new file mode 100644 index 0000000..5fedf16 --- /dev/null +++ b/final_BallFilter.py @@ -0,0 +1,307 @@ +# -*- coding: utf-8 -*- +""" +학습 구간(1~800회)에서 산출한 final_filter_params 를 사용하는 BallFilter. +BallFilter_25 의 filterOneDigitPattern 버그(인자 덮어쓰기)를 수정했습니다. +""" +from __future__ import annotations + +import final_filter_params as P +from BallFilter_25 import BallFilter as BallFilter25 + +_MAX_CONT = max(P.ALLOW_CONTINUS_MAX) +_TRIPLE_FS = tuple(frozenset(t) for t in P.TRIPLE_BLOCKLIST) + + +class BallFilter(BallFilter25): + """학습 데이터 기반 허용 집합을 쓰는 최종 필터.""" + + def filterOneDigitPattern(self, ball): + digit = set() + for b in ball: + digit.add(b % 10) + return len(digit) + + def filterTriplePairBall(self, ball): + s = set(ball) + for t in _TRIPLE_FS: + if t <= s: + return 1 + return None + + def extract_final_candidates(self, ball, no=None, until_end=False, df=None): + p_ball = df[df["no"] == no - 1].values.tolist()[0] + p_ball = p_ball[1:7] + + filter_set = set() + + if no is not None: + if self.hasWon(ball, no): + filter_set.add("이전 당첨 번호") + if not until_end: + return filter_set + + acc = sum(ball) + if acc not in P._F_SUM6: + filter_set.add("6개 합: {}".format(acc)) + if not until_end: + return filter_set + p_acc = sum(p_ball) + + if abs(acc - p_acc) not in P._F_SUM6_DIFF: + filter_set.add("6개 합 전주차: {}".format(abs(acc - p_acc))) + if not until_end: + return filter_set + + avg = acc // 6 + if avg not in P._F_AVG6: + filter_set.add("6개 평균: {}".format(avg)) + if not until_end: + return filter_set + p_avg = sum(p_ball) // 6 + if abs(avg - p_avg) not in P._F_AVG6_DIFF: + filter_set.add("6개 평균 전주차: {}".format(abs(avg - p_avg))) + if not until_end: + return filter_set + + s3f = ball[0] + ball[1] + ball[2] + if s3f not in P._F_SUM3F: + filter_set.add("b1+b2+b3: {}".format(s3f)) + if not until_end: + return filter_set + ps3f = p_ball[0] + p_ball[1] + p_ball[2] + if abs(s3f - ps3f) not in P._F_SUM3F_DIFF: + filter_set.add("b1+b2+b3 전주차: {}".format(abs(s3f - ps3f))) + if not until_end: + return filter_set + + s3b = ball[3] + ball[4] + ball[5] + if s3b not in P._F_SUM3B: + filter_set.add("b4+b5+b6: {}".format(s3b)) + if not until_end: + return filter_set + ps3b = p_ball[3] + p_ball[4] + p_ball[5] + if abs(s3b - ps3b) not in P._F_SUM3B_DIFF: + filter_set.add("b4+b5+b6 전주차: {}".format(abs(s3b - ps3b))) + if not until_end: + return filter_set + + l, h = self.getHigLowRate(ball) + if (l in (0, 1) or h in (0, 1)) and (l, h) not in P._F_HL_SEEN: + filter_set.add("high/low: {}".format((l, h))) + if not until_end: + return filter_set + + gh = ball[0] + ball[5] + if gh not in P._F_GO_SUM: + filter_set.add("고저합: {}".format(gh)) + if not until_end: + return filter_set + pgh = p_ball[0] + p_ball[5] + if abs(gh - pgh) not in P._F_GO_SUM_DIFF: + filter_set.add("고저합 전주차: {}".format(abs(gh - pgh))) + if not until_end: + return filter_set + + interval_sum = self.get_ball_interval(ball) + if interval_sum not in P._F_INTERVAL: + filter_set.add("Interval_sum: {}".format(interval_sum)) + if not until_end: + return filter_set + p_interval_sum = self.get_ball_interval(p_ball) + if abs(interval_sum - p_interval_sum) not in P._F_INTERVAL_DIFF: + filter_set.add("Interval_sum 전주차: {}".format(abs(interval_sum - p_interval_sum))) + if not until_end: + return filter_set + + firstLetterSum = self.getFirstLetterSumBall(ball) + if firstLetterSum not in P._F_FIRST_LETTER: + filter_set.add("첫수합: {}".format(firstLetterSum)) + if not until_end: + return filter_set + p_firstLetterSum = self.getFirstLetterSumBall(p_ball) + if abs(firstLetterSum - p_firstLetterSum) not in P._F_FIRST_LETTER_DIFF: + filter_set.add("첫수합 전주차: {}".format(abs(firstLetterSum - p_firstLetterSum))) + if not until_end: + return filter_set + + lastLetterSum = self.getLastLetterSumBall(ball) + if lastLetterSum not in P._F_LAST_LETTER: + filter_set.add("끝수합: {}".format(lastLetterSum)) + if not until_end: + return filter_set + p_lastLetterSum = self.getLastLetterSumBall(p_ball) + if abs(lastLetterSum - p_lastLetterSum) not in P._F_LAST_LETTER_DIFF: + filter_set.add("끝수합 전주차: {}".format(abs(lastLetterSum - p_lastLetterSum))) + if not until_end: + return filter_set + + if ball[0] not in P._F_B0: + filter_set.add("첫수: {}".format(ball[0])) + if not until_end: + return filter_set + if abs(ball[0] - p_ball[0]) not in P._F_B0_DIFF: + filter_set.add("전주와 첫수 차: {}".format(abs(ball[0] - p_ball[0]))) + if not until_end: + return filter_set + + if ball[5] not in P._F_B5: + filter_set.add("마지막 공: {}".format(ball[5])) + if not until_end: + return filter_set + if abs(ball[5] - p_ball[5]) not in P._F_B5_DIFF: + filter_set.add("마지막 공 전주차: {}".format(abs(ball[5] - p_ball[5]))) + if not until_end: + return filter_set + + uniq_last_count = self.filterOneDigitPattern(ball) + if uniq_last_count not in P._F_UNIQ_END: + filter_set.add("Unique 끝수 개수: {}".format(uniq_last_count)) + if not until_end: + return filter_set + p_uniq = self.filterOneDigitPattern(p_ball) + if abs(uniq_last_count - p_uniq) not in P._F_UNIQ_END_DIFF: + filter_set.add("Unique 끝수 전주차: {}".format(abs(uniq_last_count - p_uniq))) + if not until_end: + return filter_set + + ac_value = self.getACValue(ball) + if ac_value not in P._F_AC: + filter_set.add("ac: {}".format(ac_value)) + if not until_end: + return filter_set + p_ac_value = self.getACValue(p_ball) + if abs(ac_value - p_ac_value) not in P._F_AC_DIFF: + filter_set.add("ac 전주: {}".format(abs(ac_value - p_ac_value))) + if not until_end: + return filter_set + + def _mulchk(n_mul, allow, allow_diff): + bn = len([b for b in ball if b % n_mul == 0]) + if bn not in allow: + filter_set.add("{}의배수: {}".format(n_mul, bn)) + if not until_end: + return True + pbn = len([b for b in p_ball if b % n_mul == 0]) + if abs(bn - pbn) not in allow_diff: + filter_set.add("{}의배수 전주차: {}".format(n_mul, abs(bn - pbn))) + if not until_end: + return True + return False + + _pairs = ( + (3, P._F_MUL3, P._F_MUL3_DIFF), + (4, P._F_MUL4, P._F_MUL4_DIFF), + (5, P._F_MUL5, P._F_MUL5_DIFF), + (6, P._F_MUL6, P._F_MUL6_DIFF), + (7, P._F_MUL7, P._F_MUL7_DIFF), + (8, P._F_MUL8, P._F_MUL8_DIFF), + (9, P._F_MUL9, P._F_MUL9_DIFF), + (10, P._F_MUL10, P._F_MUL10_DIFF), + (11, P._F_MUL11, P._F_MUL11_DIFF), + (13, P._F_MUL13, P._F_MUL13_DIFF), + (17, P._F_MUL17, P._F_MUL17_DIFF), + (19, P._F_MUL19, P._F_MUL19_DIFF), + (23, P._F_MUL23, P._F_MUL23_DIFF), + ) + for n_mul, fa, fd in _pairs: + if _mulchk(n_mul, fa, fd): + return filter_set + + pn_acc = len(set(ball) & set(self.primeNumber)) + if pn_acc not in P._F_PRIME_N: + filter_set.add("소수: {}".format(pn_acc)) + if not until_end: + return filter_set + + cn_acc = len(set(ball) & set(self.compositeNumber)) + if cn_acc not in P._F_COMPOSITE_N: + filter_set.add("복소수: {}".format(cn_acc)) + if not until_end: + return filter_set + diff = abs(cn_acc - len(set(p_ball) & set(self.compositeNumber))) + if diff not in P._F_COMPOSITE_DIFF: + filter_set.add("복소수 전주차: {}".format(diff)) + if not until_end: + return filter_set + + even_count = len([b for b in ball if b % 2 == 0]) + if even_count not in P._F_EVEN_N: + filter_set.add("짝수: {}".format(even_count)) + if not until_end: + return filter_set + p_even_count = len([b for b in p_ball if b % 2 == 0]) + if abs(even_count - p_even_count) not in P._F_EVEN_DIFF: + filter_set.add("짝수 전주차: {}".format(abs(even_count - p_even_count))) + if not until_end: + return filter_set + + for fn in ( + self.filterPatternInPaper1, + self.filterPatternInPaper2, + self.filterPatternInPaper3, + self.filterPatternInPaper4, + self.filterPatternInPaper5, + self.filterPatternInPaper6, + ): + v = fn(ball) + if v is not None: + filter_set.add(v) + if not until_end: + return filter_set + + if not P.DISABLE_FILTER_PREVIOUS_NUMBER: + if self.filterPreviousNumber(ball, no): + filter_set.add("이전회차 수/좌우수") + if not until_end: + return filter_set + + count_section10 = self.getNumberOfAppearancesInSection10(ball) + if count_section10 not in P._F_SEC10: + filter_set.add("같은 10구간대만 출현: {}".format(count_section10)) + if not until_end: + return filter_set + p_count_section10 = self.getNumberOfAppearancesInSection10(p_ball) + if abs(count_section10 - p_count_section10) not in P._F_SEC10_DIFF: + filter_set.add("같은 10구간대만 출현 전주차: {}".format(abs(count_section10 - p_count_section10))) + if not until_end: + return filter_set + + for wk, fw, fwd in ( + (8, P._F_W8, P._F_W8_DIFF), + (12, P._F_W12, P._F_W12_DIFF), + (16, P._F_W16, P._F_W16_DIFF), + (20, P._F_W20, P._F_W20_DIFF), + ): + exist_ball = self.getWeeksFrequency(ball, df, no, week=wk) + if exist_ball not in fw: + filter_set.add("{} weeks: {}".format(wk, exist_ball)) + if not until_end: + return filter_set + p_exist_ball = self.getWeeksFrequency(p_ball, df, no, week=wk) + if abs(exist_ball - p_exist_ball) not in fwd: + filter_set.add("{} weeks 전주차: {}".format(wk, abs(exist_ball - p_exist_ball))) + if not until_end: + return filter_set + + type3 = self.filterTriplePairBall(ball) + if type3 is not None: + filter_set.add("직관 3개 볼을 제거: {}".format(type3)) + if not until_end: + return filter_set + + if not P.DISABLE_FILTER_ALL_PREVIOUS_7: + if self.filterAllPreivous7(ball, no): + filter_set.add("이전 7회차 전부 포함") + if not until_end: + return filter_set + + continous_ball = self.getContinusNumber(ball) + if continous_ball > _MAX_CONT: + filter_set.add("연속볼") + if not until_end: + return filter_set + + return filter_set + + def filter(self, ball, no, until_end=False, df=None, filter_ball=None): + return self.extract_final_candidates(ball=ball, no=no, until_end=until_end, df=df) diff --git a/final_filterTest.py b/final_filterTest.py new file mode 100644 index 0000000..70742c1 --- /dev/null +++ b/final_filterTest.py @@ -0,0 +1,112 @@ +# -*- coding: utf-8 -*- +""" +학습(1~800) / 검증(801~1000) / 테스트(1001~) 구간별 필터 통과(당첨번호가 필터를 통과하는지) 분석. +1_FilterTest_25.py 와 동일한 흐름이며 BallFilter 대신 final_BallFilter.BallFilter 를 사용합니다. + +실행: miniconda 환경 ncue 에서 `python final_filterTest.py` (README 참고). +""" +from __future__ import annotations + +import datetime +import os +import time + +import pandas as pd + +from final_BallFilter import BallFilter + +# PROMPT.txt 기준 구간 +TRAIN_NO = (1, 800) +VALID_NO = (801, 1000) +TEST_NO = (1001, 10**9) + + +class FilterTest: + def __init__(self, resources_path: str): + lotto_json = os.path.join(resources_path, "lotto_history.json") + self.ballFilter = BallFilter(lotto_json) + + def find_filter_method(self, df_ball, filter_ball=None, no_min=None, no_max=None): + """no_min~no_max 회차만 역순으로 검사 (None 이면 전체).""" + win_count = 0 + no_filter_ball = {} + filter_dic = {} + filter_dic_len = {} + filter_dic_1 = {} + filter_dic_2 = {} + + idx_list = list(range(len(df_ball) - 1, 19, -1)) + for i in idx_list: + no = int(df_ball["no"].iloc[i]) + if no_min is not None and no < no_min: + continue + if no_max is not None and no > no_max: + 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 + elif size == 1: + key = filter_type[0] + filter_dic_1[key] = filter_dic_1.get(key, 0) + 1 + elif size == 2: + key = ",".join(filter_type) + filter_dic_2[key] = filter_dic_2.get(key, 0) + 1 + else: + if size not in filter_dic_len: + filter_dic_len[size] = [] + filter_dic_len[size].append(filter_type) + + for f_t in filter_type: + filter_dic[f_t] = filter_dic.get(f_t, 0) + 1 + + print("\n\t[구간 {}~{}] 필터에 걸리지 않은 회차 (당첨 조합 통과)]".format(no_min, no_max)) + print("\tcount: {:,} (통과)".format(len(no_filter_ball))) + for no in sorted(no_filter_ball.keys()): + print("\t\t>", no, no_filter_ball[no]) + + return win_count, no_filter_ball + + def report_split(self, df_ball, name: str, lo: int, hi: int): + print("\n" + "=" * 60) + print(" {} | 회차 {} ~ {}".format(name, lo, hi)) + print("=" * 60) + t0 = time.time() + wc, _ = self.find_filter_method(df_ball, no_min=lo, no_max=hi) + elapsed = datetime.timedelta(seconds=time.time() - t0) + span = hi - lo + 1 + rate = (wc / span * 100) if span else 0 + print("\t처리 시간: {}".format(elapsed)) + print("\t통과 회차 수: {} / {} ({:.2f}%)".format(wc, span, rate)) + if lo >= TRAIN_NO[0] and hi <= TRAIN_NO[1]: + need = max(1, span // 100) + print("\t(참고) 100회당 최소 1회 기준 대략 {}회 이상이면 충족".format(need)) + if lo >= VALID_NO[0] and hi <= VALID_NO[1]: + print("\t(참고) 검증 200회 구간에서 최소 3회 이상이면 요구사항 예시 충족") + return wc + + +if __name__ == "__main__": + resources_path = os.path.join(os.path.dirname(__file__), "resources") + csv_path = os.path.join(resources_path, "lotto_history.txt") + df_ball = pd.read_csv(csv_path, header=None) + df_ball.columns = ["no", "b1", "b2", "b3", "b4", "b5", "b6", "bn"] + + ft = FilterTest(resources_path) + + ft.report_split(df_ball, "학습 TRAIN", TRAIN_NO[0], TRAIN_NO[1]) + ft.report_split(df_ball, "검증 VALID", VALID_NO[0], min(VALID_NO[1], int(df_ball["no"].max()))) + if int(df_ball["no"].max()) >= TEST_NO[0]: + ft.report_split( + df_ball, + "테스트 TEST", + TEST_NO[0], + int(df_ball["no"].max()), + ) diff --git a/final_filter_params.py b/final_filter_params.py new file mode 100644 index 0000000..9b9fa81 --- /dev/null +++ b/final_filter_params.py @@ -0,0 +1,148 @@ +# -*- coding: utf-8 -*- +"""학습 구간 1~800회 기준 자동 생성 — tools/compute_final_filter_params.py""" + +TRAIN_RANGE = (1, 800) +DISABLE_FILTER_PREVIOUS_NUMBER = True +DISABLE_FILTER_ALL_PREVIOUS_7 = True + +ALLOW_SUM6 = [71, 72, 73, 75, 76, 77, 78, 79, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 93, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 192, 193, 194, 195, 196, 197] +ALLOW_SUM6_DIFF = [8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 81, 82, 83, 84, 85, 86, 87, 88, 89, 92, 93, 94, 95] +ALLOW_AVG6 = [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34] +ALLOW_AVG6_DIFF = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18] +ALLOW_SUM3F = [12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81] +ALLOW_SUM3F_DIFF = [5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 58, 59] +ALLOW_SUM3B = [51, 52, 53, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 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, 118, 119, 120, 121, 122, 123, 124, 125] +ALLOW_SUM3B_DIFF = [5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 49, 50, 51, 52, 53, 54, 56, 57, 58, 59, 64] +HL_SKIP = [(0, 0), (0, 1), (1, 0), (1, 1)] +HL_SEEN = [(0, 5), (0, 6), (1, 4), (1, 5), (2, 3), (2, 4), (3, 2), (3, 3), (4, 1), (4, 2), (5, 0), (5, 1), (6, 0)] +ALLOW_GO_SUM = [24, 25, 26, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67] +ALLOW_GO_SUM_DIFF = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 27, 28, 29, 30, 31, 32] +ALLOW_INTERVAL = [15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41] +ALLOW_INTERVAL_DIFF = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25] +ALLOW_FIRST_LETTER = [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] +ALLOW_FIRST_LETTER_DIFF = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] +ALLOW_LAST_LETTER = [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40] +ALLOW_LAST_LETTER_DIFF = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25] +ALLOW_B0 = [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25] +ALLOW_B0_DIFF = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22] +ALLOW_B5 = [21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42] +ALLOW_B5_DIFF = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23] +ALLOW_UNIQ_END = [3, 4, 5, 6] +ALLOW_UNIQ_END_DIFF = [0, 1, 2, 3] +ALLOW_AC = [2, 3, 4, 5, 6, 7, 8, 9] +ALLOW_AC_DIFF = [0, 1, 2, 3, 4, 5, 6, 7] +ALLOW_MUL3 = [0, 1, 2, 3, 4, 5] +ALLOW_MUL3_DIFF = [0, 1, 2, 3, 4, 5] +ALLOW_MUL4 = [0, 1, 2, 3, 4, 5] +ALLOW_MUL4_DIFF = [0, 1, 2, 3, 4] +ALLOW_MUL5 = [0, 1, 2, 3, 4] +ALLOW_MUL5_DIFF = [0, 1, 2, 3, 4] +ALLOW_MUL6 = [0, 1, 2, 3] +ALLOW_MUL6_DIFF = [0, 1, 2, 3] +ALLOW_MUL7 = [0, 1, 2, 3] +ALLOW_MUL7_DIFF = [0, 1, 2, 3] +ALLOW_MUL8 = [0, 1, 2, 3] +ALLOW_MUL8_DIFF = [0, 1, 2, 3] +ALLOW_MUL9 = [0, 1, 2, 3, 4] +ALLOW_MUL9_DIFF = [0, 1, 2, 3] +ALLOW_MUL10 = [0, 1, 2, 3] +ALLOW_MUL10_DIFF = [0, 1, 2, 3] +ALLOW_MUL11 = [0, 1, 2, 3] +ALLOW_MUL11_DIFF = [0, 1, 2, 3] +ALLOW_MUL13 = [0, 1, 2, 3] +ALLOW_MUL13_DIFF = [0, 1, 2, 3] +ALLOW_MUL17 = [0, 1, 2] +ALLOW_MUL17_DIFF = [0, 1, 2] +ALLOW_MUL19 = [0, 1, 2] +ALLOW_MUL19_DIFF = [0, 1, 2] +ALLOW_MUL23 = [0, 1] +ALLOW_MUL23_DIFF = [0, 1] +ALLOW_PRIME_N = [0, 1, 2, 3, 4, 5] +ALLOW_COMPOSITE_N = [1, 2, 3, 4, 5, 6] +ALLOW_COMPOSITE_DIFF = [0, 1, 2, 3, 4] +ALLOW_EVEN_N = [0, 1, 2, 3, 4, 5] +ALLOW_EVEN_DIFF = [0, 1, 2, 3, 4, 5] +ALLOW_SEC10 = [2, 3, 4, 5] +ALLOW_SEC10_DIFF = [0, 1, 2, 3] +ALLOW_W8 = [0, 1, 2, 3, 4, 5] +ALLOW_W8_DIFF = [0, 1, 2, 3, 4, 5] +ALLOW_W12 = [0, 1, 2, 3, 4, 5] +ALLOW_W12_DIFF = [0, 1, 2, 3, 4, 5] +ALLOW_W16 = [0, 1, 3, 4, 5, 6] +ALLOW_W16_DIFF = [0, 1, 2, 3, 5, 6] +ALLOW_W20 = [0, 1, 3, 4, 5, 6] +ALLOW_W20_DIFF = [0, 1, 2, 3, 5, 6] +ALLOW_CONTINUS_MAX = [1, 2, 3, 4] +PAIR_BLOCKLIST = [] +TRIPLE_BLOCKLIST = [[1, 2, 32], [1, 2, 41], [1, 2, 43], [1, 3, 5], [1, 3, 13], [1, 3, 19], [1, 3, 38], [1, 4, 7], [1, 4, 11], [1, 4, 21], [1, 4, 22], [1, 4, 24], [1, 4, 25], [1, 4, 27], [1, 4, 30], [1, 4, 32], [1, 4, 36], [1, 4, 44], [1, 5, 7], [1, 5, 8], [1, 5, 15], [1, 5, 17], [1, 5, 22], [1, 5, 33], [1, 5, 43], [1, 5, 45], [1, 6, 7], [1, 6, 8], [1, 6, 10], [1, 6, 18], [1, 6, 21], [1, 6, 23], [1, 6, 26], [1, 6, 29], [1, 6, 30], [1, 6, 32], [1, 6, 35], [1, 6, 43], [1, 6, 44], [1, 7, 13], [1, 7, 17], [1, 7, 25], [1, 7, 28], [1, 7, 29], [1, 7, 31], [1, 7, 39], [1, 7, 43], [1, 7, 44], [1, 7, 45], [1, 8, 16], [1, 8, 20], [1, 8, 25], [1, 8, 30], [1, 8, 40], [1, 8, 41], [1, 9, 15], [1, 9, 22], [1, 9, 34], [1, 9, 37], [1, 9, 44], [1, 10, 11], [1, 10, 30], [1, 10, 34], [1, 10, 39], [1, 11, 19], [1, 11, 20], [1, 11, 29], [1, 11, 31], [1, 11, 33], [1, 11, 41], [1, 11, 43], [1, 12, 25], [1, 12, 30], [1, 12, 31], [1, 13, 15], [1, 13, 23], [1, 13, 27], [1, 13, 30], [1, 13, 31], [1, 13, 41], [1, 14, 17], [1, 14, 19], [1, 14, 22], [1, 14, 23], [1, 14, 25], [1, 14, 30], [1, 14, 31], [1, 14, 36], [1, 14, 38], [1, 14, 45], [1, 15, 21], [1, 15, 27], [1, 15, 29], [1, 15, 30], [1, 15, 31], [1, 15, 33], [1, 15, 43], [1, 16, 27], [1, 16, 31], [1, 17, 18], [1, 17, 38], [1, 18, 21], [1, 18, 25], [1, 18, 35], [1, 18, 39], [1, 19, 21], [1, 19, 22], [1, 19, 25], [1, 19, 29], [1, 19, 31], [1, 19, 32], [1, 19, 33], [1, 19, 37], [1, 19, 45], [1, 20, 21], [1, 20, 38], [1, 21, 28], [1, 21, 43], [1, 22, 23], [1, 22, 24], [1, 22, 26], [1, 22, 27], [1, 22, 29], [1, 22, 30], [1, 22, 34], [1, 22, 35], [1, 22, 36], [1, 22, 41], [1, 22, 43], [1, 22, 44], [1, 23, 32], [1, 23, 36], [1, 24, 37], [1, 25, 27], [1, 25, 30], [1, 25, 31], [1, 25, 32], [1, 25, 33], [1, 25, 42], [1, 25, 43], [1, 26, 39], [1, 26, 45], [1, 27, 31], [1, 27, 38], [1, 27, 44], [1, 28, 33], [1, 28, 38], [1, 28, 39], [1, 29, 30], [1, 29, 41], [1, 30, 31], [1, 30, 37], [1, 30, 40], [1, 30, 44], [1, 30, 45], [1, 31, 32], [1, 31, 33], [1, 31, 35], [1, 31, 36], [1, 31, 37], [1, 31, 38], [1, 31, 39], [1, 31, 41], [1, 31, 45], [1, 32, 38], [1, 32, 39], [1, 32, 43], [1, 32, 44], [1, 33, 38], [1, 33, 41], [1, 33, 44], [1, 35, 36], [1, 36, 43], [1, 37, 44], [1, 38, 42], [1, 38, 43], [1, 38, 44], [1, 39, 41], [1, 39, 42], [1, 41, 45], [2, 3, 8], [2, 3, 10], [2, 3, 18], [2, 3, 21], [2, 3, 28], [2, 3, 29], [2, 3, 31], [2, 3, 32], [2, 3, 35], [2, 3, 41], [2, 3, 45], [2, 4, 7], [2, 4, 9], [2, 4, 10], [2, 4, 12], [2, 4, 13], [2, 4, 14], [2, 4, 18], [2, 4, 22], [2, 4, 40], [2, 4, 41], [2, 4, 42], [2, 4, 45], [2, 5, 9], [2, 5, 21], [2, 5, 25], [2, 5, 26], [2, 5, 30], [2, 5, 35], [2, 5, 37], [2, 5, 38], [2, 5, 41], [2, 5, 42], [2, 5, 43], [2, 6, 10], [2, 6, 15], [2, 6, 23], [2, 6, 24], [2, 6, 32], [2, 6, 35], [2, 6, 38], [2, 6, 41], [2, 7, 11], [2, 7, 23], [2, 7, 31], [2, 7, 32], [2, 7, 35], [2, 7, 37], [2, 8, 10], [2, 8, 12], [2, 8, 16], [2, 8, 40], [2, 9, 11], [2, 9, 13], [2, 9, 18], [2, 9, 20], [2, 9, 21], [2, 9, 27], [2, 9, 29], [2, 9, 30], [2, 9, 32], [2, 9, 36], [2, 9, 39], [2, 10, 17], [2, 10, 20], [2, 10, 21], [2, 10, 23], [2, 10, 24], [2, 10, 27], [2, 10, 28], [2, 10, 30], [2, 10, 41], [2, 10, 43], [2, 11, 20], [2, 11, 38], [2, 11, 40], [2, 12, 13], [2, 12, 16], [2, 12, 18], [2, 12, 25], [2, 12, 29], [2, 12, 32], [2, 12, 35], [2, 12, 36], [2, 13, 21], [2, 13, 23], [2, 13, 24], [2, 13, 26], [2, 13, 35], [2, 13, 39], [2, 14, 18], [2, 14, 19], [2, 14, 20], [2, 14, 26], [2, 14, 34], [2, 14, 35], [2, 14, 37], [2, 14, 43], [2, 15, 17], [2, 15, 26], [2, 15, 32], [2, 15, 35], [2, 15, 39], [2, 16, 18], [2, 16, 23], [2, 16, 43], [2, 17, 23], [2, 17, 25], [2, 17, 35], [2, 17, 44], [2, 18, 22], [2, 18, 35], [2, 18, 41], [2, 19, 21], [2, 19, 30], [2, 19, 40], [2, 20, 22], [2, 20, 23], [2, 20, 26], [2, 20, 28], [2, 20, 32], [2, 20, 36], [2, 21, 23], [2, 21, 24], [2, 21, 31], [2, 21, 32], [2, 21, 37], [2, 21, 40], [2, 22, 26], [2, 22, 35], [2, 22, 43], [2, 23, 24], [2, 23, 28], [2, 23, 30], [2, 23, 32], [2, 23, 33], [2, 23, 35], [2, 23, 36], [2, 23, 39], [2, 23, 42], [2, 23, 45], [2, 24, 25], [2, 24, 26], [2, 24, 38], [2, 25, 35], [2, 26, 28], [2, 26, 31], [2, 26, 32], [2, 26, 35], [2, 26, 38], [2, 26, 39], [2, 26, 41], [2, 26, 42], [2, 27, 29], [2, 27, 31], [2, 27, 34], [2, 27, 45], [2, 28, 40], [2, 28, 41], [2, 29, 33], [2, 29, 35], [2, 29, 37], [2, 29, 41], [2, 29, 42], [2, 30, 37], [2, 30, 44], [2, 31, 36], [2, 31, 44], [2, 32, 37], [2, 32, 38], [2, 32, 40], [2, 32, 41], [2, 33, 38], [2, 34, 36], [2, 34, 39], [2, 35, 38], [2, 35, 44], [2, 35, 45], [2, 36, 38], [2, 36, 40], [2, 36, 43], [2, 36, 44], [2, 36, 45], [2, 37, 38], [2, 37, 42], [2, 37, 44], [2, 38, 41], [2, 38, 43], [2, 38, 44], [2, 39, 40], [2, 40, 45], [2, 42, 43], [2, 44, 45], [3, 4, 8], [3, 4, 13], [3, 4, 18], [3, 4, 21], [3, 4, 26], [3, 4, 35], [3, 4, 39], [3, 5, 9], [3, 5, 15], [3, 5, 16], [3, 5, 18], [3, 5, 23], [3, 5, 25], [3, 5, 28], [3, 5, 36], [3, 5, 40], [3, 5, 41], [3, 5, 45], [3, 6, 8], [3, 6, 11], [3, 6, 15], [3, 6, 16], [3, 6, 25], [3, 6, 26], [3, 6, 29], [3, 6, 31], [3, 6, 32], [3, 6, 33], [3, 6, 40], [3, 6, 42], [3, 6, 43], [3, 6, 45], [3, 7, 19], [3, 7, 28], [3, 7, 30], [3, 7, 35], [3, 7, 45], [3, 8, 10], [3, 8, 14], [3, 8, 25], [3, 8, 26], [3, 8, 28], [3, 8, 33], [3, 8, 37], [3, 9, 15], [3, 9, 16], [3, 9, 20], [3, 9, 21], [3, 9, 26], [3, 9, 31], [3, 9, 38], [3, 9, 39], [3, 9, 41], [3, 9, 44], [3, 10, 12], [3, 10, 18], [3, 10, 21], [3, 10, 41], [3, 11, 23], [3, 11, 25], [3, 11, 28], [3, 11, 29], [3, 11, 40], [3, 12, 17], [3, 12, 28], [3, 12, 29], [3, 12, 37], [3, 12, 44], [3, 14, 19], [3, 14, 29], [3, 14, 39], [3, 15, 16], [3, 15, 17], [3, 15, 18], [3, 15, 19], [3, 15, 21], [3, 15, 23], [3, 15, 26], [3, 15, 31], [3, 15, 39], [3, 15, 42], [3, 16, 25], [3, 16, 33], [3, 16, 41], [3, 16, 42], [3, 16, 45], [3, 17, 25], [3, 17, 26], [3, 17, 29], [3, 17, 33], [3, 17, 40], [3, 17, 42], [3, 17, 43], [3, 18, 21], [3, 18, 24], [3, 18, 25], [3, 18, 38], [3, 18, 39], [3, 18, 44], [3, 19, 26], [3, 19, 29], [3, 19, 33], [3, 19, 34], [3, 19, 40], [3, 19, 44], [3, 20, 29], [3, 20, 30], [3, 21, 24], [3, 21, 27], [3, 21, 28], [3, 21, 32], [3, 21, 34], [3, 21, 36], [3, 21, 40], [3, 21, 43], [3, 22, 34], [3, 23, 25], [3, 23, 30], [3, 23, 33], [3, 24, 28], [3, 24, 40], [3, 25, 26], [3, 25, 27], [3, 25, 28], [3, 25, 30], [3, 25, 31], [3, 25, 34], [3, 25, 35], [3, 25, 38], [3, 25, 39], [3, 25, 40], [3, 25, 41], [3, 25, 42], [3, 26, 28], [3, 26, 30], [3, 26, 32], [3, 26, 36], [3, 26, 39], [3, 26, 40], [3, 26, 45], [3, 27, 33], [3, 27, 34], [3, 27, 36], [3, 28, 29], [3, 28, 31], [3, 28, 33], [3, 28, 35], [3, 28, 36], [3, 28, 37], [3, 28, 41], [3, 29, 30], [3, 29, 31], [3, 29, 33], [3, 29, 34], [3, 30, 35], [3, 30, 42], [3, 30, 44], [3, 31, 33], [3, 31, 36], [3, 31, 45], [3, 32, 38], [3, 32, 39], [3, 33, 40], [3, 33, 44], [3, 34, 40], [3, 35, 38], [3, 35, 39], [3, 35, 41], [3, 35, 42], [3, 36, 43], [3, 36, 44], [3, 37, 40], [3, 38, 41], [3, 39, 40], [3, 44, 45], [4, 5, 10], [4, 5, 19], [4, 5, 28], [4, 5, 30], [4, 5, 33], [4, 5, 34], [4, 5, 36], [4, 5, 40], [4, 5, 44], [4, 6, 7], [4, 6, 16], [4, 6, 18], [4, 6, 22], [4, 6, 23], [4, 6, 24], [4, 6, 27], [4, 6, 29], [4, 6, 34], [4, 6, 35], [4, 6, 36], [4, 6, 38], [4, 6, 45], [4, 7, 8], [4, 7, 9], [4, 7, 21], [4, 7, 27], [4, 7, 28], [4, 7, 30], [4, 7, 34], [4, 7, 36], [4, 7, 37], [4, 7, 43], [4, 8, 12], [4, 8, 14], [4, 8, 22], [4, 8, 26], [4, 8, 28], [4, 8, 35], [4, 9, 12], [4, 9, 15], [4, 9, 20], [4, 9, 35], [4, 9, 41], [4, 9, 43], [4, 10, 39], [4, 10, 43], [4, 11, 15], [4, 11, 16], [4, 11, 19], [4, 11, 25], [4, 11, 30], [4, 11, 33], [4, 11, 34], [4, 11, 36], [4, 11, 40], [4, 11, 44], [4, 12, 13], [4, 12, 15], [4, 12, 17], [4, 12, 19], [4, 12, 21], [4, 12, 26], [4, 12, 29], [4, 12, 30], [4, 12, 31], [4, 12, 36], [4, 12, 39], [4, 12, 40], [4, 12, 44], [4, 13, 15], [4, 13, 16], [4, 13, 24], [4, 13, 25], [4, 13, 30], [4, 13, 35], [4, 14, 17], [4, 14, 27], [4, 14, 30], [4, 14, 34], [4, 14, 36], [4, 14, 38], [4, 14, 39], [4, 15, 19], [4, 15, 30], [4, 15, 32], [4, 15, 44], [4, 15, 45], [4, 16, 32], [4, 16, 45], [4, 17, 21], [4, 17, 23], [4, 17, 24], [4, 17, 25], [4, 17, 29], [4, 17, 35], [4, 17, 41], [4, 17, 45], [4, 18, 28], [4, 18, 35], [4, 18, 36], [4, 19, 23], [4, 19, 28], [4, 19, 36], [4, 19, 37], [4, 20, 42], [4, 21, 27], [4, 21, 28], [4, 21, 30], [4, 21, 31], [4, 21, 35], [4, 22, 23], [4, 22, 25], [4, 22, 26], [4, 22, 29], [4, 22, 30], [4, 22, 31], [4, 22, 32], [4, 22, 35], [4, 22, 36], [4, 22, 39], [4, 22, 45], [4, 23, 24], [4, 23, 27], [4, 23, 36], [4, 24, 29], [4, 24, 30], [4, 24, 31], [4, 24, 39], [4, 24, 43], [4, 25, 28], [4, 25, 30], [4, 25, 38], [4, 25, 39], [4, 25, 44], [4, 26, 39], [4, 26, 45], [4, 27, 29], [4, 27, 31], [4, 27, 33], [4, 27, 36], [4, 28, 41], [4, 29, 30], [4, 29, 44], [4, 30, 38], [4, 30, 39], [4, 30, 44], [4, 30, 45], [4, 31, 32], [4, 31, 38], [4, 32, 35], [4, 32, 45], [4, 34, 36], [4, 34, 42], [4, 34, 45], [4, 35, 38], [4, 35, 39], [4, 35, 41], [4, 35, 44], [4, 36, 38], [4, 36, 42], [4, 36, 44], [4, 37, 44], [4, 38, 42], [4, 40, 44], [4, 41, 44], [4, 42, 44], [4, 44, 45], [5, 6, 7], [5, 6, 10], [5, 6, 23], [5, 6, 30], [5, 6, 33], [5, 6, 34], [5, 6, 35], [5, 6, 36], [5, 6, 40], [5, 7, 10], [5, 7, 17], [5, 7, 19], [5, 7, 23], [5, 7, 24], [5, 7, 27], [5, 7, 31], [5, 7, 36], [5, 7, 38], [5, 8, 9], [5, 8, 10], [5, 8, 12], [5, 8, 13], [5, 8, 20], [5, 8, 24], [5, 8, 25], [5, 8, 31], [5, 8, 32], [5, 8, 34], [5, 8, 36], [5, 8, 37], [5, 8, 40], [5, 8, 41], [5, 8, 45], [5, 9, 10], [5, 9, 18], [5, 9, 24], [5, 9, 28], [5, 9, 33], [5, 9, 42], [5, 9, 44], [5, 10, 11], [5, 10, 14], [5, 10, 15], [5, 10, 23], [5, 10, 25], [5, 10, 26], [5, 10, 28], [5, 10, 33], [5, 10, 38], [5, 10, 40], [5, 10, 42], [5, 11, 25], [5, 11, 28], [5, 11, 40], [5, 12, 15], [5, 12, 36], [5, 12, 40], [5, 13, 15], [5, 13, 30], [5, 13, 38], [5, 15, 17], [5, 15, 24], [5, 15, 28], [5, 15, 29], [5, 15, 32], [5, 15, 33], [5, 15, 38], [5, 15, 40], [5, 15, 41], [5, 15, 44], [5, 16, 19], [5, 16, 25], [5, 16, 33], [5, 16, 36], [5, 16, 39], [5, 16, 43], [5, 16, 44], [5, 17, 19], [5, 17, 37], [5, 17, 45], [5, 18, 24], [5, 18, 26], [5, 18, 27], [5, 18, 29], [5, 18, 39], [5, 18, 44], [5, 19, 29], [5, 19, 32], [5, 19, 33], [5, 19, 35], [5, 19, 37], [5, 19, 40], [5, 20, 29], [5, 20, 32], [5, 20, 38], [5, 21, 28], [5, 21, 31], [5, 21, 32], [5, 22, 24], [5, 22, 27], [5, 22, 30], [5, 22, 40], [5, 23, 29], [5, 23, 31], [5, 23, 32], [5, 23, 37], [5, 23, 39], [5, 23, 41], [5, 23, 42], [5, 23, 44], [5, 24, 26], [5, 24, 28], [5, 24, 31], [5, 24, 36], [5, 24, 38], [5, 24, 41], [5, 24, 43], [5, 24, 45], [5, 25, 33], [5, 25, 35], [5, 25, 42], [5, 26, 28], [5, 26, 32], [5, 26, 33], [5, 26, 36], [5, 26, 37], [5, 26, 40], [5, 28, 35], [5, 28, 38], [5, 28, 40], [5, 29, 38], [5, 30, 32], [5, 30, 37], [5, 30, 40], [5, 31, 33], [5, 31, 37], [5, 31, 38], [5, 32, 36], [5, 32, 38], [5, 33, 34], [5, 33, 35], [5, 33, 36], [5, 33, 37], [5, 33, 41], [5, 33, 43], [5, 33, 45], [5, 35, 36], [5, 35, 37], [5, 35, 39], [5, 35, 41], [5, 36, 37], [5, 36, 38], [5, 36, 39], [5, 36, 40], [5, 36, 41], [5, 36, 45], [5, 37, 43], [5, 37, 44], [5, 38, 40], [5, 38, 43], [5, 39, 41], [5, 39, 42], [5, 39, 44], [5, 40, 42], [5, 40, 43], [5, 40, 44], [5, 41, 42], [5, 41, 44], [5, 43, 44], [6, 7, 8], [6, 7, 23], [6, 7, 27], [6, 7, 29], [6, 8, 9], [6, 8, 10], [6, 8, 12], [6, 8, 15], [6, 8, 19], [6, 8, 20], [6, 8, 24], [6, 8, 25], [6, 8, 27], [6, 8, 29], [6, 8, 32], [6, 8, 34], [6, 8, 41], [6, 8, 44], [6, 9, 12], [6, 9, 13], [6, 9, 14], [6, 9, 20], [6, 9, 26], [6, 9, 27], [6, 9, 29], [6, 9, 34], [6, 9, 36], [6, 9, 38], [6, 9, 42], [6, 9, 43], [6, 9, 44], [6, 9, 45], [6, 10, 13], [6, 10, 23], [6, 10, 24], [6, 10, 27], [6, 10, 33], [6, 10, 45], [6, 11, 12], [6, 11, 35], [6, 12, 16], [6, 13, 18], [6, 13, 19], [6, 13, 26], [6, 13, 33], [6, 13, 34], [6, 13, 45], [6, 14, 29], [6, 14, 32], [6, 14, 33], [6, 14, 45], [6, 15, 27], [6, 15, 29], [6, 15, 45], [6, 16, 22], [6, 16, 26], [6, 16, 35], [6, 16, 36], [6, 16, 44], [6, 17, 24], [6, 17, 25], [6, 17, 36], [6, 17, 41], [6, 17, 42], [6, 18, 20], [6, 18, 23], [6, 18, 27], [6, 18, 41], [6, 18, 44], [6, 19, 22], [6, 19, 27], [6, 19, 29], [6, 19, 37], [6, 20, 22], [6, 20, 25], [6, 20, 34], [6, 20, 35], [6, 20, 43], [6, 20, 45], [6, 21, 24], [6, 21, 25], [6, 21, 28], [6, 21, 44], [6, 22, 27], [6, 22, 33], [6, 22, 42], [6, 23, 26], [6, 23, 27], [6, 23, 29], [6, 23, 33], [6, 23, 41], [6, 23, 43], [6, 23, 44], [6, 23, 45], [6, 24, 26], [6, 24, 29], [6, 24, 31], [6, 24, 33], [6, 24, 43], [6, 24, 45], [6, 25, 27], [6, 25, 29], [6, 25, 30], [6, 25, 36], [6, 25, 39], [6, 25, 41], [6, 25, 42], [6, 25, 45], [6, 26, 31], [6, 26, 32], [6, 26, 35], [6, 26, 42], [6, 27, 29], [6, 27, 30], [6, 27, 33], [6, 27, 34], [6, 27, 36], [6, 27, 45], [6, 28, 29], [6, 28, 31], [6, 28, 37], [6, 28, 43], [6, 29, 31], [6, 29, 32], [6, 29, 33], [6, 29, 34], [6, 29, 35], [6, 29, 40], [6, 29, 44], [6, 30, 33], [6, 30, 36], [6, 30, 42], [6, 30, 44], [6, 30, 45], [6, 31, 42], [6, 32, 33], [6, 32, 41], [6, 32, 42], [6, 32, 43], [6, 32, 45], [6, 33, 35], [6, 33, 36], [6, 33, 37], [6, 33, 41], [6, 33, 42], [6, 33, 43], [6, 33, 45], [6, 34, 36], [6, 34, 43], [6, 36, 40], [6, 37, 42], [6, 37, 44], [6, 38, 42], [6, 39, 42], [6, 40, 44], [6, 41, 42], [6, 41, 44], [6, 44, 45], [7, 8, 12], [7, 8, 25], [7, 8, 26], [7, 8, 28], [7, 8, 35], [7, 8, 40], [7, 9, 16], [7, 9, 21], [7, 9, 30], [7, 9, 40], [7, 9, 41], [7, 9, 44], [7, 9, 45], [7, 10, 11], [7, 10, 14], [7, 10, 18], [7, 10, 20], [7, 10, 24], [7, 10, 27], [7, 10, 30], [7, 10, 32], [7, 10, 37], [7, 10, 39], [7, 10, 43], [7, 10, 45], [7, 11, 14], [7, 11, 15], [7, 11, 19], [7, 11, 25], [7, 11, 30], [7, 11, 34], [7, 11, 36], [7, 11, 39], [7, 11, 40], [7, 12, 17], [7, 12, 20], [7, 12, 30], [7, 12, 44], [7, 13, 14], [7, 13, 22], [7, 13, 23], [7, 13, 32], [7, 13, 34], [7, 14, 18], [7, 14, 19], [7, 14, 21], [7, 14, 25], [7, 14, 27], [7, 14, 29], [7, 14, 30], [7, 14, 41], [7, 14, 43], [7, 14, 45], [7, 15, 17], [7, 15, 29], [7, 15, 35], [7, 15, 41], [7, 16, 22], [7, 16, 30], [7, 16, 32], [7, 16, 39], [7, 16, 43], [7, 17, 21], [7, 17, 25], [7, 17, 27], [7, 17, 31], [7, 17, 34], [7, 17, 37], [7, 17, 41], [7, 17, 42], [7, 17, 43], [7, 19, 20], [7, 19, 34], [7, 21, 22], [7, 21, 25], [7, 21, 26], [7, 21, 28], [7, 21, 37], [7, 21, 40], [7, 21, 42], [7, 21, 45], [7, 22, 30], [7, 22, 45], [7, 23, 25], [7, 23, 30], [7, 23, 31], [7, 23, 38], [7, 23, 40], [7, 23, 41], [7, 24, 26], [7, 24, 32], [7, 25, 27], [7, 25, 30], [7, 25, 31], [7, 25, 32], [7, 25, 41], [7, 26, 31], [7, 26, 32], [7, 26, 41], [7, 27, 28], [7, 27, 31], [7, 27, 32], [7, 27, 34], [7, 28, 31], [7, 28, 32], [7, 28, 34], [7, 29, 37], [7, 29, 41], [7, 29, 45], [7, 30, 31], [7, 30, 32], [7, 30, 42], [7, 31, 32], [7, 31, 42], [7, 31, 43], [7, 31, 44], [7, 31, 45], [7, 32, 38], [7, 33, 42], [7, 34, 43], [7, 34, 44], [7, 35, 41], [7, 35, 42], [7, 35, 43], [7, 35, 44], [7, 35, 45], [7, 36, 44], [7, 36, 45], [7, 38, 42], [7, 38, 43], [7, 38, 45], [7, 40, 42], [7, 40, 45], [7, 42, 43], [7, 42, 44], [7, 43, 45], [8, 9, 11], [8, 9, 13], [8, 9, 14], [8, 9, 15], [8, 9, 23], [8, 9, 26], [8, 9, 30], [8, 9, 31], [8, 9, 34], [8, 9, 35], [8, 9, 36], [8, 9, 37], [8, 9, 38], [8, 9, 39], [8, 9, 41], [8, 9, 42], [8, 9, 43], [8, 9, 45], [8, 10, 17], [8, 10, 22], [8, 10, 25], [8, 10, 26], [8, 10, 29], [8, 10, 39], [8, 11, 20], [8, 11, 23], [8, 11, 24], [8, 11, 27], [8, 11, 29], [8, 11, 31], [8, 11, 32], [8, 11, 34], [8, 11, 35], [8, 11, 40], [8, 11, 42], [8, 12, 14], [8, 12, 15], [8, 12, 16], [8, 12, 17], [8, 12, 18], [8, 12, 20], [8, 12, 22], [8, 12, 23], [8, 12, 25], [8, 12, 26], [8, 12, 27], [8, 12, 28], [8, 12, 30], [8, 12, 32], [8, 12, 34], [8, 12, 37], [8, 12, 38], [8, 12, 39], [8, 12, 40], [8, 12, 41], [8, 12, 45], [8, 13, 17], [8, 13, 21], [8, 13, 41], [8, 14, 20], [8, 14, 24], [8, 14, 26], [8, 14, 41], [8, 14, 42], [8, 14, 43], [8, 15, 24], [8, 15, 26], [8, 15, 32], [8, 15, 36], [8, 15, 40], [8, 15, 42], [8, 16, 20], [8, 16, 22], [8, 16, 23], [8, 16, 24], [8, 16, 27], [8, 16, 28], [8, 16, 33], [8, 16, 35], [8, 16, 39], [8, 16, 40], [8, 17, 25], [8, 17, 28], [8, 17, 41], [8, 18, 26], [8, 18, 36], [8, 18, 41], [8, 19, 23], [8, 19, 24], [8, 19, 26], [8, 19, 29], [8, 20, 24], [8, 20, 26], [8, 20, 28], [8, 20, 31], [8, 20, 32], [8, 20, 40], [8, 21, 26], [8, 21, 32], [8, 21, 41], [8, 21, 42], [8, 21, 43], [8, 22, 27], [8, 22, 29], [8, 22, 30], [8, 22, 34], [8, 22, 37], [8, 22, 40], [8, 22, 43], [8, 22, 44], [8, 22, 45], [8, 23, 28], [8, 23, 29], [8, 23, 30], [8, 23, 32], [8, 23, 34], [8, 23, 37], [8, 24, 25], [8, 24, 26], [8, 24, 30], [8, 24, 32], [8, 25, 26], [8, 26, 33], [8, 26, 35], [8, 26, 39], [8, 26, 40], [8, 26, 41], [8, 26, 42], [8, 27, 28], [8, 28, 41], [8, 28, 44], [8, 29, 37], [8, 29, 39], [8, 29, 41], [8, 30, 36], [8, 31, 32], [8, 31, 37], [8, 31, 39], [8, 31, 40], [8, 32, 38], [8, 32, 40], [8, 32, 41], [8, 32, 44], [8, 33, 43], [8, 34, 35], [8, 34, 38], [8, 34, 42], [8, 37, 38], [8, 37, 42], [8, 37, 44], [8, 38, 43], [8, 40, 41], [8, 40, 42], [8, 41, 42], [8, 42, 44], [8, 42, 45], [9, 10, 17], [9, 10, 18], [9, 10, 19], [9, 10, 20], [9, 10, 23], [9, 10, 42], [9, 10, 43], [9, 11, 25], [9, 11, 29], [9, 11, 33], [9, 11, 34], [9, 11, 40], [9, 11, 45], [9, 12, 17], [9, 12, 18], [9, 12, 22], [9, 12, 32], [9, 12, 33], [9, 12, 42], [9, 13, 14], [9, 13, 16], [9, 13, 17], [9, 13, 22], [9, 13, 23], [9, 13, 29], [9, 13, 30], [9, 13, 36], [9, 13, 40], [9, 13, 44], [9, 14, 19], [9, 14, 24], [9, 14, 32], [9, 14, 37], [9, 14, 39], [9, 14, 40], [9, 14, 45], [9, 15, 18], [9, 15, 24], [9, 15, 32], [9, 15, 35], [9, 15, 41], [9, 15, 44], [9, 15, 45], [9, 16, 18], [9, 16, 20], [9, 16, 22], [9, 16, 30], [9, 16, 31], [9, 16, 33], [9, 16, 39], [9, 16, 42], [9, 17, 20], [9, 17, 23], [9, 17, 27], [9, 17, 40], [9, 17, 41], [9, 18, 31], [9, 18, 39], [9, 18, 41], [9, 18, 45], [9, 19, 21], [9, 19, 24], [9, 19, 26], [9, 19, 27], [9, 19, 28], [9, 19, 29], [9, 19, 31], [9, 19, 32], [9, 19, 37], [9, 19, 38], [9, 19, 43], [9, 19, 44], [9, 19, 45], [9, 20, 23], [9, 20, 31], [9, 20, 32], [9, 20, 35], [9, 20, 40], [9, 20, 42], [9, 21, 23], [9, 21, 24], [9, 21, 38], [9, 21, 39], [9, 21, 44], [9, 21, 45], [9, 22, 23], [9, 22, 26], [9, 22, 28], [9, 22, 29], [9, 22, 32], [9, 22, 39], [9, 22, 40], [9, 22, 41], [9, 22, 43], [9, 23, 27], [9, 23, 30], [9, 23, 31], [9, 23, 36], [9, 23, 41], [9, 23, 42], [9, 24, 26], [9, 24, 28], [9, 24, 35], [9, 24, 37], [9, 24, 39], [9, 24, 40], [9, 24, 42], [9, 25, 38], [9, 26, 32], [9, 26, 34], [9, 26, 36], [9, 26, 39], [9, 27, 28], [9, 27, 30], [9, 27, 33], [9, 28, 29], [9, 28, 32], [9, 28, 37], [9, 28, 42], [9, 28, 44], [9, 29, 30], [9, 29, 35], [9, 29, 36], [9, 29, 42], [9, 29, 44], [9, 30, 32], [9, 30, 38], [9, 30, 45], [9, 31, 36], [9, 31, 37], [9, 31, 42], [9, 31, 43], [9, 32, 34], [9, 32, 41], [9, 32, 45], [9, 33, 35], [9, 34, 44], [9, 35, 36], [9, 35, 44], [9, 37, 41], [9, 37, 43], [9, 37, 45], [9, 38, 40], [9, 38, 41], [9, 38, 42], [9, 39, 40], [9, 39, 42], [9, 40, 44], [9, 42, 45], [9, 43, 44], [9, 44, 45], [10, 11, 13], [10, 11, 16], [10, 11, 17], [10, 11, 30], [10, 11, 33], [10, 11, 40], [10, 11, 43], [10, 12, 17], [10, 12, 23], [10, 12, 30], [10, 12, 32], [10, 12, 34], [10, 12, 36], [10, 12, 37], [10, 12, 41], [10, 13, 14], [10, 13, 17], [10, 13, 20], [10, 13, 30], [10, 14, 17], [10, 14, 26], [10, 14, 34], [10, 14, 41], [10, 15, 28], [10, 15, 31], [10, 15, 40], [10, 15, 45], [10, 16, 21], [10, 16, 22], [10, 16, 23], [10, 16, 30], [10, 17, 20], [10, 17, 24], [10, 17, 25], [10, 17, 26], [10, 17, 28], [10, 17, 36], [10, 17, 39], [10, 17, 40], [10, 17, 41], [10, 17, 45], [10, 18, 33], [10, 19, 26], [10, 19, 30], [10, 19, 36], [10, 19, 41], [10, 20, 22], [10, 20, 29], [10, 20, 37], [10, 21, 23], [10, 21, 24], [10, 21, 26], [10, 21, 28], [10, 21, 32], [10, 21, 33], [10, 21, 44], [10, 22, 26], [10, 22, 38], [10, 22, 41], [10, 22, 45], [10, 23, 34], [10, 23, 38], [10, 23, 41], [10, 23, 45], [10, 24, 30], [10, 24, 34], [10, 25, 28], [10, 25, 30], [10, 25, 32], [10, 25, 38], [10, 25, 39], [10, 25, 42], [10, 25, 43], [10, 25, 45], [10, 26, 27], [10, 26, 30], [10, 26, 42], [10, 26, 45], [10, 27, 30], [10, 27, 32], [10, 27, 34], [10, 27, 36], [10, 27, 44], [10, 28, 29], [10, 28, 32], [10, 28, 35], [10, 28, 43], [10, 29, 30], [10, 29, 36], [10, 29, 39], [10, 30, 40], [10, 30, 41], [10, 30, 45], [10, 31, 38], [10, 32, 39], [10, 33, 34], [10, 33, 39], [10, 33, 43], [10, 34, 39], [10, 35, 44], [10, 35, 45], [10, 36, 43], [10, 36, 45], [10, 37, 42], [10, 37, 44], [10, 37, 45], [10, 38, 39], [10, 39, 45], [10, 42, 45], [10, 43, 45], [11, 12, 17], [11, 12, 22], [11, 12, 28], [11, 12, 30], [11, 12, 34], [11, 12, 40], [11, 12, 41], [11, 12, 43], [11, 13, 27], [11, 13, 39], [11, 13, 41], [11, 14, 20], [11, 14, 24], [11, 14, 25], [11, 14, 34], [11, 14, 40], [11, 14, 42], [11, 14, 44], [11, 15, 19], [11, 15, 22], [11, 15, 27], [11, 15, 29], [11, 15, 30], [11, 15, 33], [11, 15, 38], [11, 16, 20], [11, 16, 23], [11, 16, 30], [11, 16, 34], [11, 16, 42], [11, 17, 32], [11, 17, 41], [11, 17, 42], [11, 17, 43], [11, 18, 25], [11, 18, 30], [11, 18, 32], [11, 18, 33], [11, 18, 34], [11, 18, 44], [11, 19, 23], [11, 19, 30], [11, 19, 33], [11, 19, 37], [11, 19, 38], [11, 19, 40], [11, 19, 42], [11, 19, 44], [11, 20, 24], [11, 20, 30], [11, 20, 34], [11, 20, 36], [11, 20, 38], [11, 20, 40], [11, 21, 29], [11, 21, 40], [11, 22, 23], [11, 22, 27], [11, 22, 31], [11, 22, 33], [11, 22, 34], [11, 22, 40], [11, 22, 43], [11, 22, 45], [11, 23, 27], [11, 23, 31], [11, 23, 33], [11, 23, 41], [11, 24, 25], [11, 24, 31], [11, 24, 34], [11, 24, 43], [11, 25, 37], [11, 25, 38], [11, 25, 42], [11, 25, 43], [11, 26, 30], [11, 26, 32], [11, 26, 42], [11, 27, 30], [11, 27, 34], [11, 27, 42], [11, 27, 43], [11, 27, 45], [11, 28, 31], [11, 28, 38], [11, 29, 34], [11, 29, 35], [11, 29, 37], [11, 29, 40], [11, 30, 36], [11, 30, 37], [11, 31, 45], [11, 32, 34], [11, 32, 41], [11, 32, 42], [11, 32, 43], [11, 33, 34], [11, 33, 36], [11, 33, 41], [11, 33, 45], [11, 34, 36], [11, 34, 37], [11, 34, 38], [11, 34, 39], [11, 34, 40], [11, 34, 45], [11, 35, 38], [11, 36, 41], [11, 36, 42], [11, 36, 44], [11, 37, 38], [11, 37, 44], [11, 38, 40], [11, 39, 40], [11, 40, 42], [11, 40, 43], [11, 40, 44], [11, 40, 45], [12, 13, 14], [12, 13, 16], [12, 13, 23], [12, 13, 26], [12, 13, 27], [12, 13, 28], [12, 13, 30], [12, 14, 19], [12, 14, 29], [12, 14, 31], [12, 14, 36], [12, 15, 31], [12, 15, 33], [12, 15, 35], [12, 16, 17], [12, 16, 25], [12, 16, 27], [12, 16, 31], [12, 16, 32], [12, 16, 33], [12, 16, 35], [12, 16, 36], [12, 17, 27], [12, 17, 30], [12, 17, 38], [12, 18, 33], [12, 18, 36], [12, 18, 37], [12, 18, 44], [12, 18, 45], [12, 19, 30], [12, 19, 38], [12, 20, 22], [12, 20, 32], [12, 20, 37], [12, 20, 40], [12, 20, 43], [12, 21, 22], [12, 21, 23], [12, 21, 28], [12, 21, 33], [12, 21, 42], [12, 21, 44], [12, 22, 27], [12, 22, 29], [12, 22, 35], [12, 22, 38], [12, 22, 39], [12, 22, 43], [12, 22, 45], [12, 23, 24], [12, 23, 25], [12, 23, 29], [12, 23, 33], [12, 23, 38], [12, 23, 40], [12, 23, 41], [12, 25, 28], [12, 25, 30], [12, 26, 32], [12, 26, 37], [12, 27, 31], [12, 27, 34], [12, 27, 42], [12, 28, 29], [12, 28, 31], [12, 28, 33], [12, 28, 37], [12, 29, 30], [12, 30, 33], [12, 30, 35], [12, 31, 36], [12, 31, 37], [12, 31, 45], [12, 33, 34], [12, 33, 35], [12, 33, 37], [12, 33, 43], [12, 34, 39], [12, 35, 36], [12, 35, 39], [12, 35, 44], [12, 36, 38], [12, 37, 42], [12, 37, 43], [12, 37, 44], [12, 38, 39], [12, 38, 44], [12, 43, 45], [13, 14, 23], [13, 14, 29], [13, 14, 31], [13, 15, 19], [13, 15, 20], [13, 15, 22], [13, 15, 30], [13, 15, 32], [13, 16, 17], [13, 16, 20], [13, 16, 21], [13, 16, 22], [13, 16, 34], [13, 16, 39], [13, 16, 40], [13, 16, 41], [13, 16, 42], [13, 17, 24], [13, 17, 26], [13, 17, 30], [13, 17, 35], [13, 17, 37], [13, 17, 38], [13, 18, 36], [13, 18, 43], [13, 19, 21], [13, 19, 23], [13, 19, 24], [13, 19, 29], [13, 19, 30], [13, 19, 34], [13, 19, 39], [13, 19, 41], [13, 19, 44], [13, 20, 26], [13, 20, 34], [13, 21, 27], [13, 21, 28], [13, 21, 31], [13, 21, 35], [13, 21, 36], [13, 21, 38], [13, 21, 41], [13, 22, 41], [13, 22, 43], [13, 23, 25], [13, 23, 27], [13, 23, 29], [13, 23, 30], [13, 23, 33], [13, 23, 34], [13, 23, 37], [13, 23, 41], [13, 23, 42], [13, 24, 30], [13, 24, 31], [13, 24, 43], [13, 25, 30], [13, 25, 39], [13, 25, 40], [13, 26, 41], [13, 26, 42], [13, 26, 45], [13, 27, 33], [13, 27, 35], [13, 27, 36], [13, 27, 39], [13, 28, 33], [13, 28, 35], [13, 29, 32], [13, 29, 45], [13, 30, 32], [13, 30, 34], [13, 30, 37], [13, 30, 42], [13, 30, 44], [13, 31, 40], [13, 34, 35], [13, 34, 37], [13, 35, 36], [13, 35, 37], [13, 35, 41], [13, 35, 42], [13, 35, 44], [13, 36, 39], [13, 36, 43], [13, 37, 42], [13, 37, 44], [13, 38, 44], [13, 39, 44], [13, 41, 43], [13, 42, 43], [13, 42, 44], [13, 43, 44], [14, 16, 20], [14, 16, 22], [14, 16, 23], [14, 16, 26], [14, 16, 30], [14, 16, 32], [14, 16, 33], [14, 16, 34], [14, 16, 41], [14, 17, 23], [14, 17, 25], [14, 17, 28], [14, 17, 29], [14, 17, 34], [14, 17, 43], [14, 18, 19], [14, 18, 25], [14, 18, 33], [14, 18, 38], [14, 18, 40], [14, 18, 41], [14, 18, 43], [14, 18, 45], [14, 19, 29], [14, 19, 32], [14, 19, 33], [14, 19, 42], [14, 20, 21], [14, 20, 26], [14, 20, 27], [14, 20, 29], [14, 20, 32], [14, 20, 41], [14, 20, 45], [14, 21, 24], [14, 21, 28], [14, 21, 33], [14, 21, 42], [14, 22, 29], [14, 22, 42], [14, 23, 24], [14, 23, 27], [14, 23, 29], [14, 23, 41], [14, 24, 29], [14, 24, 30], [14, 24, 38], [14, 24, 43], [14, 25, 38], [14, 25, 41], [14, 25, 42], [14, 26, 29], [14, 27, 41], [14, 28, 33], [14, 28, 38], [14, 28, 42], [14, 28, 43], [14, 28, 44], [14, 29, 38], [14, 29, 39], [14, 29, 41], [14, 29, 42], [14, 29, 43], [14, 30, 35], [14, 31, 35], [14, 31, 43], [14, 32, 43], [14, 32, 44], [14, 34, 36], [14, 34, 39], [14, 36, 41], [14, 37, 43], [14, 37, 44], [14, 38, 41], [14, 38, 44], [14, 41, 43], [14, 41, 44], [14, 41, 45], [14, 42, 43], [15, 16, 18], [15, 16, 23], [15, 16, 27], [15, 16, 33], [15, 16, 35], [15, 16, 36], [15, 16, 40], [15, 16, 44], [15, 17, 20], [15, 17, 22], [15, 17, 28], [15, 17, 32], [15, 18, 20], [15, 18, 25], [15, 18, 29], [15, 18, 30], [15, 18, 31], [15, 18, 33], [15, 18, 37], [15, 18, 38], [15, 18, 43], [15, 19, 20], [15, 19, 29], [15, 19, 31], [15, 19, 32], [15, 19, 33], [15, 19, 35], [15, 20, 32], [15, 20, 37], [15, 20, 40], [15, 20, 45], [15, 21, 24], [15, 21, 37], [15, 21, 40], [15, 21, 42], [15, 22, 29], [15, 22, 30], [15, 22, 31], [15, 23, 24], [15, 23, 27], [15, 23, 30], [15, 23, 33], [15, 23, 36], [15, 24, 26], [15, 24, 34], [15, 25, 27], [15, 25, 38], [15, 25, 40], [15, 25, 44], [15, 26, 29], [15, 26, 32], [15, 26, 38], [15, 27, 28], [15, 27, 31], [15, 27, 36], [15, 27, 37], [15, 27, 38], [15, 27, 39], [15, 27, 44], [15, 28, 32], [15, 28, 35], [15, 28, 38], [15, 28, 44], [15, 29, 31], [15, 29, 32], [15, 29, 33], [15, 29, 36], [15, 29, 41], [15, 30, 32], [15, 30, 34], [15, 30, 35], [15, 30, 36], [15, 30, 40], [15, 30, 41], [15, 30, 42], [15, 31, 36], [15, 31, 37], [15, 31, 39], [15, 31, 44], [15, 31, 45], [15, 32, 37], [15, 32, 38], [15, 33, 34], [15, 33, 36], [15, 33, 42], [15, 33, 44], [15, 36, 43], [15, 36, 45], [15, 37, 41], [15, 38, 39], [15, 38, 40], [15, 38, 42], [15, 38, 44], [15, 39, 45], [15, 40, 45], [15, 41, 45], [15, 44, 45], [16, 17, 18], [16, 17, 21], [16, 17, 27], [16, 17, 35], [16, 18, 22], [16, 18, 33], [16, 18, 36], [16, 18, 40], [16, 18, 41], [16, 19, 26], [16, 19, 28], [16, 19, 30], [16, 20, 21], [16, 20, 22], [16, 20, 34], [16, 20, 37], [16, 20, 38], [16, 20, 45], [16, 21, 38], [16, 21, 40], [16, 21, 45], [16, 22, 24], [16, 22, 25], [16, 22, 26], [16, 22, 27], [16, 22, 32], [16, 22, 33], [16, 22, 35], [16, 22, 41], [16, 22, 45], [16, 23, 28], [16, 23, 37], [16, 24, 31], [16, 24, 34], [16, 25, 28], [16, 25, 32], [16, 26, 27], [16, 26, 32], [16, 26, 35], [16, 26, 37], [16, 27, 30], [16, 27, 32], [16, 28, 29], [16, 28, 31], [16, 28, 32], [16, 28, 33], [16, 28, 45], [16, 29, 32], [16, 29, 37], [16, 29, 39], [16, 29, 43], [16, 30, 32], [16, 30, 33], [16, 30, 35], [16, 30, 39], [16, 31, 33], [16, 31, 42], [16, 31, 45], [16, 32, 35], [16, 32, 36], [16, 32, 37], [16, 32, 40], [16, 32, 42], [16, 32, 44], [16, 33, 34], [16, 33, 37], [16, 34, 41], [16, 35, 41], [16, 35, 42], [16, 35, 44], [16, 37, 42], [16, 38, 42], [16, 38, 43], [16, 43, 44], [17, 18, 37], [17, 19, 26], [17, 19, 29], [17, 19, 31], [17, 19, 33], [17, 19, 39], [17, 20, 21], [17, 20, 22], [17, 20, 23], [17, 20, 25], [17, 21, 28], [17, 21, 35], [17, 21, 38], [17, 21, 41], [17, 21, 42], [17, 21, 43], [17, 22, 29], [17, 22, 32], [17, 22, 38], [17, 22, 41], [17, 22, 42], [17, 22, 44], [17, 23, 26], [17, 23, 31], [17, 23, 33], [17, 24, 28], [17, 24, 32], [17, 24, 33], [17, 24, 34], [17, 24, 38], [17, 24, 42], [17, 24, 43], [17, 25, 29], [17, 25, 32], [17, 26, 32], [17, 26, 33], [17, 26, 34], [17, 26, 44], [17, 27, 30], [17, 27, 41], [17, 27, 42], [17, 28, 31], [17, 28, 32], [17, 28, 34], [17, 28, 38], [17, 29, 36], [17, 29, 37], [17, 29, 41], [17, 29, 42], [17, 30, 39], [17, 31, 35], [17, 31, 41], [17, 32, 36], [17, 32, 38], [17, 32, 40], [17, 32, 43], [17, 33, 37], [17, 33, 39], [17, 33, 43], [17, 34, 40], [17, 35, 37], [17, 38, 39], [17, 38, 40], [17, 38, 42], [17, 39, 41], [17, 40, 41], [17, 40, 42], [17, 40, 45], [18, 19, 20], [18, 19, 35], [18, 19, 36], [18, 19, 37], [18, 19, 41], [18, 19, 43], [18, 20, 29], [18, 20, 37], [18, 20, 39], [18, 20, 41], [18, 21, 24], [18, 21, 30], [18, 21, 45], [18, 22, 27], [18, 22, 32], [18, 22, 33], [18, 22, 36], [18, 22, 37], [18, 22, 40], [18, 22, 41], [18, 23, 24], [18, 23, 31], [18, 23, 33], [18, 23, 38], [18, 23, 42], [18, 24, 28], [18, 24, 35], [18, 24, 43], [18, 25, 29], [18, 25, 36], [18, 25, 40], [18, 25, 41], [18, 25, 42], [18, 25, 43], [18, 26, 28], [18, 27, 30], [18, 27, 34], [18, 27, 35], [18, 27, 37], [18, 27, 38], [18, 27, 44], [18, 28, 29], [18, 28, 33], [18, 28, 41], [18, 28, 44], [18, 29, 40], [18, 29, 41], [18, 29, 45], [18, 30, 33], [18, 30, 35], [18, 31, 35], [18, 32, 42], [18, 33, 35], [18, 33, 39], [18, 33, 41], [18, 33, 43], [18, 34, 36], [18, 34, 37], [18, 35, 36], [18, 35, 41], [18, 36, 38], [18, 36, 45], [18, 37, 41], [18, 38, 39], [18, 38, 40], [18, 38, 42], [18, 40, 44], [18, 41, 44], [19, 20, 22], [19, 20, 27], [19, 20, 29], [19, 20, 31], [19, 20, 36], [19, 20, 37], [19, 21, 28], [19, 21, 38], [19, 22, 26], [19, 22, 27], [19, 22, 33], [19, 22, 38], [19, 22, 41], [19, 22, 45], [19, 23, 26], [19, 23, 40], [19, 24, 25], [19, 24, 28], [19, 24, 29], [19, 24, 35], [19, 24, 38], [19, 25, 30], [19, 25, 35], [19, 25, 40], [19, 26, 29], [19, 26, 32], [19, 26, 38], [19, 27, 32], [19, 27, 33], [19, 27, 36], [19, 27, 37], [19, 27, 38], [19, 27, 39], [19, 28, 29], [19, 28, 35], [19, 28, 39], [19, 28, 40], [19, 29, 30], [19, 29, 34], [19, 29, 37], [19, 29, 38], [19, 29, 41], [19, 29, 44], [19, 30, 32], [19, 31, 32], [19, 31, 37], [19, 31, 41], [19, 32, 39], [19, 32, 44], [19, 33, 37], [19, 35, 36], [19, 35, 37], [19, 35, 44], [19, 36, 37], [19, 36, 40], [19, 36, 41], [19, 38, 39], [19, 38, 45], [19, 39, 42], [19, 39, 45], [20, 21, 23], [20, 21, 27], [20, 21, 28], [20, 21, 31], [20, 21, 35], [20, 21, 36], [20, 21, 38], [20, 21, 42], [20, 21, 43], [20, 22, 31], [20, 22, 32], [20, 22, 35], [20, 22, 39], [20, 22, 45], [20, 23, 25], [20, 23, 33], [20, 23, 41], [20, 24, 26], [20, 24, 29], [20, 24, 35], [20, 25, 26], [20, 25, 27], [20, 25, 30], [20, 25, 35], [20, 25, 42], [20, 25, 44], [20, 26, 34], [20, 27, 29], [20, 27, 30], [20, 27, 34], [20, 27, 45], [20, 28, 29], [20, 28, 31], [20, 28, 33], [20, 28, 34], [20, 28, 42], [20, 28, 45], [20, 29, 30], [20, 29, 32], [20, 29, 37], [20, 29, 40], [20, 30, 32], [20, 30, 42], [20, 30, 43], [20, 31, 39], [20, 34, 36], [20, 34, 38], [20, 34, 39], [20, 37, 41], [20, 37, 44], [20, 38, 39], [20, 39, 40], [20, 39, 43], [20, 43, 45], [21, 22, 29], [21, 22, 40], [21, 22, 43], [21, 22, 45], [21, 23, 28], [21, 23, 29], [21, 23, 35], [21, 23, 36], [21, 23, 37], [21, 23, 38], [21, 23, 41], [21, 23, 42], [21, 24, 25], [21, 24, 28], [21, 24, 32], [21, 24, 34], [21, 24, 35], [21, 24, 38], [21, 25, 28], [21, 25, 31], [21, 25, 43], [21, 26, 28], [21, 27, 28], [21, 27, 30], [21, 28, 29], [21, 28, 30], [21, 28, 32], [21, 28, 33], [21, 28, 41], [21, 28, 44], [21, 29, 30], [21, 29, 36], [21, 29, 41], [21, 30, 31], [21, 30, 41], [21, 31, 34], [21, 31, 42], [21, 33, 43], [21, 33, 44], [21, 33, 45], [21, 34, 39], [21, 34, 40], [21, 36, 42], [21, 37, 39], [21, 39, 42], [21, 40, 43], [21, 41, 45], [21, 42, 43], [21, 42, 44], [21, 43, 45], [22, 23, 26], [22, 23, 27], [22, 23, 28], [22, 23, 31], [22, 23, 33], [22, 23, 39], [22, 23, 40], [22, 23, 41], [22, 23, 45], [22, 24, 25], [22, 24, 29], [22, 24, 39], [22, 24, 43], [22, 24, 45], [22, 25, 26], [22, 25, 27], [22, 25, 35], [22, 25, 39], [22, 26, 28], [22, 26, 32], [22, 27, 29], [22, 27, 32], [22, 27, 41], [22, 27, 45], [22, 28, 29], [22, 28, 30], [22, 29, 38], [22, 29, 41], [22, 29, 42], [22, 30, 31], [22, 30, 32], [22, 30, 33], [22, 31, 33], [22, 32, 37], [22, 32, 41], [22, 32, 43], [22, 33, 38], [22, 33, 39], [22, 33, 43], [22, 33, 44], [22, 34, 43], [22, 35, 44], [22, 35, 45], [22, 36, 43], [22, 39, 40], [22, 39, 42], [22, 39, 43], [22, 40, 41], [22, 40, 44], [22, 41, 44], [22, 41, 45], [22, 42, 44], [23, 24, 25], [23, 24, 26], [23, 24, 33], [23, 24, 34], [23, 24, 40], [23, 24, 41], [23, 24, 42], [23, 25, 26], [23, 25, 31], [23, 25, 34], [23, 26, 32], [23, 26, 34], [23, 26, 37], [23, 26, 38], [23, 26, 41], [23, 27, 30], [23, 27, 32], [23, 27, 37], [23, 27, 39], [23, 28, 31], [23, 28, 32], [23, 28, 41], [23, 29, 32], [23, 30, 31], [23, 30, 33], [23, 30, 39], [23, 30, 42], [23, 31, 32], [23, 31, 41], [23, 31, 42], [23, 32, 33], [23, 32, 35], [23, 32, 37], [23, 32, 41], [23, 32, 42], [23, 33, 38], [23, 33, 45], [23, 34, 37], [23, 35, 39], [23, 35, 41], [23, 35, 42], [23, 36, 42], [23, 37, 41], [23, 37, 44], [23, 38, 41], [23, 38, 44], [23, 39, 40], [23, 39, 41], [23, 40, 42], [23, 40, 43], [23, 41, 42], [24, 25, 28], [24, 25, 41], [24, 25, 42], [24, 25, 45], [24, 26, 27], [24, 26, 28], [24, 26, 31], [24, 26, 32], [24, 26, 33], [24, 26, 43], [24, 26, 44], [24, 27, 33], [24, 27, 38], [24, 27, 40], [24, 28, 29], [24, 28, 31], [24, 28, 33], [24, 28, 34], [24, 28, 41], [24, 28, 42], [24, 28, 43], [24, 28, 44], [24, 28, 45], [24, 29, 32], [24, 29, 36], [24, 29, 39], [24, 30, 37], [24, 30, 40], [24, 30, 42], [24, 30, 43], [24, 30, 44], [24, 31, 35], [24, 31, 37], [24, 31, 38], [24, 31, 41], [24, 31, 43], [24, 31, 45], [24, 32, 43], [24, 33, 43], [24, 34, 37], [24, 34, 43], [24, 35, 39], [24, 35, 41], [24, 35, 42], [24, 36, 42], [24, 36, 43], [24, 37, 39], [24, 37, 42], [24, 37, 43], [24, 38, 41], [24, 38, 43], [24, 39, 43], [24, 40, 45], [24, 42, 43], [25, 26, 28], [25, 26, 32], [25, 26, 35], [25, 26, 39], [25, 26, 41], [25, 26, 42], [25, 27, 28], [25, 27, 30], [25, 27, 33], [25, 27, 42], [25, 28, 31], [25, 28, 34], [25, 28, 35], [25, 28, 40], [25, 28, 41], [25, 29, 39], [25, 29, 41], [25, 30, 34], [25, 30, 35], [25, 30, 37], [25, 30, 38], [25, 30, 39], [25, 31, 35], [25, 31, 41], [25, 31, 42], [25, 32, 34], [25, 32, 35], [25, 32, 38], [25, 32, 39], [25, 32, 41], [25, 33, 37], [25, 33, 42], [25, 34, 42], [25, 34, 43], [25, 34, 45], [25, 36, 41], [25, 37, 40], [25, 38, 43], [25, 39, 41], [25, 40, 41], [25, 41, 42], [25, 41, 43], [25, 42, 44], [25, 43, 45], [26, 28, 38], [26, 28, 39], [26, 29, 32], [26, 29, 35], [26, 30, 37], [26, 30, 44], [26, 32, 37], [26, 32, 41], [26, 32, 43], [26, 32, 44], [26, 32, 45], [26, 34, 35], [26, 34, 37], [26, 34, 39], [26, 35, 36], [26, 35, 41], [26, 35, 44], [26, 35, 45], [26, 36, 38], [26, 38, 42], [26, 38, 44], [26, 39, 43], [26, 40, 44], [26, 40, 45], [26, 41, 43], [26, 41, 44], [27, 28, 31], [27, 28, 33], [27, 28, 34], [27, 29, 32], [27, 29, 39], [27, 31, 33], [27, 32, 33], [27, 32, 35], [27, 32, 36], [27, 32, 39], [27, 32, 41], [27, 32, 43], [27, 32, 44], [27, 33, 34], [27, 33, 42], [27, 34, 37], [27, 36, 42], [27, 37, 38], [27, 39, 41], [27, 39, 42], [27, 42, 44], [28, 29, 31], [28, 29, 32], [28, 29, 35], [28, 29, 37], [28, 29, 38], [28, 29, 41], [28, 29, 42], [28, 29, 45], [28, 30, 31], [28, 30, 37], [28, 30, 40], [28, 31, 35], [28, 31, 37], [28, 31, 40], [28, 31, 42], [28, 32, 33], [28, 32, 35], [28, 32, 39], [28, 32, 41], [28, 32, 42], [28, 32, 43], [28, 32, 44], [28, 33, 34], [28, 33, 43], [28, 34, 37], [28, 35, 36], [28, 35, 45], [28, 36, 37], [28, 36, 38], [28, 36, 43], [28, 37, 41], [28, 38, 41], [28, 39, 44], [28, 40, 42], [28, 40, 44], [28, 40, 45], [28, 41, 45], [28, 42, 44], [28, 44, 45], [29, 30, 32], [29, 30, 36], [29, 30, 40], [29, 31, 41], [29, 32, 34], [29, 32, 35], [29, 32, 41], [29, 33, 36], [29, 34, 41], [29, 34, 42], [29, 34, 43], [29, 35, 39], [29, 35, 41], [29, 35, 45], [29, 36, 42], [29, 36, 44], [29, 37, 42], [29, 37, 44], [29, 41, 43], [29, 42, 44], [29, 44, 45], [30, 31, 32], [30, 31, 35], [30, 31, 36], [30, 32, 36], [30, 32, 44], [30, 33, 40], [30, 35, 45], [30, 36, 40], [30, 37, 42], [30, 38, 42], [30, 40, 45], [30, 43, 44], [30, 44, 45], [31, 32, 35], [31, 32, 42], [31, 32, 44], [31, 33, 35], [31, 33, 39], [31, 33, 43], [31, 34, 41], [31, 35, 36], [31, 35, 41], [31, 35, 42], [31, 36, 39], [31, 36, 41], [31, 36, 42], [31, 37, 39], [31, 37, 45], [31, 38, 42], [31, 39, 42], [31, 42, 44], [31, 42, 45], [32, 34, 35], [32, 34, 37], [32, 35, 38], [32, 35, 39], [32, 35, 41], [32, 35, 42], [32, 35, 43], [32, 36, 37], [32, 36, 41], [32, 36, 44], [32, 37, 38], [32, 37, 42], [32, 38, 41], [32, 38, 45], [32, 39, 44], [32, 40, 44], [32, 41, 44], [32, 42, 43], [32, 42, 45], [32, 43, 45], [33, 34, 41], [33, 34, 45], [33, 35, 42], [33, 37, 39], [33, 38, 41], [33, 38, 43], [33, 38, 44], [33, 39, 42], [33, 39, 43], [33, 39, 45], [33, 41, 43], [33, 43, 44], [34, 35, 38], [34, 36, 38], [34, 36, 39], [34, 36, 43], [34, 37, 41], [34, 37, 42], [34, 37, 43], [34, 37, 44], [34, 38, 44], [35, 36, 38], [35, 37, 38], [35, 38, 42], [35, 39, 40], [35, 40, 41], [35, 40, 43], [36, 37, 39], [36, 38, 42], [37, 39, 42], [37, 42, 44], [38, 39, 45], [38, 40, 41], [38, 41, 42], [38, 42, 44], [38, 43, 45], [38, 44, 45], [39, 40, 42], [39, 40, 45], [39, 42, 43], [39, 42, 44], [40, 43, 45], [40, 44, 45], [41, 42, 44]] + +# frozenset 캐시 + +_F_SUM6 = frozenset(ALLOW_SUM6) +_F_SUM6_DIFF = frozenset(ALLOW_SUM6_DIFF) +_F_AVG6 = frozenset(ALLOW_AVG6) +_F_AVG6_DIFF = frozenset(ALLOW_AVG6_DIFF) +_F_SUM3F = frozenset(ALLOW_SUM3F) +_F_SUM3F_DIFF = frozenset(ALLOW_SUM3F_DIFF) +_F_SUM3B = frozenset(ALLOW_SUM3B) +_F_SUM3B_DIFF = frozenset(ALLOW_SUM3B_DIFF) +_F_GO_SUM = frozenset(ALLOW_GO_SUM) +_F_GO_SUM_DIFF = frozenset(ALLOW_GO_SUM_DIFF) +_F_INTERVAL = frozenset(ALLOW_INTERVAL) +_F_INTERVAL_DIFF = frozenset(ALLOW_INTERVAL_DIFF) +_F_FIRST_LETTER = frozenset(ALLOW_FIRST_LETTER) +_F_FIRST_LETTER_DIFF = frozenset(ALLOW_FIRST_LETTER_DIFF) +_F_LAST_LETTER = frozenset(ALLOW_LAST_LETTER) +_F_LAST_LETTER_DIFF = frozenset(ALLOW_LAST_LETTER_DIFF) +_F_B0 = frozenset(ALLOW_B0) +_F_B0_DIFF = frozenset(ALLOW_B0_DIFF) +_F_B5 = frozenset(ALLOW_B5) +_F_B5_DIFF = frozenset(ALLOW_B5_DIFF) +_F_UNIQ_END = frozenset(ALLOW_UNIQ_END) +_F_UNIQ_END_DIFF = frozenset(ALLOW_UNIQ_END_DIFF) +_F_AC = frozenset(ALLOW_AC) +_F_AC_DIFF = frozenset(ALLOW_AC_DIFF) +_F_MUL3 = frozenset(ALLOW_MUL3) +_F_MUL3_DIFF = frozenset(ALLOW_MUL3_DIFF) +_F_MUL4 = frozenset(ALLOW_MUL4) +_F_MUL4_DIFF = frozenset(ALLOW_MUL4_DIFF) +_F_MUL5 = frozenset(ALLOW_MUL5) +_F_MUL5_DIFF = frozenset(ALLOW_MUL5_DIFF) +_F_MUL6 = frozenset(ALLOW_MUL6) +_F_MUL6_DIFF = frozenset(ALLOW_MUL6_DIFF) +_F_MUL7 = frozenset(ALLOW_MUL7) +_F_MUL7_DIFF = frozenset(ALLOW_MUL7_DIFF) +_F_MUL8 = frozenset(ALLOW_MUL8) +_F_MUL8_DIFF = frozenset(ALLOW_MUL8_DIFF) +_F_MUL9 = frozenset(ALLOW_MUL9) +_F_MUL9_DIFF = frozenset(ALLOW_MUL9_DIFF) +_F_MUL10 = frozenset(ALLOW_MUL10) +_F_MUL10_DIFF = frozenset(ALLOW_MUL10_DIFF) +_F_MUL11 = frozenset(ALLOW_MUL11) +_F_MUL11_DIFF = frozenset(ALLOW_MUL11_DIFF) +_F_MUL13 = frozenset(ALLOW_MUL13) +_F_MUL13_DIFF = frozenset(ALLOW_MUL13_DIFF) +_F_MUL17 = frozenset(ALLOW_MUL17) +_F_MUL17_DIFF = frozenset(ALLOW_MUL17_DIFF) +_F_MUL19 = frozenset(ALLOW_MUL19) +_F_MUL19_DIFF = frozenset(ALLOW_MUL19_DIFF) +_F_MUL23 = frozenset(ALLOW_MUL23) +_F_MUL23_DIFF = frozenset(ALLOW_MUL23_DIFF) +_F_PRIME_N = frozenset(ALLOW_PRIME_N) +_F_COMPOSITE_N = frozenset(ALLOW_COMPOSITE_N) +_F_COMPOSITE_DIFF = frozenset(ALLOW_COMPOSITE_DIFF) +_F_EVEN_N = frozenset(ALLOW_EVEN_N) +_F_EVEN_DIFF = frozenset(ALLOW_EVEN_DIFF) +_F_SEC10 = frozenset(ALLOW_SEC10) +_F_SEC10_DIFF = frozenset(ALLOW_SEC10_DIFF) +_F_W8 = frozenset(ALLOW_W8) +_F_W8_DIFF = frozenset(ALLOW_W8_DIFF) +_F_W12 = frozenset(ALLOW_W12) +_F_W12_DIFF = frozenset(ALLOW_W12_DIFF) +_F_W16 = frozenset(ALLOW_W16) +_F_W16_DIFF = frozenset(ALLOW_W16_DIFF) +_F_W20 = frozenset(ALLOW_W20) +_F_W20_DIFF = frozenset(ALLOW_W20_DIFF) +_F_CONTINUS_MAX = frozenset(ALLOW_CONTINUS_MAX) +_F_HL_SEEN = frozenset(HL_SEEN) + diff --git a/scripts/run_with_ncue.sh b/scripts/run_with_ncue.sh new file mode 100755 index 0000000..9d9e869 --- /dev/null +++ b/scripts/run_with_ncue.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# miniconda 환경 ncue에서 Python으로 인자 실행: ./scripts/run_with_ncue.sh final_filterTest.py +set -euo pipefail +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$ROOT" +for base in "${MINICONDA_HOME:-}" "$HOME/miniconda3" "$HOME/miniforge3" "$HOME/anaconda3" "$HOME/mambaforge"; do + [ -n "$base" ] || continue + c="$base/bin/conda" + if [ -x "$c" ]; then + exec "$c" run -n ncue -- python "$@" + fi +done +if [ -n "${CONDA_EXE:-}" ] && [ -x "$CONDA_EXE" ]; then + exec "$CONDA_EXE" run -n ncue -- python "$@" +fi +echo "conda ncue 환경을 찾지 못했습니다. 터미널에서: conda activate ncue && python \"\$@\"" >&2 +exit 1 diff --git a/tools/compute_final_filter_params.py b/tools/compute_final_filter_params.py new file mode 100644 index 0000000..fd4af6d --- /dev/null +++ b/tools/compute_final_filter_params.py @@ -0,0 +1,405 @@ +#!/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()