首页
/ freeCodeCamp JavaScript 每日编程挑战解析:Challenge 25 "Vowel Repeater" 的题目规则、测试用例与源码级解法

freeCodeCamp JavaScript 每日编程挑战解析:Challenge 25 "Vowel Repeater" 的题目规则、测试用例与源码级解法

2026-09-07 17:25:08作者:魏侃纯Zoe

本文以 freeCodeCamp 课程仓库中的 JavaScript 每日编程挑战(Daily Coding Challenge)Challenge 25 "Vowel Repeater" 为主体,完整呈现这道"元音递增重复"题的题目规则、四组官方断言测试、种子代码与参考解法,并结合仓库中的挑战类型定义、课程结构与每日挑战服务源码,讲清楚这道题如何从 Markdown 挑战文件一路走到前端编辑器与测试运行链路。读完后可掌握"计数器 + 单遍扫描"的字符串变换写法、String.repeat 的边界行为,以及 freeCodeCamp 挑战文件的组织规范。

题目定位:每日编程挑战中的第 25 关

该挑战的完整定义位于 68adce01c0e1144d0a902956.md,其 frontmatter 元数据如下:

id: 68adce01c0e1144d0a902956
title: "Challenge 25: Vowel Repeater"
challengeType: 28
dashedName: challenge-25

其中 challengeType: 28 并非随意编号。在共享包 challenge-types.ts 中,28 被定义为 dailyChallengeJs(JavaScript 每日挑战类型,对应 29dailyChallengePy)。同一文件中还可以看到它对前端渲染与提交行为的影响:

  • viewTypesdailyChallengeJs 映射为 'classic',即使用经典代码编辑器视图;
  • submitTypes 将其映射为 'tests',即完成后通过运行挑战内嵌的 assert 测试来判定是否通过;
  • 辅助函数 getDailyCodingChallengeLanguage 会把它解析为 'javascript' 语言。

课程块层面的配置在 daily-coding-challenges-javascript.json 中:本挑战以 id: 68adce01c0e1144d0a902956、标题 "Challenge 25: Vowel Repeater" 登记在 challengeOrder 数组第 25 位(第 105–108 行)。该块还声明了 usesMultifileEditor: truehelpCategory: "JavaScript"disableLoopProtectTests: true 等属性,其中 disableLoopProtectTests 从源码结构看意味着这类挑战会关闭针对死循环的测试保护机制,属于每日挑战的通用配置。

题目规则:元音出现次序驱动的递增重复

原题的完整规则如下,理解每一条是写出正确实现的前提:

  1. 递增重复:给定一个字符串,返回一个新字符串,其中每个元音字母比"上一个遇到的元音"多重复一次。第一个元音保持不变;第二个元音连续出现 2 次;第三个元音连续出现 3 次,依此类推。
  2. 元音范围aeiou 五个字母,大小写均算元音。
  3. 原始元音保留原大小写:例如首字母为大写的 A,输出中第一个字符仍是 A
  4. 新增的重复部分一律小写:即"追加"出来的副本统一转小写。
  5. 非元音字符保持原样:大小写与位置都不变。

规则的关键点在于:重复次数只与"这是第几个元音"有关,与具体是哪个元音无关;且"保持不变"与"翻倍"之间的差别恰好是 1 次追加——这一点对应解法里 count 的初始值设计,下文会详述。

官方测试用例:四组断言完整覆盖边界

挑战文件 # --hints-- 一节给出了四组 assert 测试,这也是前端提交时代码必须全部通过的验收标准:

用例 1:普通小写句子

assert.equal(repeatVowels("hello world"), "helloo wooorld");

用例 2:混合大小写的驼峰词

assert.equal(repeatVowels("freeCodeCamp"), "freeeCooodeeeeCaaaaamp");

用例 3:全大写元音,验证"原始字符保留原大小写 + 追加部分小写"

assert.equal(repeatVowels("AEIOU"), "AEeIiiOoooUuuuu");

用例 4:长句子综合测试,元音计数跨单词连续累计

