"""근거 제안 3 — 값 길: 상수 지문 → M01 로직 후보(PLAN 793-3a · 분석 648-D 1-3) · 읽기만. 로직마다 입력 값 조합(400 넘으면 입력마다 혼자 가장 많이 덮는 값) → 찾기마다 줄 하나(새로 덮는 상수가 가장 많은 줄부터) → 덮은 상수 · 우연 보정 유의(−log10 꼬리 확률) · 판정 U 유일 · 다 설명 · 옵션 하나 / P 유일 / A 동점(유의 1 안) / N 유의 4 미만 / N0 상수 없음 · 동점이 모두 한 하위 로직을 부르면 그 하위(핵). 블록 = 일 절 2+ · 상수 2+ 주 절이 있으면 주 절 판정 모음 + 절 밖 상수 · 그 밖 블록 통째 + 덧 기초. """ from __future__ import annotations import itertools import json import math from collections import defaultdict from typing import Any from M02_MasterTemplete import M02_San_Basis_Const as bc from M02_MasterTemplete import M02_San_Basis_Index as bi TIE = 1.0 # 동점 폭(유의) SIG_MIN = 4.0 # 1등 유의가 이 아래면 N — 로직 1,515 중 우연 1등 ≈ 10^-3.2 fam = bi.fam def solve(hits: list[tuple]) -> dict: """hits = [(로직, 부름id, 줄, 묶음)] · 식 속 수 = 부름id None → 덮음 · 옵션 · 옵션여럿.""" inl = {h[0] for h in hits if h[1] is None} bound = [h for h in hits if h[1] is not None] inputs = sorted({i for h in bound for i in h[3] if not i.startswith("§")}) vals = {i: sorted({h[3][i] for h in bound if i in h[3]}) for i in inputs} by_call: dict = defaultdict(lambda: defaultdict(set)) row_bind = {} for k, call, row, b in bound: by_call[call][row].add(k) row_bind[(call, row)] = b order = sorted(by_call, key=lambda c: -max(len(x) for x in by_call[c].values())) def run(combo: dict): cov, fixed, pick = set(inl), dict(combo), {} while True: best = None for call in order: if call in pick: continue for r, ks in by_call[call].items(): gain = len(ks - cov) if ( gain and (best is None or gain > best[0]) and all( fixed.get(i, v) == v if i.startswith("§") else combo.get(i) == v for i, v in row_bind[(call, r)].items() ) ): best = (gain, call, r, ks) if best is None: break _, call, r, ks = best cov |= ks pick[call] = r fixed |= {i: v for i, v in row_bind[(call, r)].items() if i.startswith("§")} return cov, pick, -len(pick) if (math.prod(len(v) for v in vals.values()) if vals else 1) <= 400: combos = [dict(zip(inputs, c)) for c in itertools.product(*[vals[i] for i in inputs])] or [ {} ] else: # ponytail: 입력 값 조합이 많으면 입력마다 혼자 가장 많이 덮는 값 — 상호작용 놓칠 수 있음 cnt = defaultdict(set) for h in bound: for i, v in h[3].items(): cnt[(i, v)].add(h[0]) combos = [{i: max(vals[i], key=lambda v: len(cnt[(i, v)])) for i in inputs}] best, outs = (-1, -1), [] for combo in combos: cov, pick, ev = run(combo) score = (len(cov), ev) if score > best: best, outs = score, [(combo, cov, pick)] elif score == best: outs.append((combo, cov, pick)) used = { i for _, _, pick in outs for call, r in pick.items() for i in row_bind[(call, r)] if not i.startswith("§") } resolved, opt_amb = {}, {} for i in sorted(used): seen = sorted({o[0].get(i) for o in outs if o[0].get(i) is not None}) if len(seen) == 1: resolved[i] = json.loads(seen[0]) elif seen: opt_amb[i] = [json.loads(x) for x in seen] same_cov = len({frozenset(o[1]) for o in outs}) == 1 return {"덮음": outs[0][1], "옵션": resolved, "옵션여럿": opt_amb, "조합같은덮음": same_cov} def rank(idx: bi.Index, K: list[dict]) -> list[dict]: """상수 목록 → 로직 후보(유의 차례).""" per_item, anchored, how_of = defaultdict(list), set(), defaultdict(dict) for ki, k in enumerate(K): for item, call, row, bd, how, anc in idx.explain(k): per_item[item].append((ki, call, row, bd)) how_of[item].setdefault(ki, how) if anc: anchored.add(item) real = sorted(i for i, k in enumerate(K) if not k.get("일반")) ub = [] for it, hs in per_item.items(): if it in anchored: c = len({h[0] for h in hs} & set(real)) ub.append((bi.pb_sig([idx.q_of(it, K[i]) for i in real], c), c, it)) ub.sort(key=lambda x: (-x[0], -x[1], x[2])) out, best = [], 0.0 for s_ub, c, item in ub: if c == 0 or s_ub < best - TIE or len(out) >= 200: break got = solve(per_item[item]) got_real = got["덮음"] & set(real) s_val = bi.pb_sig([idx.q_of(item, K[i]) for i in real], len(got_real)) best = max(best, s_val) out.append({"로직": item, **got, "꼴": how_of[item], "실수": len(got_real), "유의": s_val}) out.sort(key=lambda o: (-o["유의"], -o["실수"], o["로직"])) return out def collapse(idx: bi.Index, tied: list[dict]) -> tuple[list[dict], str]: """동점 정리 — 다른 동점이 모두 그 로직을 부르면 그 하위(핵) · 위 로직 하나면 그 위 로직 · 그 밖 = 동점 위 로직이 덮는 하위는 뺌.""" for c in tied: others = [o for o in tied if o is not c] if others and all(o["로직"] in idx.callers.get(c["로직"], set()) for o in others): return ( (others, "위 로직 하나") if len(others) == 1 else ([c], f"핵 하위 로직(위 로직 {len(others)})") ) keep = [] for o in tied: if not [ p for p in tied if p is not o and p["로직"] in idx.callers.get(o["로직"], set()) and o["덮음"] <= p["덮음"] ]: keep.append(o) return keep, "" def judge(idx: bi.Index, K: list[dict]) -> dict: """한 단위(절 · 블록) 판정 — U · P · A · N · N0.""" real = {i for i, k in enumerate(K) if not k.get("일반")} if not real: return {"판정": "N0", "까닭": "지문 없음(상수 0)", "후보": [], "K": K} cands = [o for o in rank(idx, K) if o["실수"]] if not cands: return {"판정": "N", "까닭": "맞는 표 없음", "후보": [], "K": K} top = cands[0] if top["유의"] < SIG_MIN: return { "판정": "N", "까닭": f"유의 못 미침(1등 {top['실수']}/{len(real)} · 유의 {top['유의']})", "후보": cands[:5], "K": K, } tied, note = collapse(idx, [o for o in cands if o["유의"] >= top["유의"] - TIE]) if len(tied) >= 2: return { "판정": "A", "까닭": f"동점 {len(tied)}", "후보": tied[:12], "n": len(tied), "K": K, "남음": sorted(real - top["덮음"]), } top = tied[0] left = sorted(real - top["덮음"]) full = not left and not top["옵션여럿"] and top["조합같은덮음"] rest = [o for o in cands if o is not top] return { "판정": "U" if full else "P", "까닭": note, "후보": [top] + rest[:4], "K": K, "남음": left, } def cover(idx: bi.Index, K: list[dict], first: dict) -> list[dict]: """블록 덮기 — 첫 기초 뒤 남은 상수로 다음 기초(새 상수 2+ · 유일 · 넷까지).""" picks, done = [first], set(first["덮음"]) for _ in range(3): rest = [k if i not in done else {**k, "일반": True} for i, k in enumerate(K)] if all(k.get("일반") for k in rest): break got = judge(idx, rest) if got["판정"] not in ("U", "P"): break o = got["후보"][0] if len(o["덮음"] - done) < 2: break picks.append({**o, "동점": 1}) done |= o["덮음"] return picks def assemble(idx: bi.Index, bK: list[dict], mains: list) -> dict: """절 2+ 블록 — 주 절 판정 모음 + 절 밖 상수(머리 · 비목 절)는 고른 기초가 덮는지 · 남으면 따로.""" picks, verdicts, covered = [], [], set() def key(c): return (round(c["v"], 9), fam(c["단위"]), c.get("직종")) for _, _, K, sj in mains: verdicts.append(sj["판정"]) if sj["판정"] in ("U", "P"): o = sj["후보"][0] mine = {key(K[j]) for j in o["덮음"]} picks.append({**o, "덮음": {i for i, c in enumerate(bK) if key(c) in mine}}) covered |= {key(c) for c in K} rest = [c if key(c) not in covered else {**c, "일반": True} for c in bK] left = {i for i, c in enumerate(rest) if not c.get("일반")} if left and picks: chosen = {p["로직"] for p in picks} for o in rank(idx, rest): if o["로직"] not in chosen: continue got = o["덮음"] & left for p in picks: if p["로직"] != o["로직"]: continue p["덮음"] = p["덮음"] | got if got: # 절 밖 상수가 정한 옵션도 얹음(같은 입력 다른 값 = 여럿) for kk, vv in o["옵션"].items(): if kk in p["옵션"] and p["옵션"][kk] != vv: p["옵션여럿"] = {**p["옵션여럿"], kk: [p["옵션"][kk], vv]} else: p["옵션"] = {**p["옵션"], kk: vv} left -= got if len(left) >= 2: extra = judge(idx, [c if i in left else {**c, "일반": True} for i, c in enumerate(bK)]) if extra["판정"] in ("U", "P"): picks.append(extra["후보"][0]) left -= extra["후보"][0]["덮음"] verdicts.append(extra["판정"]) if not picks: out = { "판정": "A" if "A" in verdicts else ("N" if "N" in verdicts else "N0"), "후보": [], "K": bK, } a = next((x[3] for x in mains if x[3]["판정"] == "A"), None) return out | ({"후보": a["후보"], "n": a["n"], "K": a["K"]} if a else {}) allu = not left and all(not p["옵션여럿"] for p in picks) return { "판정": "U" if allu else "P", "후보": picks, "K": bK, "기초목록": picks, "남음": sorted(left), } def _show(o: dict) -> dict: return {"로직": o["로직"], "옵션": o["옵션"], "옵션여럿": o["옵션여럿"]} def value(idx: bi.Index, pr: dict) -> dict: """블록 준비(`Const.prep`) → 값 판정 {판정 U|P|A|N|N0, 기초 · 동점 · 약한후보 [{로직, 옵션}], 절 [{절, 판정, 기초?, 동점?}], K, 입력}.""" lines, per_line, secs = pr["lines"], pr["per_line"], pr["secs"] bK, bIns = bc.gather(per_line, range(len(lines))) sec_res = [] for title, ids in secs: K, _ = bc.gather(per_line, ids) sec_res.append((title, ids, K, judge(idx, K))) mains = [x for x in sec_res if len([k for k in x[2] if not k.get("일반")]) >= 2] multi = len(secs) >= 2 and len(mains) >= 1 bj = assemble(idx, bK, mains) if multi else judge(idx, bK) rec: dict[str, Any] = { "판정": bj["판정"], "K": [{"단위": c["단위"]} for c in bK], "입력": [c["v"] for c in bIns], } if bj["판정"] in ("U", "P"): rec["기초"] = [_show(p) for p in (bj.get("기초목록") or cover(idx, bK, bj["후보"][0]))] elif bj["판정"] == "A": rec["동점"] = [_show(o) for o in bj["후보"]] rec["n"] = bj["n"] elif bj["판정"] == "N" and bj.get("후보"): rec["약한후보"] = [_show(o) for o in bj["후보"][:3]] rec["절"] = [] for title, _, _, sj in sec_res: one = {"절": title, "판정": sj["판정"]} if sj["판정"] in ("U", "P"): one["기초"] = _show(sj["후보"][0]) elif sj["판정"] == "A": one["동점"] = [o["로직"] for o in sj["후보"]] rec["절"].append(one) return rec