首页
/ freeCodeCamp 每日编程挑战第 83 题解析:Python 实现基于字母权重的签名校验(Signature Validation)

freeCodeCamp 每日编程挑战第 83 题解析:Python 实现基于字母权重的签名校验(Signature Validation)

2026-09-09 20:14:12作者:宗隆裙

本篇技术指南以 freeCodeCamp 开源课程仓库中 curriculum/challenges/english/blocks/daily-coding-challenges-python/68e39ed6106dac2f0a98fd65.md(Challenge 83: Signature Validation)为核心,完整还原题目规则、测试用例与官方参考解法,并结合仓库源码讲解这道 Python 挑战在 freeCodeCamp 平台上的组织方式与运行机制。读完本文,你将掌握"字母权重编码 + 求和比对"这类校验型题目的标准解题思路,理解 ord() 与字符区间判断的组合用法,并能够独立写出通过全部单元测试的实现。

挑战背景:这道题在课程体系中的位置

本题是 freeCodeCamp 课程中 daily-coding-challenges-python 板块(block)的第 83 道每日编程挑战。从 板块结构定义 可以看到,该板块包含 200 余道 Python 小题,覆盖字符串处理、数学计算、模拟仿真等常见面试与竞赛题型,帮助学习者以每日一题的方式持续训练编程基本功。

在挑战文件头部 frontmatter 中,本挑战通过 challengeType: 29 标记为每日编程挑战类型,dashedName: challenge-83 作为 URL 化标识;题目本身按 freeCodeCamp 标准挑战格式组织为 --description--(题目描述)、--hints--(测试用例)、--seed--(初始代码)与 --solutions--(官方参考解法)四个部分。该板块在 板块 JSON 中声明了 helpCategory: "Python"usesMultifileEditor: true,说明它属于 Python 帮助分类,并启用多文件编辑器进行作答。

题目规则:字母权重编码与签名计算

给定三个输入——消息字符串(message)、密钥字符串(key)和一个签名数字(signature),要求按照下述编码方法判断签名是否有效:

  1. 字母取值规则:消息与密钥中的每个字母对应一个数值——
    • az(小写)分别取值 126
    • AZ(大写)分别取值 2752
  2. 非字母字符:所有其他字符(空格、数字、标点符号等)不产生任何数值,即视为 0
  3. 签名计算:签名 = 消息中所有字符的数值之和 + 密钥中所有字符的数值之和。

最终,将计算出的签名与传入的 signature 参数比对:相等返回 True,否则返回 False

字母权重速查表

字符区间 数值范围 计算方式
az 1 – 26 字符在字母表中的位置
AZ 27 – 52 小写对应值 + 26
其他字符 0 无贡献

例如 a = 1z = 26A = 27Z = 52;空格、逗号、问号、数字等一律按 0 处理。

官方给出的完整示例

题目用消息 "foo"、密钥 "bar" 演示了完整计算过程:

f (6) + o (15) + o (15) = 36
b (2) + a (1) + r (18) = 21
36 + 21 = 57

因此 verify("foo", "bar", 57) 应返回 True;若传入 54,因与真实签名不符,应返回 False

输入输出契约与初始代码

你需要实现函数 verify,接收三个参数并返回布尔值:

  • message: str —— 待校验的消息字符串;
  • key: str —— 密钥字符串;
  • signature: int —— 声称的签名值。

返回 True 表示签名有效,False 表示无效。

题目给出的初始(seed)代码如下:

def verify(message, key, signature):

    return message

它只是一个占位实现,当前直接返回了 message,需要你替换为真正的校验逻辑才能通过测试。

完整测试用例清单(hints)

挑战通过 6 个单元测试对实现进行验证,覆盖了大小写混合、标点与空格干扰、长字符串等多种场景:

调用 期望结果 说明
verify("foo", "bar", 57) True 题目示例,纯小写
verify("foo", "bar", 54) False 签名偏小,必须拒绝
verify("freeCodeCamp", "Rocks", 238) True 大小写混合(CR 按大写区间计值)
verify("Is this valid?", "No", 210) False 含空格与问号,均计 0
verify("Is this valid?", "Yes", 233) True 相同消息、不同密钥,签名随之变化
verify("Check out the freeCodeCamp podcast,", "in the mobile app", 514) True 长消息 + 逗号与空格,考验累加正确性

