"""导读案例：用页数预测打印耗时。仅使用 Python 标准库。"""

# 每个二元组都是（页数，实际耗时/秒）。数据为教学构造，并非真实测量。
TRAIN = [(5, 37), (10, 47), (15, 66), (20, 81), (25, 92), (30, 112)]
VALIDATION = [(8, 43), (18, 76), (28, 103)]
TEST = [(12, 58), (22, 85), (26, 101)]


def predict(pages, a, b):
    return a * pages + b


def mse(data, a, b):
    """均方误差：逐条计算误差的平方，再取平均。"""
    total = 0.0
    for pages, actual in data:
        error = predict(pages, a, b) - actual
        total += error ** 2
    return total / len(data)


def mae(data, a, b):
    """平均绝对误差：逐条计算误差的绝对值，再取平均。"""
    total = 0.0
    for pages, actual in data:
        total += abs(predict(pages, a, b) - actual)
    return total / len(data)


def train(data):
    # 只用训练集，在 11 × 41 = 451 组候选参数中选择。
    best_loss = float("inf")
    best_parameters = None
    for step in range(11):
        a = step / 2  # 0、0.5、1、……、5
        for b in range(41):  # 0、1、2、……、40
            loss = mse(data, a, b)
            if loss < best_loss:
                best_loss = loss
                best_parameters = (a, b)
    return best_parameters


def main():
    # 基线也只用训练集：无论页数多少，都预测训练耗时的平均值。
    mean_time = sum(actual for _, actual in TRAIN) / len(TRAIN)
    a, b = train(TRAIN)
    print(f"训练得到：a={a:.1f}, b={b:.1f}")
    print(f"训练 MSE：{mse(TRAIN, a, b):.2f} 秒²")

    # 用验证集选择模型方案，不用测试集调参。
    candidates = {"均值基线": (0, mean_time), "线性模型": (a, b)}
    best_name = None
    best_score = float("inf")
    for name, (candidate_a, candidate_b) in candidates.items():
        score = mae(VALIDATION, candidate_a, candidate_b)
        print(f"{name}的验证 MAE：{score:.2f} 秒")
        if score < best_score:
            best_score = score
            best_name = name

    # 方案到此固定，测试结果不再用于修改它。
    a, b = candidates[best_name]
    print(f"选定方案：{best_name}")
    print(f"测试 MAE：{mae(TEST, a, b):.2f} 秒")

    # 对一个尚未实际打印的新任务进行预测。
    print(f"24 页的预测耗时：{predict(24, a, b):.0f} 秒")


if __name__ == "__main__":
    main()
