freeCodeCamp 每日编程挑战解析:Python 实现 Pounds to Kilograms 单位换算与格式化输出

原创2026-09-09 21:20:051,739 阅读
文章标签:教育前端后端

freeCodeCamp 每日编程挑战解析:Python 实现 Pounds to Kilograms 单位换算与格式化输出

本篇技术指南以 freeCodeCamp 开源仓库中「每日编程挑战(Daily Coding Challenges)」Python 模块的第 120 题 Challenge 120: Pounds to Kilograms(磅转千克)为讲解主体,完整还原题目描述、5 个自动化测试用例、种子代码与官方参考实现,并深入剖析 f-string 保留两位小数、名词单复数切换等核心技巧。读完本文,你将掌握如何在 freeCodeCamp 的 Python 挑战环境中写出通过全部 unittest 断言的健壮解法,并理解这类挑战在仓库中的运行机制与验证链路。

挑战概览:它在整个项目中处于什么位置

该挑战文档位于仓库的 daily-coding-challenges-python 块中,是该 Python 系列 200 多道每日一题中的第 120 题。从块配置可以看出该模块的关键属性:

  • challengeType: 29:Python 编码挑战类型(对应前端 ShowClassic 组件中的 challengeType: 29,见 show-daily-coding-challenge.tsx);
  • usesMultifileEditor: true:启用多文件编辑器,Python 侧使用 main.py 作为种子文件;
  • helpCategory: "Python":归入 Python 帮助分类。

仓库同时维护了同编号的 JavaScript 版本(daily-coding-challenges-javascript 块),并由 daily-challenges.test.js 通过 Vitest 自动断言两个语言版本在 id、标题、描述、测试数量上保持一一对应。也就是说,这道题不仅是一道独立练习,更是 freeCodeCamp「同题双语言」课程体系的一部分。

题目要求逐条拆解

原文档(691b559495c5cb5a37b9b480.md)给出了明确规格:实现函数 convert_to_kgs(lbs),给定一个以磅为单位的数值,返回字符串 "(lbs) pounds equals (kgs) kilograms.",并满足以下规则:

  1. "(lbs)" 处替换为输入的磅数(原样输出,不做格式化);
  2. "(kgs)" 处替换为换算后的千克数,四舍五入保留两位小数,且始终显示两位小数(例如 0.45、1.00、45.36);
  3. 换算系数固定为 1 磅 = 0.453592 千克;
  4. 当输入为 1 时,单位词用单数 pound,否则用复数 pounds;
  5. 当换算结果为 1 时,单位词用单数 kilogram,否则用复数 kilograms。

表面上是单位换算,实质考验的是三点:浮点数乘法的精度处理、字符串格式化的「补零」能力,以及基于最终显示文本而非数值本身做单复数判断的思维。

五个自动化测试用例:理解断言边界

原文档在 # --hints-- 段给出了 5 个测试,全部通过 runPython 注入 Python 的 unittest.TestCase().assertEqual 执行。逐一对照如下:

输入 lbs 期望返回值 考察点
1 "1 pound equals 0.45 kilograms." 单数 pound;0.453592 四舍五入为 0.45
0 "0 pounds equals 0.00 kilograms." 结果 0.0 必须显示为 0.00(两位小数补齐)
100 "100 pounds equals 45.36 kilograms." 常规换算:100 × 0.453592 = 45.3592 四舍五入为 45.36
2.5 "2.5 pounds equals 1.13 kilograms." 浮点输入与进位:2.5 × 0.453592 = 1.13398 → 1.13
2.20462 "2.20462 pounds equals 1.00 kilogram." 千克侧恰好为 1,使用单数 kilogram

注意最后一个用例:2.20462 × 0.453592 ≈ 1.00000,换算结果显示为 1.00 且单位取单数 kilogram——这是全题最容易遗漏的边界条件。输入侧 2.20462 与输出侧 1.00 都不是整数 1,但判定「是否为 1」必须基于格式化后的显示值,这正是官方参考实现的精妙之处。

种子代码与解题思路

挑战在编辑器中提供如下种子函数(main.py):

def convert_to_kgs(lbs):

    return lbs

种子只完成了「原样返回输入」,显然无法通过任何断言。解题的最小步骤是:

  1. 用换算系数 0.453592 计算千克值;
  2. 用格式化语法保留两位小数并强制补齐;
  3. 分别根据输入值和换算结果显示单复数单位;
  4. 按模板拼接完整句子。

官方参考实现逐步拆解

def convert_to_kgs(lbs):
    KG_PER_POUND = 0.453592
    kgs = f"{lbs * KG_PER_POUND:.2f}"

    pound_word = "pound" if lbs == 1 else "pounds"
    kilogram_word = "kilogram" if kgs == "1.00" else "kilograms"

    return f"{lbs} {pound_word} equals {kgs} {kilogram_word}."

逐行解读其中的三个关键设计:

① 常量命名与可读性 KG_PER_POUND = 0.453592 把魔法数字提取为语义化常量,对应题目「1 pound equals 0.453592 kilograms」的换算基准。虽然本题只使用一次,但显式常量让审阅者一眼看懂换算依据,也方便后续按比例调整。

