☰
freeCodeCamp 每日编程挑战解析:用 Python 实现 Pangram 判定函数
2026/10/17 7:09:15 网站建设 项目流程

freeCodeCamp 每日编程挑战解析:用 Python 实现 Pangram 判定函数

【免费下载链接】freeCodeCampfreeCodeCamp.org's open-source codebase and curriculum. Learn math, programming, and computer science for free.项目地址: https://gitcode.com/GitHub_Trending/fr/freeCodeCamp

本篇技术指南围绕 freeCodeCamp 开源仓库中「每日编程挑战(Daily Coding Challenges)」系列的Challenge 24: Pangram展开,完整讲解题目规则、8 组自动化测试用例、种子代码结构以及官方参考解法的逐行原理,并结合仓库中 API 路由、测试执行机制与课程结构文件,帮助读者掌握「字符串清洗 + 集合去重 + 有序比较」这一类经典判题思路,并能在本地用 unittest 独立复现验证。

一、挑战背景:什么是 Pangram 判定

Pangram(全字母短句)指一句话中包含字母表中每一个字母至少一次的文本,最著名的英文例子是"The quick brown fox jumps over the lazy dog."(快速棕狐跳过懒狗)。本题在此基础上做了一个变体:不要求使用完整的 26 个字母,而是给定一个任意的字母集合,判断输入文本是否"恰好使用"了该集合中的全部字母、且没有使用集合之外的字母。

这道题位于仓库课程结构daily-coding-challenges-python块的 Challenge 24: Pangram(挑战 id:6821ec02237de8297eaee79a),对应的挑战源文件为 6821ec02237de8297eaee79a.md。从课程结构文件可见,该块共包含数百道 Python 每日挑战(Challenge 1: Vowel Balance 一直到 Challenge 200+),本道题是其中典型的字符串处理 + 集合逻辑题目,challengeType: 29表明它是每日编程挑战类型的课程条目。

二、题目描述与两条核心规则

原题描述如下:

Given a word or sentence and a string of lowercase letters, determine if the word or sentence uses all the letters from the given set at least once and no other letters.

给定一个单词或句子,以及一串小写字母,判断该单词或句子是否至少一次用到了给定集合中的全部字母,并且没有使用其他字母。

题目明确了两条处理规则:

  1. 忽略文本中的非字母字符(Ignore non-alphabetical characters in the word or sentence),即空格、标点、数字等一律不参与判定;
  2. 忽略字母大小写(Ignore letter casing in the word or sentence),即Hello与hello、World与world视为相同字母。

这意味着判定过程是一个双条件逻辑:输入文本去重后的小写字母集合 == 给定字母集合。既不能缺少给定集合中的任何字母(all the letters at least once),也不能多出任何给定集合之外的字母(no other letters)。

三、8 组测试用例逐一拆解

题目的# --hints--小节用 Pythonunittest写入了 8 组判定用例,通过runPython在浏览器内执行并调用TestCase().assertIs(is_pangram(...), True/False)进行断言。全部用例整理如下:

输入 sentence输入 letters期望结果解释
"hello""helo"Truehello 只含 h/e/l/o,且四字母齐全
"hello""hel"False缺少字母o
"hello""helow"False多出字母w(hello 中没有 w)
"hello world""helowrd"True空格被忽略,h/e/l/o/w/r/d 恰好齐全
"Hello World!""helowrd"True大小写被忽略,感叹号被忽略
"Hello World!""heliowrd"False缺少l,且多出i
"freeCodeCamp""frcdmp"FalsefreeCodeCamp 含有给定集合之外的 e/o/a
"The quick brown fox jumps over the lazy dog.""abcdefghijklmnopqrstuvwxyz"True经典全字母短句,恰好用全 26 个字母

从这组用例可以归纳出测试设计者的三个考察点:

  • 缺失判定:第 2、6 组验证"给定集合中某个字母没有出现"时必须返回False;
  • 多余判定:第 3 组验证"文本出现了集合外字母"时必须返回False;
  • 清洗规则:第 4、5 组验证空格与标点被忽略,第 5 组同时验证大小写被忽略;第 8 组验证"恰好使用完整字母表"的全字母短句判定。

四、种子代码:你需要补全的函数骨架

题目在# --seed--中给出如下函数骨架:

def is_pangram(sentence, letters): return sentence