这些用例的意图很明确:密钥不同则签名不同(对比第 4、5 条,同一消息在 "No" 下为 213,在 "Yes" 下为 233),任何非字母字符都必须被安全忽略。

在课程体系中,这些 --hints-- 会被转换成实际的测试执行代码。本挑战的每个断言都以内嵌 JavaScript 的形式声明,运行时通过 runPython 调用 Python 的 unittest 框架执行,例如第一条用例对应:

({test: () => { runPython(`
from unittest import TestCase
TestCase().assertIs(verify("foo", "bar", 57), True)`)
}})

也就是说,平台会把你提交的 verify 函数注入到 Python 解释器中,再依次运行这 6 个断言;任何一条失败都会导致挑战未通过。这也解释了为什么函数必须恰好命名为 verify,且返回值必须是 Python 布尔值。

官方参考解法逐步拆解

挑战自带的参考解法如下:

def verify(message, key, signature):
    def charValue(ch) -> int:
        if 'a' <= ch <= 'z':
            return ord(ch) - ord('a') + 1
        elif 'A' <= ch <= 'Z':
            return ord(ch) - ord('A') + 27
        else:
            return 0

    def compute_sum(s):
        return sum(charValue(ch) for ch in s)

    total = compute_sum(message) + compute_sum(key)
    return total == signature

第一步:charValue 单字符取值函数

核心是利用 Python 内置的 ord() 函数把字符转换为 ASCII 码,再用"差值偏移"得到题设数值,从而避免手写 52 项映射表:

  • 小写分支:'a' <= ch <= 'z' 利用 Python 字符串比较特性判断字符是否位于小写区间;ord(ch) - ord('a') + 1a 映射为 1b 映射为 2……z 映射为 26
  • 大写分支:'A' <= ch <= 'Z' 同理,ord(ch) - ord('A') + 27A 映射为 27……Z 映射为 52,与题设"大写 = 小写 + 26"完全吻合。
  • 其余情况返回 0,覆盖空格、标点、数字等所有非字母字符。

值得注意,判断大小写也可以用 ch.islower() / ch.isupper(),但官方解法选择区间比较,语义上更严格地限定了 ASCII 英文字母范围。

第二步:compute_sum 字符串求和函数

compute_sum 用生成器表达式 sum(charValue(ch) for ch in s) 一次性遍历字符串,对每个字符调用 charValue 并累加。配合 Python 的 sum() 内建函数,代码非常简洁,且由于生成器惰性求值,不会产生额外列表开销。

第三步:主逻辑与复杂度分析

total = compute_sum(message) + compute_sum(key)
return total == signature

先分别求消息与密钥的权重和,再相加得到完整签名,最后与传入的 signature 做相等比较并直接返回布尔结果。

  • 时间复杂度O(len(message) + len(key)),只需各遍历一遍。
  • 空间复杂度O(1)(不含输入本身),除少量标量变量外无额外存储。
  • 正确性:比较运算 == 在 Python 中返回布尔值,恰好满足函数返回 True/False 的契约;两个 assertIs 断言会严格校验返回的就是布尔对象。

从仓库源码看这套挑战的完整运转机制

这道题并非孤立文本,freeCodeCamp 仓库为"每日编程挑战"构建了完整的数据链路,理解它能帮你把握题目的真实运行环境。

1. 挑战数据通过 API 按日期下发

每日编程挑战路由 定义了若干公开 GET 端点,其中最核心的是:

  • GET /daily-coding-challenge/date/:date —— 按 YYYY-MM-DD 日期获取当天挑战;
  • GET /daily-coding-challenge/today —— 获取美中时区当天的挑战;
  • GET /daily-coding-challenge/month/:month/daily-coding-challenge/all —— 按月或全量列出挑战的轻量信息(id、编号、日期、标题);
  • GET /daily-coding-challenge/newest —— 获取最新挑战日期。

