freeCodeCamp 每日编程挑战 55:用 Python 实现恒星光谱分类(OBAFGKM)
【免费下载链接】freeCodeCampfreeCodeCamp.org's open-source codebase and curriculum. Learn math, programming, and computer science for free.项目地址: https://gitcode.com/GitHub_Trending/fr/freeCodeCamp
本篇技术指南围绕 freeCodeCamp 开源仓库中的每日编程挑战第 55 题展开:根据恒星表面温度(开尔文 K)判断其光谱分类,输出经典的"O"、"B"、"A"、"F"、"G"、"K"、"M"分类字母。文章将完整解析题目要求、七个温度区间划分、八个官方测试用例、种子代码与标准解法,并结合仓库源码说明每日挑战的存储、校验与评分机制。读完本文,你不仅能独立解出这道题,还能理解这类挑战题目在 freeCodeCamp 中从 Markdown 到数据库、再到前端评测的完整链路。
挑战背景:世界空间周与天文主题编码
本题位于 daily-coding-challenges-python 区块,是该系列的第 55 题(Challenge 55)。题目描述明确指出:10 月 4 日是世界空间周(World Space Week)的开始,接下来的七天每天都会带来一个天文主题的编程挑战,本挑战正是"空间周第一天"的恒星分类题。
在区块的challengeOrder中可以找到本挑战的登记信息:ID 为68c1a929005bf54d342aa8d2,标题为 "Challenge 55: Space Week Day 1: Stellar Classification",紧随其后的是空间周第二至第七天(Exoplanet Search、Phone Home、Landing Spot、Goldilocks Zone、Moon Phase、Launch Fuel)。由此可见,本题并非孤立的语法练习,而是 freeCodeCamp 用连续主题串联编程能力的教学设计:同一周内,你将从"恒星分类"一路写到"发射燃料计算"。
题目要求:温度到光谱类型的映射
题目给出一个恒星表面温度(单位:开尔文 K),要求返回该恒星的光谱分类。分类依据是经典的哈佛光谱分类体系(OBAFGKM),温度越高,字母越靠前,具体的区间划分如下:
| 分类 | 温度区间 (K) | 说明 |
|---|---|---|
"O" | 30,000 K 及以上 | 最热、最蓝的恒星 |
"B" | 10,000 K – 29,999 K | 蓝白色恒星 |
"A" | 7,500 K – 9,999 K | 白色恒星 |
"F" | 6,000 K – 7,499 K | 黄白色恒星 |
"G" | 5,200 K – 5,999 K | 黄色恒星(如我们的太阳) |
"K" | 3,700 K – 5,199 K | 橙色恒星 |
"M" | 0 K – 3,699 K | 最冷、最红的恒星 |
注意区间的边界处理:每个区间都是"下限包含、上限不包含"(除"O"无上限、"M"无下限)。例如 6,000 K 属于"F"而非"G",3,700 K 属于"K"而非"M",3,699 K 属于"M"。这些边界值恰恰是官方测试用例重点覆盖的对象。
从天文常识看,我们的太阳表面温度约为 5,778 K,落在 5,200 K – 5,999 K 区间,因此属于"G"型恒星——这也是本题测试用例的第一个断言。
函数签名与种子代码
题目要求实现名为classification的函数,接收一个参数temp(恒星表面温度,单位为 K),返回对应的分类字母(字符串)。种子代码位于原文档的--seed-contents--部分:
def classification(temp): return temp种子函数目前只是原样返回温度,你需要补充判断逻辑。挑战类型为challengeType: 29,即每日编程挑战(Daily Coding Challenge),与普通练习不同,这类题目在仓库中采用"JavaScript 与 Python 双语言"的结构——同一道题在 daily-coding-challenges-javascript 区块存在对应的 JavaScript 版本,本文聚焦 Python 解法。
官方测试用例:边界值与典型值的双重覆盖
--hints--部分给出了 8 个测试断言,它们共同验证了解法的正确性。每个测试都通过runPython调用 Python 的unittest.TestCase().assertEqual(...)来执行:
| 输入温度 (K) | 期望输出 | 测试意义 |
|---|---|---|
5778 | "G" | 太阳表面温度,典型 G 型恒星 |
2400 | "M" | 典型的红矮星 |
9999 | "A" | A 区间上边界(10,000 K 以下) |
3700 | "K" | K 区间下边界 |
3699 | "M" | 与上一条形成边界对照 |
210000 | "O" | 远超 30,000 K 的极端高温 |
6000 | "F" | F/G 分界边界值 |
11432 | "B" | 典型的 B 型恒星 |
这组用例的设计颇具匠心:既有5778、2400、11432这样的"典型值",也有9999、3700、3699、6000这样的"边界值",可以同时检验你的比较运算符是否用了>=而非>,以及区间是否出现缝隙或重叠。
在 freeCodeCamp 的评测体系中,hints中的testString会被解析为可执行的测试代码。从 api/src/daily-coding-challenge/schemas/daily-coding-challenge.ts 可以看到,每个挑战的测试由text(测试说明)与testString(可执行测试字符串)两部分构成;在 client/src/utils/daily-coding-challenge-validator.ts 中,DailyCodingChallengeFromDb结构也明确了tests数组会保存这两部分内容,用于前端评测时回放。
标准解法:一条 if/elif 链搞定七个区间
官方--solutions--给出的解法如下:
def classification(temp): if temp >= 30000: return "O" elif temp >= 10000: return "B" elif temp >= 7500: return "A" elif temp >= 6000: return "F" elif temp >= 5200: return "G" elif temp >= 3700: return "K" else: return "M"解法思路拆解
这个解法的精髓在于利用递减的阈值配合elif短路求值,将"区间判断"转化为"下界判断":
- 首先检查是否
>= 30000,命中即返回"O"; - 未命中说明
temp < 30000,此时再检查>= 10000,命中的自然落入 10,000 – 29,999 的"B"区间; - 依此类推,每个
elif都隐含了"已排除上方所有区间"的前提; - 最后所有条件都不满足时(
temp < 3700),落入else返回"M"。
由于 Python 的elif链从上到下依次短路求值,这种写法不需要显式写出每个区间的上界,也天然保证了区间不重叠、无缝隙,正确处理了题目中所有边界值:
9999:不满足>= 30000、>= 10000,但满足>= 7500,返回"A"✓3700:满足>= 3700,返回"K"✓3699:不满足任何elif,落入else,返回"M"✓6000:满足>= 6000,返回"F"✓
复杂度分析
该算法的时间复杂度为 O(1):最多执行 7 次整数比较;空间复杂度为 O(1),仅使用常数额外空间。由于区间数量固定(7 类),无论输入温度多大,判断次数都不随输入规模增长。
常见易错点
- 边界运算符选错:若将
>=写成>,则9999会落入"B"、6000会落入"G",导致测试失败; - 区间顺序颠倒:若从低温向高温判断(先判断
"M"),由于低温条件最先满足,高温恒星会被误判为"M"; - 返回类型错误:题目要求返回字符串,如
"G"而非整数或布尔值。
进阶思路:二分查找与查表法
虽然 if/elif 链是本题的最简解,但了解其他实现思路有助于应对"区间数量变大"的泛化场景:
二分查找法:将阈值与分类放入有序序列,用bisect定位温度所在区间:
import bisect def classification(temp): thresholds = [30000, 10000, 7500, 6000, 5200, 3700] classes = ["O", "B", "A", "F", "G", "K", "M"] # bisect_right 返回第一个大于 temp 的阈值下标,temp 恰好等于阈值时归入更热区间 idx = bisect.bisect_right(thresholds, temp - 1) return classes[min(idx, len(classes) - 1)]在区间数量固定的场景下,这种写法与 if/elif 链性能相当,但可读性因人而异;if/elif 链的优势是意图直白、无需引入bisect。
从 Markdown 到每日挑战:仓库中的完整链路
本挑战的 Markdown 文件只是"源头",每日编程挑战在实际运行中还经历以下环节(均可从仓库源码验证):
1. 区块定义:curriculum/structure/blocks/daily-coding-challenges-python.json 声明了该区块的属性——helpCategory: "Python"、blockLayout: "legacy-challenge-list"、usesMultifileEditor: true,并在challengeOrder中按顺序登记 300+ 道挑战,本挑战(ID68c1a929005bf54d342aa8d2)排在第 55 位。
2. 数据入库:tools/daily-challenges/seed-daily-challenges.ts 是每日挑战的种子脚本。它通过 GraphQL 从 dev-playground 超级区块抓取 JavaScript 与 Python 两套挑战数据,校验两者数量一致且等于EXPECTED_CHALLENGE_COUNT = 365后,以bulkWrite+upsert写入 MongoDB 的DailyCodingChallenges集合。每个挑战会被分配一个顺序递增的challengeNumber和按起始日期(2025-08-11 UTC)逐日递增的date字段,这正是"每日"挑战的时间来源。
3. 接口与校验:api/src/daily-coding-challenge/routes/daily-coding-challenge.ts 提供查询每日挑战的 API;api/src/daily-coding-challenge/schemas/daily-coding-challenge.ts 使用 Joi 校验数据库返回结构,要求每条挑战必须同时包含javascript与python两个语言对象,且每个对象都含tests与challengeFiles数组——也就是说,本道 Python 挑战的种子代码与测试字符串,最终会被打包进这个统一结构中。
4. 前端入口:client/src/components/daily-coding-challenge/widget.tsx 是用户在首页/学习地图上看到的"每日编程挑战"入口组件,提供"前往今日挑战"(按美国中部时间的月日生成链接)与"挑战归档"两个按钮;client/src/client-only-routes/show-daily-coding-challenge.tsx 则负责渲染具体的挑战页面并驱动评测。
5. 评测执行:挑战页面中的测试通过runPython将--hints--里的testString(如from unittest import TestCase; TestCase().assertEqual(classification(5778), "G"))注入 Python 运行时执行,用户代码的返回值与期望值逐一比对。这也是为什么每个 hint 都用unittest.TestCase().assertEqual包裹的原因——评测器只需要断言函数输出即可,无需关心用户如何实现。
在本地验证你的解法
你可以直接在本地 Python 环境(3.x 即可,无需额外依赖)中验证解法:
python -c " from unittest import TestCase def classification(temp): if temp >= 30000: return 'O' elif temp >= 10000: return 'B' elif temp >= 7500: return 'A' elif temp >= 6000: return 'F' elif temp >= 5200: return 'G' elif temp >= 3700: return 'K' else: return 'M' cases = [(5778, 'G'), (2400, 'M'), (9999, 'A'), (3700, 'K'), (3699, 'M'), (210000, 'O'), (6000, 'F'), (11432, 'B')] for temp, expected in cases: TestCase().assertEqual(classification(temp), expected) print(f'classification({temp}) = {classification(temp)!r} (期望 {expected!r})') print('全部 8 个测试通过') "如果输出显示 8 组断言全部通过,你的实现就与官方--hints--完全一致。
小结
本挑战用一道简洁的天文映射题,综合训练了 Python 条件判断的三个关键能力:
- 区间划分与边界处理:理解"下限包含、上限不包含"的区间语义,并正确选用
>=; elif链的短路求值:利用判断顺序消除冗余条件,写出无缝隙、无重叠的分支逻辑;- 面向测试的编码习惯:以官方 hints 中的边界用例为基准自查实现。
在仓库中,这道题对应的源文件位于 curriculum/challenges/english/blocks/daily-coding-challenges-python/68c1a929005bf54d342aa8d2.md,如果你想继续挑战同一主题系列,可以在同一目录下阅读空间周其余六天的题目(68c1a929005bf54d342aa8d3至68c497f3aaefc9fd9f1b0e25),把天文主题连续刷完。
【免费下载链接】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),仅供参考