排行榜、背包整理、活动排名都靠 table.sort,但比较器写错一个等号,轻则排序结果错乱,重则抛出 invalid order function 让整段逻辑中断。排序陷阱的根源在比较器的数学性质:不合格的比较器是大多数排序事故的案底。
---错误的比较器:用 <= 破坏严格弱序
local bad = {}
for i = 1, 100 do bad[i] = math.random(1, 10) end
-- table.sort(bad, function(a, b) return a <= b end) -- 可能抛异常
---正确的比较器:严格小于
table.sort(bad, function(a, b) return a < b end)
---多字段排序:主分相同按时间次分
table.sort(records, function(a, b)
if a.score ~= b.score then
return a.score > b.score
end
return a.at < b.at
end)
比较器必须是严格弱序:相同元素比较必须返回 false,用 <= 写比较器在等值元素多时触发引擎的 invalid order function 异常。战力榜上大量同分玩家的场景正是等值高发区,多字段比较器用次级字段(达成时间)做决胜,同分的先后有了确定语义。
---Lua 的 sort 不保证稳定:同序元素相对位置会乱
---需要稳定排序时带上唯一序号做决胜键
local ranked = {}
for i, r in ipairs(submissions) do
ranked[i] = { seq = i, data = r }
end
table.sort(ranked, function(a, b)
if a.data.score == b.data.score then
return a.seq < b.seq -- 序号决胜,等效稳定排序
end
return a.data.score > b.data.score
end)
Lua 的 table.sort 不是稳定排序:分值相同的两条记录在两次排序里的相对顺序可能不同,排行榜刷新时名分相同的人名次跳动。稳定性的工程解法是带唯一序号做决胜键:录入时的 seq 让同分者按提交先后排列,稳定排序的语义自己实现。性能的边界:万级记录的排序 8 毫秒内完成,十万级以上的实时排序改增量维护(插入时按二分定位),排序的压力被摊薄到每次插入。
排序函数的属性测试:随机数据集上验证比较器的反对称与传递性,违规即拦截;排行榜的名次跳动率监控,无版本变更的名次异常跳动通常是排序回归。
比较器曾经用 <= 想把并列排在一起,大表排序直接抛异常中断结算,严格小于加次级决胜后修复。比较器里曾经调用了会修改表的业务函数,排序中途表被改,行为诡异难查,比较器内禁止副作用进编码规范。浮点分数直接比较曾经受精度噪声影响,0.1 加 0.2 不等于 0.3 的经典问题让同分判异,分数按整数万分比存储后干净。
排行榜的决胜规则写进产品文档:同分按达成时间、再同分按注册先后,规则的确定性是公信力。结算类的排序在事务快照上执行,排序期间的数据变动不干扰结果。排序的比较器单元测试进模板工程,属性测试的随机数据集每次构建重跑。
本文由 996 技术组基于 996 引擎官方知识库与浮生梦老师课程体系整理。团队长期从事传奇类引擎 Lua 后端逻辑、客户端界面与商业版本交付,内容以官方知识库与真实项目为出处,按版本持续修订。
全站技术干货持续更新:996 引擎 / Lua 实战帖,语法、参数与示例一篇讲透。进入文章地图 · 查看全部 →
实战应用:用在哪里 全服 400 多个 NPC 脚本在开服瞬间集中加载,引擎进程启动耗时 47 秒,值班期间重启就是 47 …
实战应用:用在哪里 行会跨图集合靠世界喊话,从比奇赶到沙巴克动辄三分钟,集合效率低。行会驿站给每个行会一个专属传送网络:驿站…
实战应用:用在哪里 跨服战场周日结算夜出了双事故:30 名玩家积分凭空翻倍,随后 12 名玩家积分变成负数。两条线索指向同一…
实战应用:用在哪里 烈火剑法点到一半想换逐日剑法,没有遗忘功能只能养小号,新手加点点废的投诉长期霸榜。技能遗忘与重修框架管三…
实战应用:用在哪里 战斗日志全量滚动,烈火剑法暴击、治疗、拾取一场下来上千条,列表越长滑动越卡。改造分两件事:内存里只留最近…
实战应用:用在哪里 夏季活动缺一个全服参与的轻竞速,选定龙舟:五人一队从白日门河道上游划向盟重码头,河道布设障碍与顺流带,全…