值得注意的细节是:路由对"未来挑战"做了保护——在 date/:date 处理中,若请求日期晚于美中时区当天,会返回 404(Challenge not found.),保证挑战按时间逐步解锁;月份与全量接口同样以"日期 ≤ 今天"为查询条件。此外,路由还通过 fastify.Sentry?.metrics?.count 记录 dcc.challenge_vieweddcc.challenge_not_found 等指标,用于观测挑战的访问情况。

从该路由的单元测试 daily-coding-challenge.test.ts 的 fixture 可以看到,数据库中的 Python 挑战数据形如:python: { tests: [...], challengeFiles: [{ contents, fileKey: 'mainpy' }] },即每个挑战同时携带测试列表与初始代码文件(Python 文件键为 mainpy)。这也印证了本挑战 markdown 中 --seed-- 的 Python 代码最终会作为 challengeFiles 内容呈现给学习者。

2. 客户端对挑战数据做结构校验

API 返回的数据在客户端展示前还会经过 Joi 校验。daily-coding-challenge-validator.ts 中定义了 DailyCodingChallengeFromDb 接口与 validateDailyCodingChallengeSchema 校验函数,要求每个挑战必须包含 idchallengeNumber(正整数,≥1)、titledatedescription,且 javascriptpython 两个语言分支都必须具备 testschallengeFiles 字段。这意味着"Python 版"挑战(如本题)与"JavaScript 版"挑战在数据层完全平行,本挑战正是 Python 分支的一份具体实例。

3. 挑战 Markdown 的结构约定

从本文件可以看到,freeCodeCamp 挑战文档采用统一 frontmatter + 分段格式:

  • frontmatterid(全局唯一)、title(人类可读标题)、challengeType(29 表示每日编码挑战)、dashedName(URL 友好名);
  • --description--:题目描述与规则;
  • --hints--:每个 hint 是"说明文本 + 可执行测试",本挑战的测试统一走 runPython + unittest.TestCase
  • --seed-- / --seed-contents--:学习者看到的初始代码;
  • --solutions--:官方参考实现。

这种结构既便于人工阅读维护,也便于构建工具解析后注入数据库。仓库中的 daily-coding-challenge-validator.ts客户端页面 show-daily-coding-challenge.tsx 等文件共同完成了从数据库 → API → 前端编辑器 → 测试执行的闭环。

边界条件与常见陷阱

在实现本题时,有四个易错点值得重点检查:

  1. 非字母字符必须计 0,而不是报错或跳过索引"Is this valid?" 中的空格与问号、"Check out..." 中的逗号都是干扰项,若把它们误计为其他值会导致签名偏大、测试失败。
  2. 大小写是两套取值区间。大写字母从 27 开始,"freeCodeCamp" 中的两个 C"Rocks" 中的 R 都必须按大写规则取值,混用会导致 238 对不上。
  3. 签名不一致要返回 False。函数是"校验器"而非"生成器",输入 54210 这类错误签名时不能抛出异常,而要静默返回 False
  4. 返回真正的布尔值total == signature 本身即产生布尔值;若写成 return 1 if total == signature else 0,虽然 assertIs(..., True) 仍可能通过,但语义上不如直接返回布尔值清晰。

延伸思考:同板块的"字母求和"家族

daily-coding-challenges-python 板块内,与本题思路相近的挑战还包括 Challenge 63 "Battle of Words"、Challenge 204 "Sum the Letters"、Challenge 172 "Letters-Numbers" 等(见 板块结构定义)。它们共享"把字母映射为数值并聚合计算"的核心模式。掌握了 ord() - ord(base) + offset 这一通用偏移技巧后,你可以举一反三地解决这一整类题目;区别仅在于聚合方式是求和、比较双方大小,还是做进一步组合运算。

本文小结:Challenge 83 是一道典型的"编码规则 + 数值校验"入门题。通过字母区间判断与 ASCII 偏移映射,将字符串问题转化为纯数值比较;其官方解法以两个内嵌函数实现了清晰的关注点分离,复杂度为线性。结合仓库源码可见,这类挑战在 freeCodeCamp 中从 Markdown 文档到 API 下发、再到前端编辑器和 unittest 测试执行,已形成一套完整、自动化的每日编程训练闭环。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
docsdocs
暂无描述
Markdown
899
5.83 K
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.76 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
925
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.84 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
601
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
395
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.04 K
525