news 2026/9/10 7:17:04

freeCodeCamp Python 每日挑战实战:S P A C E J A M 字符串空格变换(Challenge 4)详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
freeCodeCamp Python 每日挑战实战:S P A C E J A M 字符串空格变换(Challenge 4)详解

freeCodeCamp Python 每日挑战实战:S P A C E J A M 字符串空格变换(Challenge 4)详解

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

本文基于 freeCodeCamp 课程库中的 Python 每日编程挑战第 4 题「S P A C E J A M」挑战文档展开,完整讲解这道字符串变换题的题目要求、全部测试用例与官方解法,并结合仓库源码剖析challengeType: 29(dailyChallengePy)挑战是如何被定义、打包进课程区块、最终在浏览器中通过 Pyodide Worker 执行和测试验证的整条链路。读完你可以独立复现该题解法、理解 freeCodeCamp Python 挑战文件的 Markdown 结构化格式(description / hints / seed / solutions 四段式),以及测试代码中runPython沙箱的执行机制。

题目背景:daily-coding-challenges-python 区块中的第 4 题

该挑战位于课程仓库 daily-coding-challenges-python 挑战文件,其 Frontmatter 元数据为:

id: 681cb1afdab50c87ddb2e515 title: "Challenge 4: S P A C E J A M" challengeType: 29 dashedName: challenge-4

区块编排文件 daily-coding-challenges-python.json 中可以看到该挑战的登记位置:

{ "id": "681cb1afdab50c87ddb2e515", "title": "Challenge 4: S P A C E J A M" }

该区块整体配置了"helpCategory": "Python""usesMultifileEditor": true"blockLayout": "legacy-challenge-list",说明它属于 Python 帮助分类、在多文件编辑器中呈现的每日挑战列表。

关于challengeType: 29,在共享包 challenge-types.ts 中有明确定义:

const dailyChallengePy = 29;

同一文件中还声明了该类型的配套属性:[dailyChallengePy]: 'classic'(展示样式)、[dailyChallengePy]: 'tests'(测试面板形态)、[challengeTypes.dailyChallengePy]: 'python'(语言映射)。也就是说,challengeType: 29是 freeCodeCamp 专属的「Python 每日挑战」类型,与常规 Python 课程挑战(challengeTypes.python)在运行时走相同的执行路径,但在页面与进度逻辑上独立区分。

题目要求:三步字符串变换

题目原文(description 段)的要求是:

给定一个字符串,移除字符串中的所有空格,在每两个字符之间插入两个空格,将所有字母转换为大写,并返回结果。

  • 非字母字符保持不变(空格除外)。

拆解一下这条规则链,它实际上规定了三个必须按序执行的操作:

  1. 删空格:移除输入中的全部空白字符(包括连续多个空格),这是唯一的“删除”动作;
  2. 加空格:在剩余相邻字符之间插入两个空格作为分隔符;
  3. 转大写:仅字母字符变大写,标点、数字、@$&等非字母字符保持原样(大小写本身就不受影响)。

顺序有讲究:如果先插入空格再删空格,插入的空格会被误删;如果先转大写,结果虽然等价但不符合题目给出的操作顺序。官方解法采用的正是「先删、再插、最后大写」的顺序。

种子代码(seed 段)

挑战文档的 seed 段给学习者的初始代码是一个直接原样返回的存根函数:

def space_jam(s): return s

学习者需要在space_jam内部实现上述三步变换并return结果。

全部五个测试用例(hints 段)

挑战文档的 hints 段给出了 5 组断言,它们是本题的完整验收标准。这些测试通过runPython在沙箱中执行 Python 代码,调用unittest.TestCase().assertEqual校验返回值:

用例 1:常规混合大小写字符串

space_jam("freeCodeCamp")应返回"F R E E C O D E C A M P"

({test: () => { runPython(` from unittest import TestCase TestCase().assertEqual(space_jam("freeCodeCamp"), "F R E E C O D E C A M P")`) }})

用例 2:含大量前导、内部、尾随空格的字符串

space_jam(" free Code Camp ")应返回"F R E E C O D E C A M P"

({test: () => { runPython(` from unittest import TestCase TestCase().assertEqual(space_jam(" free Code Camp "), "F R E E C O D E C A M P")`) }})

注意:这个用例和用例 1 的期望输出完全相同——它专门验证「所有连续空格(无论出现在哪)都必须被彻底移除」这一规则。

