Ruby项目中简化获取对象数组属性最高/最低出现次数的方法
Ruby项目:简化获取最多/最少访问港口名称的函数
我正在开发Ruby项目,需要从FleetShip对象数组中分别获取指定日期下,出现次数最多、最少的ending_port对应的名称。目前功能已实现,但代码过于冗长,希望找到更简洁的实现方式。
现有代码
def most_visited_port(time) most_visited_port_name = "" most_visited = 0 ending_port_array = [] @ships.each do |ship| ending_port_array << ship.ending_port most_visited = ending_port_array.sort.max_by { |v| ending_port_array.count(v) } != ending_port_array.sort.reverse.max_by { |v| ending_port_array.count(v) } ? false : ending_port_array.max_by { |v| ending_port_array.count(v) } end @ships.each do |ship| if time.to_date === ship.time_arrived.to_date && ship.ending_port == most_visited most_visited_port_name = ship.ending_port_name end end pp most_visited_port_name end def least_visited_port(time) least_visited_port_name = "" least_visited = 0 ending_port_array = [] @ships.each do |ship| ending_port_array << ship.ending_port least_visited = ending_port_array.sort.min_by { |v| ending_port_array.count(v) } != ending_port_array.sort.reverse.min_by { |v| ending_port_array.count(v) } ? false : ending_port_array.min_by { |v| ending_port_array.count(v) } end @ships.each do |ship| if time.to_date === ship.time_arrived.to_date && ship.ending_port == least_visited least_visited_port_name = ship.ending_port_name end end pp least_visited_port_name end
FleetShip对象数组示例
[#<FleetShip:0x0000000108444450 @average_speed=46.02272727272727, @beginning_port=7, @beginning_port_name="Summermill", @distance=81.0, @ending_port=3, @ending_port_name="Seamont", @id=0, @ship_name="Alpha", @time_arrived=2016-06-12 08:05:36 -0500, @time_left=2016-06-12 06:20:00 -0500>, #<FleetShip:0x0000000108444400 @average_speed=32.01932579334578, @beginning_port=7, @beginning_port_name="Summermill", @distance=81.0, @ending_port=3, @ending_port_name="Seamont", @id=1, @ship_name="Sea Ghost", @time_arrived=2016-06-12 11:07:47 -0500, @time_left=2016-06-12 08:36:00 -0500>]
优化方案
核心思路
- 先筛选出指定日期的船只,避免处理无关数据
- 用Ruby内置方法高效统计端口出现次数,替代手动遍历计数
- 提取通用逻辑,消除两个方法的代码重复
- 处理空数据、并列端口等边界情况
优化后代码
# 通用方法:获取指定日期下访问次数最多/最少的港口名称 # mode 参数传入 :most 或 :least def visited_port_name(time, mode) # 筛选出当日到港的船只 daily_ships = @ships.select { |ship| ship.time_arrived.to_date == time.to_date } return nil if daily_ships.empty? # 统计每个ending_port的出现次数(Ruby 2.7+ 可用tally,低版本用注释里的写法) port_counts = daily_ships.tally { |ship| ship.ending_port } # 兼容Ruby 2.7以下版本: # port_counts = daily_ships.group_by { |ship| ship.ending_port }.transform_values(&:size) # 根据模式找到目标端口 target_port = case mode when :most port_counts.max_by { |_port, count| count }&.first when :least port_counts.min_by { |_port, count| count }&.first end # 返回对应的港口名称 daily_ships.find { |ship| ship.ending_port == target_port }&.ending_port_name end # 获取最多访问港口名称 def most_visited_port(time) pp visited_port_name(time, :most) end # 获取最少访问港口名称 def least_visited_port(time) pp visited_port_name(time, :least) end
处理并列端口的扩展
如果需要返回所有并列的港口名称(比如多个港口访问次数相同且都是最多/最少),可以修改通用方法:
def visited_port_names(time, mode) daily_ships = @ships.select { |ship| ship.time_arrived.to_date == time.to_date } return [] if daily_ships.empty? port_counts = daily_ships.tally { |ship| ship.ending_port } target_count = case mode when :most then port_counts.values.max when :least then port_counts.values.min end target_ports = port_counts.select { |_port, count| count == target_count }.keys # 去重后返回所有对应名称 daily_ships.select { |ship| target_ports.include?(ship.ending_port) }.map(&:ending_port_name).uniq end
内容的提问来源于stack exchange,提问作者DrSnipesMcGee21
相关产品推荐
相关产品推荐

