"""任务 3 · 关键词打分：题面映射到算法族（lab 0027）

这一段**完全不靠模型**：拿题面里的关键词，给每个算法族打分。它不是主力，
但它是**交叉检查**——模型给了一个结论，关键词表给不出任何支持，就值得警惕。

它的定位要说准：

  · 它是**提示**，不是**判决**；
  · 它给出的是**排序的候选**，不是唯一答案；
  · 它最可靠的用法是当交叉检查。

所以这一段交回的是**候选列表**（排好序的），不是一个名字。排序口径是两级的：
先按命中数降序，再按族名字典序升序——这样同分时谁在前是确定的、可复现的。

关键词表有一个必须**如实保留**的现象：假阳性。「合并果子」那道题
（`每次把两堆合并，代价是重量之和。求最小总代价，每次取最小的两堆。`）
正确答案是**堆**，但题面里有「重量」两个字，于是**背包**也以 1 个命中并列出现在候选里。

    题面「合并果子」首选 堆（命中 1 个：['每次取最小']）
         并列 背包（命中 1 个：['重量']）

这是**要如实报告的现象，不许藏**：表改窄了（把「重量」删掉、或者给堆加几个词把它顶下去），
这一行的并列就没了。判分比的是**同一张表的另一个实现**（tools.canonical_classify），
表不一致就直接点名说哪张表被动过。

任务里**不许自己写一份族名与关键词表**：表就是 tools.FAMILIES，逐字用它。

接口：文件名、函数名、带 print 的行都是判分接口，别改；要写的是两个 TODO。

跑： py task3_classify.py
判： py run_tests.py
"""

from tools import (FAMILIES, PROBLEMS, canonical_scores, clamp, show_banner, show_line)


# ══ TODO 1 ═════════════════════════════════════════════════
# classify(text)：按 tools.FAMILIES 的关键词命中数给算法族打分，交回**排好序的候选**。
#
# 形状：[ (命中数, 族名, 命中词表), ... ]
#
#   · 遍历 FAMILIES 的每一对 (family, keys)；
#   · hit = [k for k in keys if k in text]——命中词表按关键词表里的先后顺序，这是判分接口；
#   · **一个都没命中的族不放进去**（放进去的话「首选」就变成字典序第一个族了）；
#   · 排序：先按命中数**降序**，再按族名**升序**。字典序升序这一点是判分接口：
#     「岛屿数量」那一行是图的搜索排在并查集前面，就是靠这条。
#     写 sort(key=...) 时把两级都写进 key：(-命中数, 族名)。
#   · 空文本、没命中任何词 → 交回空表 []。
def classify(text):
    return []


# ══ TODO 2 ═════════════════════════════════════════════════
# explain_problems()：把 tools.PROBLEMS 里六道题面各跑一遍 classify 并打印出来，
# 最后交回 {题面名: 候选列表}。
#
# 打印格式是判分接口，照课件那一节的形状：
#
#     题面「木材加工」首选 二分答案（命中 2 个：['最大化', '最长']）
#         并列 图的搜索（命中 1 个：['网格']）        ← 第二候选与首选**同分**时叫「并列」
#         次选 背包（命中 1 个：['重量']）            ← 不同分时叫「次选」
#
# 一条题面只打印两行：首选那一行，加上第二个候选那一行（有第二个候选时才打）。
# 第二行的前缀按同不同分选「并列」或「次选」。
# 一个都没命中的题面打 题面「名字」：一个都没命中。
# 打印里的命中词表直接用 repr(命中词表)——这样中文引号、方括号都跟课件逐字一致。
def explain_problems():
    return {}


def tied_rows(rows):
    """把候选表里与首选同分的那几个挑出来（工具函数，别改）。"""
    if not rows:
        return []
    return [row for row in rows[1:] if row[0] == rows[0][0]]


if __name__ == '__main__':                      # 别改：判分读的就是下面这几行
    show_banner('0027 六道题面各跑一遍')
    got = explain_problems()

    show_banner('0027 假阳性：合并果子那一行的并列')
    merge = classify(PROBLEMS['合并果子'])
    # 下面每一格都是从 merge 里**查**出来的，查不到就打占位值——
    # 这样候选表还空着时脚本照样跑得动，打出来的每一行都是判分要读的那一行。
    show_line('首选', merge[0][1] if merge else '（还没接上）')                  # 别改：期望 堆
    show_line('首选命中数', merge[0][0] if merge else 0)                        # 别改：期望 1
    tied = tied_rows(merge)
    show_line('并列的族', '、'.join(row[1] for row in tied) if tied else '（没有并列）')  # 别改
    for row in tied:
        show_line('并列族 ' + row[1] + ' 命中', '、'.join(row[2]))              # 别改：期望 重量
    show_line('候选数', len(merge))                                            # 别改：期望 2

    show_banner('0027 族名表与判分用的是同一张')
    show_line('族名表大小', len(FAMILIES))       # 别改：期望 8
    show_line('木材加工候选数', len(classify(PROBLEMS['木材加工'])))     # 别改：期望 1
    show_line('区间调度候选数', len(classify(PROBLEMS['区间调度'])))     # 别改：期望 1
    # 这一行用来核对「同分时谁在前」：并查集与图的搜索都是 1 个命中，图的搜索在前。
    island = classify(PROBLEMS['岛屿数量'])
    show_line('岛屿数量首选', island[0][1] if island else '（还没接上）')   # 别改：期望 图的搜索