用例 3:句尾标点

space_jam("Hello World?!")应返回"H E L L O W O R L D ? !"

({test: () => { runPython(` from unittest import TestCase TestCase().assertEqual(space_jam("Hello World?!"), "H E L L O W O R L D ? !")`) }})

用例 4:符号、数字与空格混合

space_jam("C@t$ & D0g$")应返回"C @ T $ & D 0 G $"

({test: () => { runPython(` from unittest import TestCase TestCase().assertEqual(space_jam("C@t$ & D0g$"), "C @ T $ & D 0 G $")`) }})

这个用例覆盖了@$&、数字0等非字母字符:它们不参与大小写转换,但作为普通字符参与「两空格分隔」。注意&两侧的原始空格被删除后,&自己也成为一个被分隔的字符。

用例 5:短语

space_jam("allyourbase")应返回"A L L Y O U R B A S E"

({test: () => { runPython(` from unittest import TestCase TestCase().assertEqual(space_jam("all your base"), "A L L Y O U R B A S E")`) }})

这里有一个值得注意的细节:文档的说明文字写的是输入"allyourbase"(无空格),而实际断言代码传入的是"all your base"(含两个空格)。两者经过第一步删空格后都归一化为allyourbase,因此结果一致——这也从侧面印证了「先删空格」是整个变换正确性的关键前提。

官方解法(solutions 段)逐行剖析

挑战文档 solutions 段给出的官方解法只有三行:

def space_jam(s): s = s.replace(" ", "") s = " ".join(s) return s.upper()

逐行对应题目三步:

代码对应操作说明
s.replace(" ", "")移除所有空格str.replace无第三个参数时会替换所有出现位置,一次清空前导、内部、尾随及连续空格
" ".join(s)插入两个空格分隔对字符串迭代时join遍历的是单个字符,因此for循环式的" ".join("abc")得到"a b c"
s.upper()全部字母转大写str.upper只影响字母字符,@$?!、数字等原样保留,精确满足「非字母字符保持不变」

