- M01 값 역색인 · 숫자 정규화 한 벌(N1~N10 · 단위 기준 · % ↔ 소수 · t ↔ kg · 인/양 몫) · M01 지문마다 한 번 · 서버 시작 때 뒤에서 지음
- 값 길(상수 지문 → 로직 · 우연 보정 유의 4) · 합친 길(분석 648-D2 차례 · 후보 2~6 엔진 품 대조) · 검산 · 옵션(B1~B6 → 값 길 V → 고르기 맞댐 → 입력 필요)
- 세 단 확정 · 근접(판 차이 · 현장 값 · 감률 · 할증 배수 · 단위 기준 차이) · 사람(+ 값 약한 1 등) · 없음(M01 보강 목록)
- 길 GET hopyo-basis/{키} · hopyo-basis/{키}/{번} · 파생 저장본 · 올림 때 HP 숨은식(STmate G 식) · 끝내기 때 지움 · 판정 길 그대로
- 보정 자료 = 여덟 파일 상수 1,094 · 시험 test_793_3a 11(459 블록 기초 260 · 옵션 전부 52 · 품 같음 141 · 없음 85)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
308 lines
12 KiB
Python
308 lines
12 KiB
Python
"""근거 제안 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
|