② f-string 的 .2f 格式说明符 f"{lbs * KG_PER_POUND:.2f}" 做了三件事:执行浮点乘法、四舍五入到小数点后两位、不足两位时用 0 补齐。因此 0 * 0.453592 = 0.0 会显示为 "0.00",1 * 0.453592 ≈ 0.453592 会显示为 "0.45"(四舍五入),完美命中测试用例 1、2 的预期。这也是为什么结果被显式转成字符串 kgs 而不是保留 float——为下一步的字符串比较做准备。

③ 基于显示值而非数值的单复数判断

  • pound_word = "pound" if lbs == 1 else "pounds":直接比较输入数值,覆盖 convert_to_kgs(1) 返回 "1 pound ..." 的用例;
  • kilogram_word = "kilogram" if kgs == "1.00" else "kilograms":比较的是格式化后的字符串 "1.00" 而非浮点数 1.0。原因在于,当换算结果接近 1 时(如输入 2.20462),浮点计算值可能是 0.9999999... 或 1.0000001...,直接做数值比较会因浮点误差导致判断不稳定;而 .2f 格式化后统一收敛为 "1.00",字符串比较既精确又可读。

④ f-string 模板拼接 f"{lbs} {pound_word} equals {kgs} {kilogram_word}." 直接复刻题目要求的输出模板,输入值 lbs 保持原始形式(整数 1、100,浮点 2.5、2.20462 原样输出),与全部 5 个断言逐字符吻合。

本地复现与验证:超越编辑器直接跑通全部断言

原文档的断言依赖平台内置的 runPython,但你完全可以脱离平台在本地验证解法。将参考实现保存为 main.py,再手动复刻测试逻辑:

from unittest import TestCase
from main import convert_to_kgs

tc = TestCase()
tc.assertEqual(convert_to_kgs(1), "1 pound equals 0.45 kilograms.")
tc.assertEqual(convert_to_kgs(0), "0 pounds equals 0.00 kilograms.")
tc.assertEqual(convert_to_kgs(100), "100 pounds equals 45.36 kilograms.")
tc.assertEqual(convert_to_kgs(2.5), "2.5 pounds equals 1.13 kilograms.")
tc.assertEqual(convert_to_kgs(2.20462), "2.20462 pounds equals 1.00 kilogram.")
print("All 5 assertions passed.")

运行时只需在 main.py 所在目录执行 python main.py 即可看到全部断言通过。这也验证了题目在设计上的自洽性:5 个用例覆盖了整数/零/浮点输入、四舍五入进位、小数补零、两侧单复数共 5 类边界,是一道测试驱动设计的典型样例。

从源码看这道题如何被加载与执行

理解「为什么这样实现」后,不妨从仓库源码追溯这道题的完整运行链路:

  1. 内容来源:挑战 Markdown 文件(含描述、hints、seed、solution)位于 curriculum/challenges/english/blocks/daily-coding-challenges-python 目录,由 curriculum 的构建脚本解析为结构化数据;
  2. 前端加载:客户端通过 GET /daily-coding-challenge/day/{MM-DD} 从 API 拉取当日挑战数据(见 api/src/daily-coding-challenge/routes/daily-coding-challenge.ts 与 api/src/daily-coding-challenge/README.md);
  3. 数据校验:show-daily-coding-challenge.tsx 在渲染前用 daily-coding-challenge-validator.ts 校验响应结构;
  4. 挑战渲染:Python 版本被组装为 challengeType: 29、文件 main.py、helpCategory: 'Python' 的 challengeNode,交由经典的 ShowClassic 模板渲染(见 show-daily-coding-challenge.tsx);
  5. 测试执行:# --hints-- 中的断言通过 runPython 在浏览器内以 unittest.TestCase 运行,与你在本地执行的逻辑完全一致。

入口组件还支持语言切换(JavaScript / Python,记忆在 dailyCodingChallengeLanguage 存储键中),并且通过 helpers.ts 中的日期工具将「月-日」映射到当天挑战(如 2 月 29 日映射到 2 月 28 日),保证每日一题的稳定展示。

延伸思考:同一模式的其他挑战

磅转千克并不是孤立的一题。该 Python 块中还存在大量「单位换算 + 格式化输出」的姊妹题,例如 Challenge 113: Miles to Kilometers(英里转千米)与本体的差异仅在换算系数与输出模板,解题模式完全可复用;此外还有 Challenge 71: Tip Calculator、Challenge 77: Duration Formatter 等,都围绕「数值计算 + 严格文本格式」展开。刷题时只要掌握了「格式化字符串先行、再基于显示文本做判断」的思路,就能举一反三。

小结

Challenge 120: Pounds to Kilograms 是一道「小而美」的格式化编程题:它用 5 个断言把浮点精度、.2f 补齐、基于字符串的单复数判断全部串了起来。参考实现中「先 f"{...:.2f}" 再与 "1.00" 比较」的做法,是处理浮点边界问题的实用范式,值得在更复杂的单位换算、金额格式化场景中沿用。完整题目原文与官方解法可直接查阅仓库中的 691b559495c5cb5a37b9b480.md。

登录后查看全文
freeCodeCamp