C@t$ & D0g$为例走一遍执行过程:

  1. 删空格后:C@t$&D0g$
  2. join后:C @ T...(中间过程C @ t $ & D 0 g $
  3. upper()后:C @ T $ & D 0 G $,与用例 4 期望值一致。

从源码结构看,该解法没有用到任何第三方库,纯标准库三件套(replace/join/upper),时间复杂度为 O(n),n 为字符串长度——对这道每日挑战而言是最直接的实现路径。学习者也可以先upper()再变换,结果等价;测试只校验最终字符串,不约束内部步骤顺序。

边界行为推断

结合五组测试用例,可以推断出该题对边界输入的行为约定:

  • 空字符串"":三步操作均为恒等,期望返回""
  • 纯空格字符串" ":第一步删空后变为"",最终返回""(用例 2 中前导尾随空格被删除的行为推广到极端情况);
  • 连续多空格:与单个空格同等处理,全部删除,不会在结果中留下空位。

需要说明的是,这些行为是由题目规则与官方解法自然推导出来的,挑战文档本身只显式覆盖了上述五个断言。

挑战文件的四段式结构

这道题的 Markdown 文件展示了 freeCodeCamp Python 挑战的标准结构,对想理解课程仓库格式的人很有参考价值:

  1. Frontmatterid(24 位十六进制,与文件名一致)、titlechallengeTypedashedName
  2. # --description--:题目正文,渲染为学习者看到的题目描述;
  3. # --hints--:提示区,混排自然语言提示与内嵌的 JS 测试块({test: ...}形式,供测试面板执行);
  4. # --seed--## --seed-contents--:编辑器初始代码;
  5. # --solutions--:通过全部测试后的参考解法。

hints 中的{test: () => { runPython(...) }}块是 JS 代码,运行在挑战测试沙箱里;runPython是沙箱暴露给测试的全局函数,把一段 Python 代码字符串送入 Pyodide 解释器执行。在客户端源码 frame.ts 中可以看到其实现入口:

export const runPythonInFrame = function ( ... ) { void contentDocument?.__runPython(code); }

即最终调用 iframe 文档内部的__runPython,与学习者预览区共用同一套 Python 运行时。

运行时链路:从 challengeType 29 到浏览器里的 Pyodide

runPython之所以能在浏览器里直接跑 Python,依赖仓库中的一条完整执行链路:

  1. 挑战识别:execute-challenge-saga.js 的updatePreviewSaga会检查挑战类型,dailyChallengePy(即 29)与pythonmultifilePythonCertProjectpyLab一样走 Python 分支:

    if ( challengeData.challengeType === challengeTypes.python || challengeData.challengeType === challengeTypes.multifilePythonCertProject || challengeData.challengeType === challengeTypes.pyLab || challengeData.challengeType === challengeTypes.dailyChallengePy ) { yield updatePython(challengeData); }
  2. 代码投递:同文件的updatePython(L409-L421)先buildChallengeData组装源码,再调用 python-worker-handler.ts 中的runPythonCode,把{ type: 'run', code }消息 post 给 Web Worker:

    export function runPythonCode(code: { contents: string; editableContents: string; }): void { lastCodeMessage = code; getPythonWorker().postMessage({ type: 'run', code }); }

    该处理器还维护了lastCodeMessage:Worker 因忙碌或重置而停止监听后,客户端收到stopped消息会自动重放最后一次代码(L61-L68),保证学习者改动代码不会丢失执行。

  3. Worker 端执行:Worker 的源码是 python-worker.ts,它通过loadPyodide加载 Pyodide(WASM 版 CPython),并用Object.freeze(self)冻结 Worker 全局对象以防止学习者代码破坏运行时状态。每次run请求到达后,initRunPython会创建全新的 globals dict、注册自定义print/input(通过pyodide.registerJsModule('jscustom', ...)),然后在隔离命名空间里pyodide.runPython(pyCode, { globals })执行学习者代码。挑战文档里那些runPython(\from unittest import TestCase ...`)` 测试代码,最终就是在这套沙箱里被解释执行的。

这条链路说明:daily-coding-challenges-python 区块的每一个挑战(包括本文的 S P A C E J A M),其 hints 中的runPython断言都是在浏览器内真实执行的 Pyodide Python 环境里完成的,无需后端服务器参与测试。

小结

  • 本题的规则链是「删全部空格 → 相邻字符间插两个空格 → 字母转大写」,官方三行解法replace+join+upper精确对应三步;
  • 五个测试用例共同覆盖了:混合大小写、连续/首尾空格、句尾标点、符号数字混合、短语输入五类场景,其中用例 2 与用例 5 都隐式验证了「内部空格必须全部删除」这一关键规则;
  • challengeType: 29在 challenge-types.ts 中定义为dailyChallengePy,使该挑战在运行时与常规 Python 挑战共用 Pyodide 执行链路(python-worker.ts + python-worker-handler.ts),而在课程编排上作为独立的每日挑战列表区块呈现;
  • 挑战 Markdown 的「Frontmatter + description + hints + seed + solutions」四段式结构,是 freeCodeCamp 课程库挑战文件的标准写法,hints 中的 JS 测试块与runPython沙箱共同构成了挑战的验收机制。

【免费下载链接】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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 7:15:51

Python LSTM时间序列预测实战:状态管理与滚动预测

简介:本资源是一套完整可用的基于LSTM神经网络的时间序列预测实战代码包,面向人工智能初学者、数据科学学习者及需要快速落地时序建模任务的工程师。项目覆盖从原始数据清洗、特征工程构建、LSTM模型搭建与训练,到最终预测结果可视化全流程&a…

作者头像 李华
网站建设 2026/9/10 7:15:43

ESP32+STM32双MCU智能小车:CAN总线避障与WiFi图像直传实战

简介:本资源是一套完整的物联网毕业设计项目方案,面向嵌入式开发初学者与高校电子/自动化专业学生,聚焦智能小车多模态控制与跨平台图像传输实践。项目实现STM32主控小车的自动避障(三路超声波)与手动遥控(…

作者头像 李华
网站建设 2026/9/10 7:15:05

STM32F407多通道ADC+DMA实时采集原理与工程实践

简介:本资源是一套基于STM32F407的多通道ADC采集完整工程实现,面向嵌入式初学者与STM32F4系列开发者,解决模拟信号高效同步采样与CPU负载过高的典型问题。项目深度融合ADC多通道配置、DMA双缓冲传输及HAL库标准驱动框架,适用于温度…

作者头像 李华
网站建设 2026/9/10 7:13:13

SpringBoot+Android电子书阅读器毕设系统:设计与实现全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华