Ruby基于参考数组对Struct结构体数组实现自定义顺序排序
Ruby 自定义按数组优先级排序的实现说明
报错根因
- 当调用
levels.index(e.level)查找Player的level在参考列表中的位置时,如果level值和参考数组中的字符串完全不匹配,index方法会直接返回nil,不会返回可参与排序比较的数值 - 你给这个返回值额外包了一层数组作为
sort_by的排序键,匹配不到时排序键就会变成[nil];排序过程中Ruby尝试比较两个包含nil的数组时,无法定义nil的大小关系,直接抛出comparison of Array with Array failed的异常 - 对照你的实际代码:参考数组里第一项是带横杠的
'level-1',但Edward存储的level值是不带横杠的'level1',两个字符串不相等,刚好触发了返回nil的分支,这就是代码当场报错的直接触发点
正确实现方案
基础兜底写法
不需要给排序键多余套数组,给匹配不到的level设置统一的兜底权重,避免出现无法比较的nil值:
# 匹配不到参考列表的level,统一排到结果末尾,权重设为无穷大 arr.sort_by { |e| levels.index(e.level) || Float::INFINITY }
注意:如果需要让匹配不到参考列表的level排到结果最前面,把兜底值改成-1或者-Float::INFINITY即可。单值排序不需要用数组包裹排序键,只有当你需要多级排序(比如先按level排、同level再按name排)时,才需要用数组形式的排序键。
性能优化写法
当Player数组规模较大时,每次循环都调用index遍历参考数组会产生不必要的性能消耗,可以提前把参考数组转成「level字符串-排序权重」的哈希,把查找效率从线性遍历降到常量时间:
# 预生成 level 对应的排序权重映射 level_weight = levels.each_with_index.to_h arr.sort_by { |e| level_weight[e.level] || Float::INFINITY }
适配你的预期输出
你期望把level为level1的Edward排在排序结果最前面,但参考数组里并没有存储这个level值,只需要给这个值设置比所有现有参考值更小的权重即可:
# 给level1单独设置权重0,原有参考数组的权重整体后移一位 level_weight = { "level1" => 0 }.merge(levels.each_with_index.to_h.transform_values { |idx| idx + 1 }) arr.sort_by { |e| level_weight[e.level] || Float::INFINITY }
运行上述代码就能得到你给出的预期排序结果。
内容的提问来源于stack exchange,提问作者Lucas
相关产品推荐
相关产品推荐

