freeCodeCamp 每日编程挑战 364:用加权平均混合两桶 RGB 颜料(Between Two Buckets)
【免费下载链接】freeCodeCampfreeCodeCamp.org's open-source codebase and curriculum. Learn math, programming, and computer science for free.项目地址: https://gitcode.com/GitHub_Trending/fr/freeCodeCamp
本篇围绕 freeCodeCamp 每日编程挑战(Daily Coding Challenges)JavaScript 序列中的第 364 题 “Between Two Buckets” 展开:给定两桶各带 RGB 颜色与浓淡(fullness)等级的颜料,要求返回混合后的 RGB 颜色数组。读完本文,你将完整掌握这道题的输入输出契约、全部官方测试用例、加权平均混合的数学推导与参考实现,并能结合仓库源码了解该挑战在 freeCodeCamp 平台中作为dailyChallengeJs题型(challengeType 28)的承载方式。
题目描述:两桶颜料的加权混合
原题位于 curriculum/challenges/english/blocks/daily-coding-challenges-javascript/6a26df95efa55a2524399745.md,题面约定如下:
给定两桶颜料,每桶都有一个 RGB 颜色和一个浓淡等级(fullness),请返回混合后的 RGB 颜色,以三个整数组成的数组形式返回。
具体的数据结构约束:
- 每桶是一个对象(JavaScript)或字典(Python),包含两个属性:
color:长度为 3 的整数数组[r, g, b];fullness:0–100 之间的数值,表示该桶颜料的“量”。
- 混合颜色是两个颜色各自三个通道的加权平均,权重就是各自的 fullness,每个通道的结果四舍五入到最接近的整数。
也就是说,函数签名与核心计算式为:
mixPaint(bucket1, bucket2) // 对每个通道 c ∈ {r, g, b}: // mixed[c] = round( (bucket1.color[c] * bucket1.fullness + bucket2.color[c] * bucket2.fullness) // / (bucket1.fullness + bucket2.fullness) )官方测试用例(hints)全解析
原题通过 5 组断言用例定义了完整的行为契约,这里全部继承并逐组说明其考察点:
用例 1:等量混合,结果应为两色中点
assert.deepEqual(mixPaint({ color: [250, 250, 250], fullness: 50 }, { color: [0, 0, 0], fullness: 50 }), [125, 125, 125]);两桶 fullness 相等,权重各占 50%。以红通道为例:(250 * 50 + 0 * 50) / 100 = 125。三个通道对称,结果为[125, 125, 125]。这组用例验证“等权”时的简单平均行为。
用例 2:不等权混合,结果偏向浓的那一桶
assert.deepEqual(mixPaint({ color: [250, 250, 250], fullness: 80 }, { color: [0, 0, 0], fullness: 20 }), [200, 200, 200]);(250 * 80 + 0 * 20) / 100 = 200。权重 80:20 使结果明显偏向第一桶,验证了“加权”而非“平均”。
用例 3:两桶颜色完全相同时,权重不影响结果
assert.deepEqual(mixPaint({ color: [100, 150, 200], fullness: 30 }, { color: [100, 150, 200], fullness: 70 }), [100, 150, 200]);颜色相同时,无论 fullness 如何分配,加权平均仍等于该颜色本身。这组用例是对公式的恒等性质检验。
用例 4:非整除结果,考察四舍五入
assert.deepEqual(mixPaint({ color: [143, 143, 101], fullness: 45 }, { color: [100, 204, 204], fullness: 90 }), [114, 184, 170]);手算验证(total = 45 + 90 = 135):
- 红:
(143 * 45 + 100 * 90) / 135 = 15435 / 135 ≈ 114.33→ 114 - 绿:
(143 * 45 + 204 * 90) / 135 = 24795 / 135 ≈ 183.67→ 184 - 蓝:
(101 * 45 + 204 * 90) / 135 = 22905 / 135 = 170→ 170
注意绿通道是“向上舍入”(183.67 → 184),说明题面要求的“四舍五入到最接近整数”不是简单的截断取整。
用例 5:再一组非整除混合
assert.deepEqual(mixPaint({ color: [15, 134, 249], fullness: 29 }, { color: [97, 178, 55], fullness: 54 }), [68, 163, 123]);total = 29 + 54 = 83:
- 红:
(15 * 29 + 97 * 54) / 83 = 5673 / 83 ≈ 68.35→ 68 - 绿:
(134 * 29 + 178 * 54) / 83 = 13498 / 83 ≈ 162.63→ 163 - 蓝:
(249 * 29 + 55 * 54) / 83 = 10191 / 83 ≈ 122.78→ 123
这组用例同时覆盖了小数值通道(15)与大数值通道(249),并再次检验舍入方向。
起始代码与参考实现
原题给出的种子代码(seed)是一个占位实现,直接返回第一个桶:
function mixPaint(bucket1, bucket2) { return bucket1; }原题附带的官方参考解法如下:
function mixPaint(bucket1, bucket2) { const total = bucket1.fullness + bucket2.fullness; return bucket1.color.map((channel, i) => Math.round((channel * bucket1.fullness + bucket2.color[i] * bucket2.fullness) / total) ); }逐行拆解:
const total = bucket1.fullness + bucket2.fullness;—— 先求出总权重,作为加权平均的分母。fullness 的取值范围是 0–100,因此只要两桶不同时为 0,total 就在 1–200 之间,除法安全。bucket1.color.map((channel, i) => ...)—— 以第一桶的三个通道为基准做映射,同时利用i作为下标从bucket2.color[i]取出对应通道,实现两数组“按位配对”,无需手写zip。Math.round((channel * bucket1.fullness + bucket2.color[i] * bucket2.fullness) / total)—— 对每个通道计算加权和再除以总权重,并用Math.round完成题面要求的“四舍五入到最接近整数”。Math.round对.5一律向 +∞ 方向取整,而 5 组官方用例中均不出现精确.5的中间值,因此该行为在本题契约内没有歧义。
等价的展开式写法(不用map)同样成立:
function mixPaint(bucket1, bucket2) { const total = bucket1.fullness + bucket2.fullness; const [r1, g1, b1] = bucket1.color; const [r2, g2, b2] = bucket2.color; const f1 = bucket1.fullness, f2 = bucket2.fullness; return [ Math.round((r1 * f1 + r2 * f2) / total), Math.round((g1 * f1 + g2 * f2) / total), Math.round((b1 * f1 + b2 * f2) / total) ]; }两种写法在语义上完全一致;map版本的优点是天然遍历三个通道,未来若扩展到 CMYK 等更多通道只需改动颜色数组本身。
一个值得注意的边界:total 为 0
从源码结构看,题面将 fullness 约束为 0–100,官方解法没有对total === 0做防护——若两桶 fullness 都是 0,Math.round(NaN / 0 相关运算)会得到NaN。题面的 5 组用例都不触发这一分支,因此就本题契约而言无需处理;但这一细节恰好展示了“实现贴合测试契约”与“防御式编程”之间的取舍:在刷题场景以契约为界,在工程场景则应先判断分母。
这道题在 freeCodeCamp 平台中的承载方式
这道挑战不是一个孤立的算法题,而是 freeCodeCamp 每日编程挑战(Daily Coding Challenges)体系的一部分。结合仓库源码,可以确认以下事实:
- 题型编号:原文件 frontmatter 中
challengeType: 28。在 packages/shared/src/config/challenge-types.ts 中,常量dailyChallengeJs = 28(第 30 行),即本题属于“JavaScript 每日挑战”题型;相邻的dailyChallengePy = 29则是 Python 版每日挑战。 - 渲染与提交方式:同一文件中
viewTypes把dailyChallengeJs映射为'classic'(使用经典编辑器视图),submitTypes将其映射为'tests'(提交时运行断言测试判定通过)——这也解释了为什么原题的判定完全依赖assert.deepEqual形式的 hints 用例。 - 题型识别工具函数:
getIsDailyCodingChallenge(challengeType)通过判断类型是否属于[dailyChallengeJs, dailyChallengePy]来识别每日挑战(见 challenge-types.ts#L182-L188)。 - 所属内容块:该题属于结构块 curriculum/structure/blocks/daily-coding-challenges-javascript.json,对应题目前缀 “Challenge 364”,即 JavaScript 每日挑战序列中的第 364 题。
- 服务端接口:每日挑战的信息接口位于 api/src/daily-coding-challenge/(该 README 注明“Endpoints to get daily coding challenge info. Daily challenge submission still lives in the main part of the API.”)。
- 前端校验:客户端通过 client/src/utils/daily-coding-challenge-validator.ts 中的 Joi schema 校验从数据库取出的挑战数据,要求每种语言(javascript/python)都必须包含
tests(text+testString)与challengeFiles(fileKey+contents)数组——原题中的 seed 代码与 hints 断言正是以challengeFiles与tests的形态进入这条链路的。
对想在本地查看原题全文与全部用例的读者,直接打开 6a26df95efa55a2524399745.md 即可;题目、5 组测试、种子代码与官方解法都在同一文件的--description--、--hints--、--seed--与--solutions--区块中。
小结:这道题到底在考什么
把“Challenge 364: Between Two Buckets”抽象出来,它考察的是三个基础但高频的 JavaScript 能力:
- 加权平均的数学建模:把自然语言“按浓淡混合”翻译成
(a·w1 + b·w2) / (w1 + w2)的逐通道公式,并正确处理四舍五入(用例 4、5 专门验证了这一点); - 数组按位配对:用
map((channel, i) => ...)的下标技巧替代显式循环或zip,这是处理“两个等长数组按位运算”的惯用模式; - 与测试契约对齐:题面没有额外边界(如 total 为 0)的要求,实现以 5 组
assert.deepEqual用例为完整验收标准——这也是每日挑战类题目的典型形态:契约明确、判分基于断言,通过即完成。
掌握这一模式后,类似“按比例合并两组数值数组并取整”的题目(亮度混合、浓度配比、坐标插值等)都可以直接套用同一套加权平均骨架。
【免费下载链接】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),仅供参考