学习者需要在不改变函数签名is_pangram(sentence, letters)的前提下补全函数体,使其对上述 8 组用例全部返回正确布尔值。初始骨架直接return sentence,说明这里只是占位,等待学习者用逻辑替换。

五、官方参考解法逐行剖析

题目在# --solutions--中提供了官方参考解法:

import re def is_pangram(sentence, letters): used_letters = [] for char in sentence.lower(): if re.match(r'[a-z]', char) and char not in used_letters: used_letters.append(char) sorted_letters = ''.join(sorted(letters.lower())) sorted_used_letters = ''.join(sorted(used_letters)) return sorted_letters == sorted_used_letters

逐行拆解其核心思路:

  1. sentence.lower():先统一转小写,一次性满足"忽略大小写"规则,让后续正则只需匹配[a-z]一个区间;
  2. re.match(r'[a-z]', char):用正则逐字符过滤,只保留字母表中的小写字母,天然"忽略非字母字符"(空格、数字、标点全部被跳过)。注意这里使用re.match而非re.search,因为re.match从字符串开头匹配,对单个字符char而言二者等价,但语义上更直接;
  3. char not in used_letters:去重,保证同一个字母只记录一次——这正是"uses all the letters ... at least once"(至少一次)的落地点:出现多次与出现一次等价;
  4. ''.join(sorted(letters.lower()))与''.join(sorted(used_letters)):对给定集合与文本提取出的字母分别排序后拼接成字符串;
  5. return sorted_letters == sorted_used_letters:字符串相等比较,同时验证"不缺失"(文本集合是给定集合的超集方向)与"不溢出"(文本集合不包含给定集合之外的字母)。排序消除了字母出现顺序对比较结果的影响,把"集合相等"这一无序概念转化为有序字符串的相等判断。

复杂度分析

  • 时间复杂度:遍历sentence为 O(n)(n 为句子长度),去重时的char not in used_letters是列表线性查找 O(m)(m 为已收集字母数),排序为 O(k log k)(k ≤ 26),整体最坏约 O(n·m + k log k)。由于字母表最多 26 个字母,常数很小;
  • 空间复杂度:O(k),k 为去重后的字母数,最大 26。

六、更 Pythonic 的备选解法:集合(Set)视角

官方解法用"排序后字符串相等"模拟集合比较,而 Python 原生集合可以让这道题的语义更直白。由于字母集合天然无序,"集合相等"正是set == set的原生语义,可以写成:

import re def is_pangram(sentence, letters): used = set(re.findall(r'[a-z]', sentence.lower())) return used == set(letters.lower())

这里re.findall一次性提取全部小写字母并交给set()去重,set(letters.lower())把给定集合也转成集合,最后直接比较。这种写法与官方解法逻辑等价:同样先忽略大小写、忽略非字母、再去重,最后做集合相等判断,且省去了排序步骤,代码更短、语义更接近"uses all the letters ... and no other letters"的题面描述。

对学习者而言,两种解法都可以通过全部 8 组用例;官方解法胜在"不依赖集合的等值语义、纯字符串操作即可完成",集合解法胜在可读性。推荐理解官方解法后再尝试用集合重写,体会同一逻辑的不同表达。

七、测试是如何运行的:runPython 与 unittest 断言机制

题目中的每个 hint 都是一段嵌入 Markdown 的 JavaScript 代码,其核心是调用浏览器沙箱中的runPython:

({test: () => { runPython(` from unittest import TestCase TestCase().assertIs(is_pangram("hello", "helo"), True)`) }})

这段测试的含义是:

  • runPython(...)将 Python 代码字符串注入挑战环境执行;
  • from unittest import TestCase导入 Python 标准库单元测试框架;
  • TestCase().assertIs(is_pangram("hello", "helo"), True)断言函数返回值与期望的布尔值是同一个对象(assertIs检查身份等价,对布尔值而言即要求严格返回True/False,而非1/0等可隐式转换的值)。

这意味着学习者实现的is_pangram必须返回真正的布尔值。若写成return 1或返回字符串,assertIs会判定失败——这是本挑战在"严格返回类型"层面的隐性要求,也是初学 Python 时容易踩的坑。

八、这道题在 freeCodeCamp 项目中的位置

理解一道挑战如何"上线"到产品中,能帮助学习者把握整套课程的工程链路。从当前仓库源码可以确认以下事实:

1. 课程结构定义

挑战在 curriculum/structure/blocks/daily-coding-challenges-python.json 的challengeOrder中被登记为"Challenge 24: Pangram"(id:6821ec02237de8297eaee79a),该块配置了usesMultifileEditor: true、helpCategory: "Python"、blockLayout: "legacy-challenge-list",即学习者通过多文件编辑器完成 Python 每日挑战。

2. 每日挑战的数据发布接口

挑战内容(描述、hints、测试)经 tools/daily-challenges/seed-daily-challenges.ts 脚本从课程源文件灌入数据库(该脚本同时拉取 JavaScript 与 Python 两套挑战,并断言二者数量一致)。运行方式在 tools/daily-challenges/README.md 中有说明:复制sample.env为.env、启动客户端、进入tools/daily-challenges后执行pnpm seed-daily-challenges。

挑战数据落库后,由 API 侧的公开只读路由对外提供,实现在 api/src/daily-coding-challenge/routes/daily-coding-challenge.ts:

  • GET /daily-coding-challenge/today:返回今日挑战;
  • GET /daily-coding-challenge/date/:date:按YYYY-MM-DD返回指定日期挑战;
  • GET /daily-coding-challenge/day/:day:按MM-DD返回(用于跨年循环展示);
  • GET /daily-coding-challenge/month/:month:按YYYY-MM返回当月挑战列表;
  • GET /daily-coding-challenge/all:返回全部已发布挑战的摘要列表;
  • GET /daily-coding-challenge/newest:返回最新一条挑战的日期。

路由层对"未来挑战"做了屏蔽——例如date路由中,若查询日期晚于美国中部时区当日,则返回404 Challenge not found(源码注释还指出挑战数据截止于 2026-08-10)。日期相关的时区换算逻辑(getNowUsCentral、getUtcMidnight、getSourceDate等)集中在 api/src/daily-coding-challenge/utils/helpers.ts,其中getSourceDate会把任意日期映射回 2025-08-11 至 2026-08-10 的原始挑战周期,并将 2 月 29 日映射到 2 月 28 日。接口响应结构与校验定义在 api/src/daily-coding-challenge/schemas/daily-coding-challenge.ts:每条挑战包含id、date、challengeNumber、title、description,以及javascript与python两种语言的tests(text + testString)与challengeFiles。

3. 前端入口

客户端首页/地图页通过 client/src/components/daily-coding-challenge/widget.tsx 提供"前往今日挑战"与"挑战归档"两个入口按钮;client/src/components/daily-coding-challenge/helpers.ts 负责把当前美国中部时间格式化为MM-DD(含闰日映射),用于拼装挑战路由。

九、易错点与调试建议

  • 不要忽略去重:只判断"文本字母 ⊆ 给定集合"是不够的,还必须保证"给定集合的每个字母都至少出现一次"。忘掉去重直接做排序比较会在letters含重复字母时出错;
  • 不要忽略非字母字符:若不过滤空格与标点,"hello world"这类带空格的用例会直接失败;
  • 统一大小写时机:先lower()再过滤,否则re.match(r'[a-z]', char)会漏掉大写字母;
  • 返回布尔值而非其他真值:测试用assertIs(..., True),必须返回True/False字面量;
  • 本地快速验证:把函数定义与 8 组用例抄入本地文件,用python -m unittest或直接断言脚本跑一遍,即可脱离浏览器环境复现全部判定逻辑。

十、小结

Pangram 挑战看似简单,实则完整覆盖了字符串类编程题的四个经典环节:输入清洗(忽略非字母)、归一化(忽略大小写)、去重(至少一次)、集合比较(不多不少)。官方解法用"排序后字符串相等"实现集合语义,是纯字符串工具链的优雅示范;集合写法则更贴近题面语义。无论哪种解法,最终都要通过 8 组unittest断言。将解题逻辑与 API 路由、日期辅助函数 以及 课程结构文件 串联起来观察,就能看到一道题目从 Markdown 源文件到数据库、再到前端挑战页面的完整工程链路——这正是 freeCodeCamp 每日编程挑战模块的技术全貌。

【免费下载链接】freeCodeCampfreeCodeCamp.org's open-source codebase and curriculum. Learn math, programming, and computer science for free.项目地址: https://gitcode.com/GitHub_Trending/fr/freeCodeCamp

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询