assert.equal(repeatVowels("I like eating ice cream in Iceland"), "I liikeee eeeeaaaaatiiiiiing iiiiiiiceeeeeeee creeeeeeeeeaaaaaaaaaam iiiiiiiiiiin Iiiiiiiiiiiiceeeeeeeeeeeeelaaaaaaaaaaaaaand");

这四组用例分别覆盖了:基础行为、大小写混合、纯大写元音、以及"元音序号贯穿整个字符串"这一最容易出错的全局计数场景。以用例 3 "AEIOU" 逐字符拆解:

字符 元音序号 输出 说明
A 第 1 个 A 保持不变
E 第 2 个 Ee 原始 E + 1 个小写 e
I 第 3 个 Iii 原始 I + 2 个小写 i
O 第 4 个 Oooo 原始 O + 3 个小写 o
U 第 5 个 Uuuuu 原始 U + 4 个小写 u

拼接即得 AEeIiiOoooUuuuu,与期望输出一致。再看用例 1 "hello world":元音依次为 e(1)、o(2)、o(3)、o(4),输出为 h e l l o o(空格的 lo 后追加 1 个 o)与 w o o o r l d(第 4 个元音 o 追加 2 个 o),得到 "helloo wooorld"

种子代码与挑战文件结构

挑战文件的 # --seed-- / ## --seed-contents-- 一节提供了编辑器中初始显示的模板代码:

function repeatVowels(str) {

  return str;
}

这是一个最简桩实现——直接原样返回输入。学习者需要将其改写为真正的转换逻辑。在 freeCodeCamp 的挑战文件规范中,# --description--# --hints--(内嵌测试)、# --seed--(初始代码)、# --solutions--(参考解答)是标准段落结构,前端会根据 challengeType 决定如何渲染这些段落:对 dailyChallengeJs 类型,编辑器显示 seed 代码,提交时执行 hints 中的测试。

参考解法逐行解析

仓库中 # --solutions-- 给出的参考实现只有 14 行:

function repeatVowels(str) {
  const vowels = "aeiouAEIOU";
  let count = 0;
  let result = "";

  for (let char of str) {
    result += char;
    if (vowels.includes(char)) {
      result += char.repeat(count).toLowerCase();
      count++;
    }
  }

  return result;
}

逐段拆解其设计:

  1. const vowels = "aeiouAEIOU":把大小写共 10 个字符拼成一个字符串,用 String.prototype.includes 做成员判断,一行同时满足规则 1(大小写都算元音)。
  2. let count = 0 的初始值是关键:题目要求"第 1 个元音保持不变",而解法里统一执行 char.repeat(count)。当 count 为 0 时,"x".repeat(0) 返回空字符串 "",恰好实现"零追加";命中元音后 count++ 自增。于是第 k 个元音追加 k-1 个副本,总出现次数为 k,与题目"第 2 个出现 2 次、第 3 个出现 3 次"完全对齐。这里 count 的语义是"已遇到的元音个数",而非"当前元音的序号"。
  3. for (let char of str) 而非 for...in 或下标遍历for...of 按码点迭代字符串,对包含 emoji 等多字节字符的输入也安全。
  4. result += char 先无条件追加原始字符:保证每个字符(含非元音)按原顺序、原大小写进入结果,直接满足规则 3 与规则 5。
  5. char.repeat(count).toLowerCase() 只作用于追加部分:原始字符 char 本身未被转换,因此大写元音的首字符保留大写;toLowerCase() 只把重复出来的副本变小写,精确实现规则 4。
  6. count++ 只在元音分支内执行:非元音字符不消耗序号,"I like eating ice cream in Iceland" 中跨 6 个单词的元音序号才能连续累加到 11、17、20 等。

整体复杂度为 O(n) 单遍扫描(n 为字符串长度),无额外数据结构,是最直观的线性解。

常见陷阱与边界行为

