freeCodeCamp 每日编程挑战第 310 题「British to American」:英式拼写转美式拼写的字符串替换完整解法
本篇技术指南围绕 freeCodeCamp 开源课程中 Daily Coding Challenge 第 310 题(Challenge 310: British to American)展开,完整解析题目要求、13 条英式/美式拼写对照表、大小写不敏感与派生词两条隐藏规则,并逐行剖析官方参考解法中基于正则与 replace 回调的替换原理。读者阅读本文后将掌握"基于映射表 + 全局忽略大小写正则 + 首字母大小写还原回调"这一类字符串规范化问题的通用解法,并了解该挑战在 freeCodeCamp 仓库中的题目组织、API 下发与数据库播种机制。
一、挑战定位:Daily Coding Challenge 生态中的一道字符串处理题
Challenge 310「British to American」是 freeCodeCamp 课程体系中 daily-coding-challenges-javascript 区块的成员。该区块共规划 365 道挑战(对应全年每日一题),每道题同时提供 JavaScript 与 Python 两个语言的实现与测试。Challenge 310 在区块中的登记记录位于 区块结构清单(id: 6a151d32d772271dd2448c2c,title: "Challenge 310: British to American"),其 challengeType 为 28(每日挑战类型)。
从仓库的实际运转机制可以了解这道题所处的完整链路:
- 题目正文以 Markdown 形式存放在 curriculum/challenges/english/blocks/daily-coding-challenges-javascript/ 下,文件名即挑战 id;
- 播种脚本 会从课程中拉取全部挑战数据(默认期望 365 道),从
2025-08-11起按天递增日期写入 MongoDB 的DailyCodingChallenges集合; - API 侧通过 daily-coding-challenge 路由 提供
/today、/date/:date、/day/:day、/month/:month、/all、/newest等 GET 接口下发题目,响应结构由 schemas/daily-coding-challenge.ts 定义,其中description(题目描述)、tests(测试用例)、challengeFiles(种子代码)均按javascript/python两个语言维度存储。
Challenge 310 正是在"按日期下发题目 → 用户在前端每日挑战界面作答 → 测试用例自动判定"这一机制中被消费的一环。而本文聚焦的,是这道题本身的算法内核:用一张固定映射表完成英式拼写到美式拼写的文本规范化。
二、题目要求全解:13 条对照表与两条关键规则
原题描述如下:给定一个句子,依据下方查找表,将其中所有英式英语拼写转换为对应的美式英语拼写,并返回更新后的句子。
完整对照表(原文 13 条,一条不少):
| 英式拼写 | 美式拼写 |
|---|---|
"colour" |
"color" |
"flavour" |
"flavor" |
"honour" |
"honor" |
"neighbour" |
"neighbor" |
"labour" |
"labor" |
"humour" |
"humor" |
"centre" |
"center" |
"fibre" |
"fiber" |
"defence" |
"defense" |
"offence" |
"offense" |
"organise" |
"organize" |
"recognise" |
"recognize" |
"analyse" |
"analyze" |
对照表覆盖了英语拼写差异的几大经典类别:-our → -or(colour/flavour/honour/neighbour/labour/humour)、-re → -er(centre/fibre)、-ce → -se(defence/offence)、-ise → -ize(organise/recognise/analyse)。理解这些词族规律有助于举一反三:即使将来映射表扩充,替换策略本身无需改变。
在对照表之外,题目还明确规定了两条容易被忽略的规则,它们是本题真正的考点:
- 替换必须大小写不敏感:例如
"Colour"(句首大写)应转换为"Color",而不是小写的"color"或漏掉不转换。 - 派生词也要跟着变:输入中可能包含以表中词根为基础扩展出的单词,同样需要转换。题目给出的两个显式例子:
"colouring"→"coloring"(词根colour+ 后缀-ing)"disorganised"→"disorganized"(前缀dis-+ 词根organise+ 后缀-d)
三、测试用例逐条拆解:从单词替换到复合句式
原题提供了 5 组测试断言(全部使用 assert.equal 做严格相等比较),它们是判定实现正确与否的唯一标准。逐条拆解如下:
用例 1:简单单数替换
assert.equal(britishToAmerican("I love the colour blue."), "I love the color blue.");
仅含一个命中词 colour,验证最基础的单次替换。
用例 2:-re → -er 词族
assert.equal(britishToAmerican("The fibre optic cable is new."), "The fiber optic cable is new.");
验证 fibre → fiber,且替换不影响句中其他单词。
用例 3:多个词同句命中 + 词族交叉
assert.equal(
britishToAmerican("It's an honour to meet someone with such humour."),
"It's an honor to meet someone with such humor."
);
honour 与 humour 同时出现在一个句子中,验证多次替换的累积效果,且 honour 前是冠词 an,替换结果 honor 不影响冠词本身。
用例 4:大写还原 + 派生词 + 多词组合
assert.equal(
britishToAmerican("The unrecognised artist analysed his colour palette at the centre."),
"The unrecognized artist analyzed his color palette at the center."
);
这一条同时检验三条核心能力:
unrecognised→unrecognized:派生词(前缀un-+ 词根recognise);analysed→analyzed:派生词(词根analyse+ 后缀-d);colour、centre:普通单次替换。
用例 5:压力测试——近乎全词表的大句子
assert.equal(
britishToAmerican("The offence analysed, with organisation, the defence centre and recognised that the neighbouring labouror was humourous, flavourful, and colourful."),
"The offense analyzed, with organisation, the defense center and recognized that the neighboring laboror was humorous, flavorful, and colorful."
);
这是最刁钻的一条,隐藏着三个关键细节:
neighbouring→neighboring:词根neighbour+ 后缀-ing;humourous→humorous:词根humour+ 后缀-ous,替换后变成humor+ous=humorous;organisation保持不变:因为对照表中没有organise的派生路径能匹配organisation(其词干是organis,不含organise这一连续子串),所以原句中的organisation被原样保留——注意预期输出中它没有被改成organization。
四、官方参考解法逐行剖析
题目的种子代码(# --seed--)为:
function britishToAmerican(sentence) {
return sentence;
}
种子函数接受一个字符串参数并直接原样返回,全部逻辑需要由解题者补充。官方参考解法(# --solutions--)完整如下:
function britishToAmerican(sentence) {
const lookup = {
colour: "color",
flavour: "flavor",
honour: "honor",
neighbour: "neighbor",
labour: "labor",
humour: "humor",
centre: "center",
fibre: "fiber",
defence: "defense",
offence: "offense",
organise: "organize",
recognise: "recognize",
analyse: "analyze"
};
let result = sentence;
for (const [british, american] of Object.entries(lookup)) {
const regex = new RegExp(british, "gi");
result = result.replace(regex, match => {
return match[0] === match[0].toUpperCase()
? american[0].toUpperCase() + american.slice(1)
: american;
});
}
return result;
}
这套解法由三个层次构成,每一层都精准对题目要求:
第一层:映射表数据结构(lookup 对象)
13 条英式拼写作为键、美式拼写作为值。由于键名都是合法 JavaScript 标识符(纯字母),可以直接使用对象字面量而非 Map。Object.entries() 按对象属性插入顺序返回键值对数组,因此替换顺序与表中排列顺序一致——在本数据集中各词根互不包含(除了单词内部的公共后缀如 -our),替换顺序不会造成冲突,但仍建议保持表序以提升可读性。
第二层:子串匹配而非单词匹配(new RegExp(british, "gi"))
这是整个解法中最关键也最容易被误解的设计决策。正则没有使用词边界锚点 \b,而是以 "gi" 标志做"全局 + 忽略大小写"的子串匹配:
g(global):替换句子中所有出现的该词根,而不是只替换第一个;i(ignoreCase):让colour能同时命中Colour、COLOUR、ColoUr等各种大小写形态,满足"替换应大小写不敏感"的规则一;- 不加
\b:使得colouring(内含colour)、disorganised(内含organise)、neighbouring(内含neighbour)、humourous(内含humour)这类派生词也能被命中,满足"基于词根的派生词也要转换"的规则二。
正因如此,解法才能在不显式枚举任何派生词的情况下,仅凭 13 个词根覆盖测试中出现的全部变体。需要指出的是,这种"无边界子串替换"是有意为之的取舍:它也会把任何意外包含该子串的单词一并替换(例如一个自定义专有名词恰好含 centre),但在本题目限定场景下,这正是实现规则二的最简手段。
第三层:replace 回调中的大小写还原
result = result.replace(regex, match => {
return match[0] === match[0].toUpperCase()
? american[0].toUpperCase() + american.slice(1)
: american;
});
回调函数的参数 match 是被命中的实际文本(保留了原文的大小写形态)。通过判断 match[0] === match[0].toUpperCase() 来识别"首字母是否为大写":
- 若首字母大写(如
Colour),则返回美式词的首字母大写 + 剩余部分("Color"); - 否则返回完整小写美式词(
"color")。
这个判断对全大写场景同样成立(COLOUR 的首字母 C 是大写,会得到 COLOR 的映射逻辑下的 "Color"),虽然原题测试未覆盖全大写输入,但该写法天然兼容。由于 replace 对每个匹配独立调用回调,同一句子中大小写混合出现(如 Colour 和 colour 并存)也能各自得到正确还原。
五、边界情况与易错点总结
结合测试用例与解法,可以提炼出本题的四个关键易错点:
| 易错点 | 说明 | 错误示范 | 正确做法 |
|---|---|---|---|
| 忘记大小写不敏感 | 只用 replace(british, american),Colour 无法命中 |
"Colour".replace(/colour/, "color") 无效果 |
正则加 i 标志 |
| 只替换第一个命中 | 忘了 g 标志 |
多词句只改第一处 | 正则加 g 标志 |
用了词边界 \b |
派生词被排除在外 | /\bcolour\b/ 匹配不到 colouring |
去掉 \b,做子串匹配 |
| 直接整体替换丢失大写 | 用 replace(regex, american) 把 Colour 变成小写 color |
用例 4 断言失败 | 用回调按命中文本首字母还原大小写 |
另外两个值得注意的细节:
organisation这类"差一个字母"的陷阱词:organisation含organis但不含organise,因此不会被转换。如果题目要求把organisation也转成organization,就需要额外补充词根或更复杂的规则;本题对照表没有它,所以保持原样才是正确输出。- 派生词的正确性依赖"替换结果 + 后缀拼接"恰好拼出合法美式词:
humour+ous→humorous、neighbour+ing→neighboring、analyse+d→analyzed,这些拼接之所以成立,是因为-our → -or、-re → -er、-ise → -ize的转换只作用于词根字母,不影响附加后缀。如果某个派生规则会改变词根尾字母(例如y → i类变换),则子串替换策略需要另行扩展。
六、延伸:挑战数据如何被验证与下发
Challenge 310 的测试用例并非只在课程文件中静态存在。从仓库实现可以梳理出它的完整生命周期:
- 定义阶段:题目 Markdown 中的
--hints--小节定义了判定用的测试字符串(即上文 5 组assert.equal),--seed-contents--给出用户作答起点,--solutions--给出参考答案;区块结构清单 daily-coding-challenges-javascript.json 登记了挑战 id 与标题。 - 播种阶段:seed-daily-challenges.ts 从课程中拉取挑战并按天写入数据库,脚本内置了 365 道的数量校验与固定起始日期(
2025-08-11)的防误改保护。 - 下发阶段:API 的 daily-coding-challenge 路由 通过 Prisma 查询
dailyCodingChallenges表,按日期返回挑战详情,且"不返回晚于今天(美国中部时间)的挑战";响应的description、tests、challengeFiles结构由 类型定义 约束,客户端还会用 daily-coding-challenge-validator.ts 中的 Joi 校验器对响应做二次校验。 - 作答阶段:前端 每日挑战小组件 提供"今日挑战"与"历史归档"入口,用户写出
britishToAmerican后由测试框架逐条执行--hints--中的断言判定通过与否。
七、总结
Challenge 310「British to American」是一个典型的"映射表 + 文本规范化"问题,但它的两个隐藏规则——大小写不敏感、派生词连带替换——把难度从简单的查表提升到了对正则匹配语义的准确理解上。官方解法的精髓在于刻意省略词边界 \b,用无边界子串匹配一次性解决所有派生词形态,再借助 replace 回调按命中文本首字母还原大小写。这一组合技巧同样适用于拼写纠错、术语统一、地区化文案适配等真实场景:只要把 lookup 换成任意键值映射表,函数骨架即可直接复用,是值得沉淀为模板的通用字符串处理范式。
掌握这道题后,读者不妨进一步研究仓库中同区块的相邻挑战(如 Challenge 311: Spellcaster、Challenge 114: Camel to Snake),它们在字符串变换思路上与本题目一脉相承。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0631
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
video-shotcraftAI宣传片skill,使用 Remotion 制作电影级产品视频:提供106 张镜头配方卡和可复用的视频魔板。适用于 Claude Code 与 Codex以及所有其他智能体Markdown00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python09
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00