前两个单元你学会了协作指挥和反转操作。这一单元,捕手 Senick 要在云峰上追踪一头传说中体型无与伦比的树妖——Burleous Majorus。要抓住它,Senick 得先做一件很"科学"的事:测量一群单位的平均大小,把平均值当作基线(baseline),再从中筛选出那些"超出平均水平"的目标。这正是数据分析的思想——先算出群体的整体水平,再据此判断个体是否突出。本单元的核心,就是求平均值与多条件筛选。
sum / length 计算平均值并把它封装成 average 函数;用 and 把"距离更近"和"大小高于平均值"两个条件组合起来做多条件筛选;在遍历中不断更新那个"最近且满足条件"的最佳元素;最后把选出的最佳目标对象返回给调用者。
Senick 不知道 Burleous Majorus 到底该有"多大"才算大。孤立地看一个数字没有意义——身高 3 米算大吗?如果周围的树妖平均身高就是 3 米,那它只是普通个头。所以正确的做法是先算出整个种群的平均大小,把它当作一条参考基线:凡是超过平均值的,才是"偏大"的候选。平均值把绝对数值变成了有意义的相对判断。
本单元所有关卡都遵循同一个"两步走"结构。第一步:遍历所有单位,求出它们某个属性(如 size)的平均值。第二步:再遍历一次,用这个平均值作为筛选标准,挑出满足条件(高于平均且距离最近)的最佳目标。理解这个"先统计、后决策"的两遍遍历结构,就抓住了整个单元的骨架。
求和是求平均的第一步,用的是编程里最经典的累加器模式(accumulator pattern):先建一个初始为 0 的变量 sum,然后遍历数组,把每个元素的目标属性一个个加到 sum 上。循环结束时,sum 里装的就是所有元素的总和。这个"变量+循环累加"的套路,是数据统计的基本功。
# 累加器模式:求数组中所有元素 size 的总和 def sumSize(burls): sum = 0 # 1. 初始化累加器 for i in range(len(burls)): # 2. 遍历每个元素 burl = burls[i] sum += burl.size # 3. 把它的 size 加进去 return sum # 4. 返回总和
sum = 0 一定要写在 for 循环之前。如果放进循环里,每一轮都会把 sum 重置为 0,最后只会剩下最后一个元素的值,得不到总和。
关卡里的数组元素都是单位对象,每个对象带着自己的属性(如 size、health、pos)。用 range(len(array)) 遍历时,i 是索引,array[i] 是第 i 个对象,array[i].size 才是它的大小。也可以先把对象取出到变量再访问,两种写法等价,后者更易读。
for i in range(len(burls)): burl = burls[i] sum += burl.size
for i in range(len(burls)): sum += burls[i].size
有了总和,求平均值就非常简单——用总和除以元素的数量。数量就是数组的长度,在关卡里写作 burls.length(标准 Python 里是 len(burls))。这一个除法,就把一堆数字浓缩成了一个代表"整体水平"的值。
# 平均值的最小例子 sum = 1 + 2 + 5 + 4 # 总和 = 12 average = sum / 4 # 4 个元素 → 平均值 = 3
把求和与相除合并进一个函数,就得到了通用的求平均值工具。它接收一个数组,内部用累加器求和,最后返回 sum / len(array)。这样主程序只要写一句 averageSize(yaks) 就能拿到平均大小,干净利落。这正是"睡眠样本"关卡要求你从头独立实现的函数。
# 通用的求平均大小函数(求和与相除合二为一) def averageSize(units): sum = 0 # 初始化累加器 for i in range(len(units)): unit = units[i] sum += unit.size # 累加每个单位的大小 return sum / len(units) # 总和 ÷ 数量 = 平均值
average 函数只需记住四句话——初始化 sum 变量;遍历每个元素;把元素的 size 加到 sum;返回 sum 除以数组长度。任何"求某属性平均值"的需求,都套这四步。
len(units) 为 0,除法会出错(除以零)。稳妥的做法是在相除前先判断 if len(units) > 0,否则返回 0 或跳过,避免程序崩溃。
"诱骗牦牛"关卡的挑战在于:好诱饵既要够大、又要够近。只看距离不行——可能选到一头很近的小牦牛,骗不到大树妖;只看大小也不行——最大的那头可能远在牛群深处,跑过去会激怒整群牦牛。所以要用逻辑运算符 and 把两个条件串成一个:只有同时满足"大小 > 平均值"且"距离 < 当前最近"的牦牛,才算合格候选。
在遍历牦牛的循环里,对每头牦牛先算出它的距离 yakDistance 和大小 yakSize,然后用一个 if 同时检查两个条件。只有当这头牦牛比目前记录的最佳更近,并且大小超过平均值时,才认为它是"更好的选择",进而更新记录。
# 用 and 同时检查"高于均值"和"更近"两个条件 yakDistance = hero.distanceTo(yak) yakSize = yak.size if yakSize > avgSize and yakDistance < closestDist: # 这头牦牛又大又近,是更好的候选 ...
yakSize > avgSize(大小要大于平均)而不是小于,否则会选到小牦牛。同样,距离要 < closestDist(越来越近)。把大于小于写反,是本关最常见的失误。
这其实是第一单元"打擂台求最值"的升级版。区别在于:现在擂主的更换不只看一个指标,还要先通过筛选条件。做法是设两个记录变量——bestYak = None(当前最佳)和 closestDist = Infinity(当前最近距离,初始设为无穷大)。遍历每头牦牛,若它同时满足两个条件,就让它当新擂主:更新 bestYak 和 closestDist。遍历结束,擂台上就是那头"高于平均、且在合格者中最近"的最佳牦牛。
# 条件擂台:找出高于均值且最近的最佳牦牛 bestYak = None closestDist = 9999 # 初始设成很大的距离 for i in range(len(yaks)): yak = yaks[i] yakDistance = hero.distanceTo(yak) yakSize = yak.size if yakSize > avgSize and yakDistance < closestDist: bestYak = yak # 更新最佳目标 closestDist = yakDistance # 更新最近记录
closestDist 必须比任何真实距离都大(如 9999 或 Infinity),这样第一头合格的牦牛就一定能"打赢"它、成功成为擂主。这和第二单元求最小血量设 99999 是同一个道理。
bestYak 和 closestDist 必须同时更新。只更新 bestYak 却忘了更新 closestDist,下一次比较用的还是旧距离,擂台机制就失灵了。
筛选的最终产物是一个单位对象(那头最佳牦牛),而不仅仅是它的大小或距离。把整个 bestYak 对象保存下来并返回,调用方才能对它下命令——比如 hero.say(bestYak) 标出它,或指挥单位移向 bestYak.pos。记住:我们要的是"哪一个",所以保存和返回的应当是元素本身。
closestDist 是一个数字,只用来比较;bestYak 是一个对象,是我们真正想要的结果。很多新手只记住了最小的距离值,却忘了同时记住"是哪头牦牛",导致循环结束后手里只有一个数字、找不到目标本体。
这一节聚焦每一关的策略、思路与算法设计,只展示用于说明的关键片段,完整通关程序请你自己动手完成。
Senick 要找传说中体型无与伦比的 Burleous Majorus,可他不知道"多大才算大"。本关的任务是趁树妖迁徙,测出这群树妖的平均大小并说出来,作为将来识别巨型树妖的参考基线。这是整个单元的起点——一切筛选都建立在"平均值"之上。
关卡把求平均值拆成了两个协作的函数。sumSize 负责用累加器模式遍历所有树妖、把它们的 size 累加成总和;averageSize 则调用 sumSize 拿到总和,再除以数量得到平均值。这种"求和归求和、相除归相除"的分工,体现了函数职责单一的思想。
# 关键片段:sumSize 求总和,averageSize 求平均 def sumSize(burls): sum = 0 for i in range(len(burls)): sum += burls[i].size # 累加每个树妖的大小 return sum def averageSize(burls): sum = sumSize(burls) # 借助 sumSize 拿到总和 return sum / len(burls) # 总和 ÷ 数量
while True 里先用 hero.findEnemies() 拿到树妖数组,调用 averageSize 得到平均值,再用 hero.say(avgSize) 说出来。这样每帧都会报告当前视野内树妖的平均大小。
sumSize 函数体是一个 pass,那只是"什么都不做"的占位符,提醒你这里需要自己填写累加逻辑。真正实现时要把 pass 替换成累加器代码。
要抓大树妖 Majorus,需要一头够分量的诱饵牦牛。本关的两难在于:诱饵必须大小高于平均值(够大才骗得到大树妖),但又不能选牛群深处那一头(跑太远会激怒整群牦牛)。所以最佳策略是——在所有"高于平均大小"的牦牛里,挑距离英雄最近的那一头。这就把"大小"和"距离"两个属性结合了起来。
算法分两步。第一步补全 averageSize,求出牦牛的平均大小 avgSize。第二步做条件擂台:设 bestYak = None、closestDist = Infinity,遍历每头牦牛,用 if yakSize > avgSize and yakDistance < closestDist 同时检查"够大"和"更近",满足就更新 bestYak 与 closestDist。最后 hero.say(bestYak) 标出这头最佳诱饵。
# 关键片段:在高于均值的牦牛中找最近的一头 avgSize = averageSize(yaks) bestYak = None closestDist = 9999 for i in range(len(yaks)): yak = yaks[i] yakDistance = hero.distanceTo(yak) if yak.size > avgSize and yakDistance < closestDist: bestYak = yak closestDist = yakDistance hero.say(bestYak)
if yakDistance < closestDist,很可能选中一头很近的小牦牛,Senick 就抓不到大树妖了。size > avgSize 这个条件绝不能省。
这是 Senick 大计划的最后一步。前两关你分别学会了"求和"和"求和+相除",本关不再给你任何脚手架——要求你从零开始、独立地写出完整的 average 函数,测出雪人的平均尺寸。这是对本单元核心技能的一次综合检验。
算法很清晰。第一步用 hero.findByType("yeti") 精确找出所有雪人(而不是所有敌人),存进 yetis。第二步套用求平均值的四步模板:初始化 sum = 0、遍历累加每个雪人的 size、返回 sum / len(yetis)。第三步用 hero.say(...) 说出平均尺寸。
# 关键片段:findByType 精确取雪人,再求平均 yetis = hero.findByType("yeti") # 只要雪人 def averageSize(yetis): sum = 0 for i in range(len(yetis)): sum += yetis[i].size return sum / len(yetis) hero.say(averageSize(yetis)) # 说出雪人平均尺寸
findByType("yeti") 能只挑出雪人,避免把其他单位也算进平均值里。当你只关心某一类单位时,findByType 往往比笼统的 findEnemies 更合适。
sum = 0 必须放在 for 循环之前,放里面每轮重置会只剩最后一个元素的值。average 函数要 return sum / len(array),只 return 总和就成了"求和"而非"求平均"。size > avgSize(大于平均),写成小于会选到小牦牛,骗不到大树妖。and 同时判断大小与距离,只看距离会选错目标。bestYak 和 closestDist 要一起更新,只更新一个会让擂台失灵。bestYak 这个对象本身,光有 closestDist 数字找不到目标。len 为 0,相除会报错,必要时先判断数组非空。findEnemies 会混入其他单位污染平均值。这一单元你和捕手 Senick 一起,把"数据分析"的思想带进了云峰狩猎!你掌握了用累加器模式遍历数组求和——从 0 开始、逐个把属性加进去;学会了用 sum / length 计算平均值,并把它封装成通用的 average 函数;懂得了用 and 把"大小高于平均"和"距离更近"两个条件组合起来做多条件筛选;通过条件擂台不断更新那个最近又合格的最佳目标,并把整个目标对象返回给调用者。在"寻找开始""诱骗牦牛""睡眠样本"三关里,你从测基线、到按均值筛诱饵、再到独立默写均值函数,一步步走完了 Senick 的完整计划。来回顾一下核心概念:
| 核心概念 | 关键点 | 对应关卡 |
|---|---|---|
| 遍历数组求和 | sum=0 起步,循环累加属性 | 寻找开始 |
| 函数分工 | sumSize 求和,averageSize 相除 | 寻找开始 |
| 计算平均值 | sum / len(array) | 三关 |
| 多条件筛选 | size > avg and dist < closest | 诱骗牦牛 |
| 条件擂台 | closestDist 初值设大,逐步刷新 | 诱骗牦牛 |
| 更新最佳目标 | bestYak 与 closestDist 同步更新 | 诱骗牦牛 |
| 返回对象 | 保存元素本身,而非只存数字 | 诱骗牦牛 |
| 独立写均值函数 | findByType + 四步模板 | 睡眠样本 |
懂得先测出群体基线、再用多个条件权衡取舍、最后精准锁定最佳目标的英雄,才是既有数据头脑、又有决断力的真正猎手。
继续向雪山之巅挺进吧,勇敢的程序员!🏔️📊🐂
扣哒世界 编程知识 · 计算机科学 6(安息云峰):第三单元
扣哒世界教研团队