在实现这道题时,以下几个细节最容易导致测试失败:

  • repeat 的负数陷阱:如果误把 count 写成"先自增再使用"(即第 1 个元音也追加 1 次),"hello world" 会变成 "heello worlold" 之类的错误结果;反之若把初始值设为 1 却忘记调整语义,同样会整体偏移 1 次。对照官方用例 1 的 helloo(只有 1 个追加 o)可以快速自检序号是否正确。
  • toLowerCase 作用域:若写成 result += char.toLowerCase().repeat(count),原始元音大小写会被破坏,用例 2、3 均会失败。
  • 元音判断遗漏大写vowels 若只写 "aeiou",用例 3 全大写输入将原样返回,无法通过。
  • String.repeat 的语义count 为 0 返回空串是 ES6 规范行为,也是本解法"第 1 个元音不追加"能成立的语言基础;若传入负数会抛出 RangeError,本题中 count 从 0 递增,不会触发。
  • 字符串拼接效率:参考解法用 result += char 逐字符拼接,教学场景下足够清晰;处理超长文本时可考虑收集到数组再 join(""),但对本挑战的输入规模无实际影响。

从挑战文件到运行链路:仓库中的配套实现

理解这道题在仓库中的完整流转,有助于认识 freeCodeCamp 挑战体系的结构:

  1. 内容定义:挑战正文、测试、seed 与解法全部以 Markdown 形式存放在 daily-coding-challenges-javascript 目录,文件名即挑战 id。课程构建工具(见 challenge-parsercurriculum/src 下的 get-challenges.ts)会解析这些段落生成结构化数据。
  2. 块顺序块结构 JSON 通过 challengeOrder 固定 250+ 道挑战的呈现顺序,本挑战排在第 25 位,紧邻 Challenge 24 "Pangram" 之后。
  3. 类型分发:前端根据 challenge-types.ts 中的 viewTypes/submitTypes 映射,选择经典编辑器视图与 tests 提交模式。
  4. 每日挑战服务:API 侧提供一组公开只读路由(见 daily-coding-challenge.ts 及其 README 中"仅用于获取挑战信息,提交仍走主 API"的说明),包括按日期 /daily-coding-challenge/date/:date(YYYY-MM-DD)、按月日 /day/:day(MM-DD)、/today/month/:month(YYYY-MM)、/all/newest 六类端点,响应结构由 TypeBox schema(schemas/daily-coding-challenge.ts)约束,其中 singleChallengeResponse 同时携带 javascriptpython 两种语言的 tests 数组和 challengeFiles。日期解析、"不返回美国中部时间今天之后的未来挑战"等逻辑在 utils/helpers.ts 与路由内实现。
  5. 前端展示与端到端验证:客户端有专门的每日挑战页面 show-daily-coding-challenge.tsx,配套组件位于 daily-coding-challenge 组件目录,e2e 测试 daily-coding-challenge.spec.ts 覆盖其用户流程。
  6. 数据种子tools/daily-challenges 下的 seed-daily-challenges.ts 等脚本负责将挑战数据写入数据库的 dailyCodingChallenges 模型,供上述 API 查询。

小结

Challenge 25 "Vowel Repeater" 是一道用规则约束清晰、测试用例完备的字符串变换题:核心是"全局元音计数 + 单遍扫描 + 条件追加"三步,参考解法利用 String.repeat(0) 返回空串的特性,优雅地把"第一个元音不变"统一进了同一个表达式;大小写规则通过"原始字符原样保留、仅对副本 toLowerCase()"精确落实。仓库中从 Markdown 挑战文件、块结构 JSON、挑战类型映射到 API 只读路由与 e2e 测试的完整链路,则展示了这道题如何被组织、分发与验证。建议读者先在本地按四组断言复现解法,再对照官方用例 4 的长句输出逐字符核对元音序号,以巩固对递增重复逻辑的理解。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.16 K
2.78 K
kernelkernel
deepin linux kernel
C
34
18
docsdocs
暂无描述
Markdown
904
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
934
1.86 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
862
1.36 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.96 K
1.03 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.38 K
1.47 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
535
606
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
549
398
leetcodeleetcode
🔥LeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解
Markdown
77
23