-1

我有一个继承的 Ruby 脚本,它读取一个 csv 文件,其中包含具有这种格式的开始时间和结束时间的“电视节目”:

   start_time = 20:00:00
   end_time = 20:45:00

目标是根据开始和结束时间为每个电视节目分配一个“时隙”(以下值之一):

 23:00:00 - 05:00:00 = Late Night = l
 05:00:00 - 09:00:00 = Morning = m
 09:00:00 - 17:00:00 = Day Time = d
 17:00:00 - 20:00:00 = Evening = e
 20:00:00 - 23:00:00 = Prime = p

现在我有一个巨大的 if/else 语句,大约 100 行 Ruby 代码:

 if(start_time >= 50000 && start_time < 90000) #start time is between 5 and 9 am
      if(end_time <= 90000)
        @timeSlot = ["Morning"]
        puts "timeSlot = [Morning]"
      elsif(end_time <= 170000 && end_time > 90000)
        @timeSlot = ["Morning", "Daytime"]
        puts "timeSlot = [Morning, Daytime]"
      elsif(end_time <= 200000 && end_time > 90000 && end_time > 170000)
         @timeSlot =["Morning", "Daytime", "Evening"]
         puts "timeSlot =[Morning, Daytime, Evening]"
      elsif(end_time <= 230000 && end_time > 90000 && end_time > 170000 && end_time > 200000)
          @timeSlot =["Morning", "Daytime", "Evening", "Prime"]
          puts "timeSlot =[Morning, Daytime, Evening, Prime]"
      else
           @timeSlot =["Morning", "Daytime", "Evening", "Prime", "LateNight"]
           puts "timeSlot =[Morning, Daytime, Evening, Prime, LateNight]"
       end    
    elsif (start_time >= 90000 && start_time < 170000)
    .........
    ........


    end

我试图更改实现,以便代码易于维护、扩展和阅读。我对这个问题的第一次尝试是使用 excel 中的矩阵直观地解决它,如图所示。

在此处输入图像描述

这是直观显示的问题。现在的问题是如何以有效的方式在代码中做到这一点?

欢迎任何建议

4

5 回答 5

4

还有一个变种...

require 'time'
RANGES = [
  ["00:00:00", "Late Night"],
  ["05:00:00", "Morning"],
  ["09:00:00", "Day Time"],
  ["17:00:00", "Evening"],
  ["20:00:00", "Prime"],
  ["23:00:00", "Late Night"]]
NBR_PERIODS = RANGES.size
LAST_PERIOD = NBR_PERIODS - 1

class TimesToList
  def initialize
    @ranges = []
    RANGES.each { |r| @ranges << Time.strptime(r.first,"%H:%M:%S") }
  end

  def list_of_periods(start_time, end_time)  
    start_period = secs_to_period(Time.strptime(start_time, "%H:%M:%S"))
    end_period = secs_to_period(Time.strptime(end_time, "%H:%M:%S"))
    ((start_time <= end_time) ? (start_period..end_period).to_a :
      (start_period..LAST_PERIOD).to_a + (0..end_period).to_a).map {|p|
      p == 0 ? LAST_PERIOD : p}.uniq.sort.map {|p| RANGES[p].last}
  end

  private

  def secs_to_period(t) NBR_PERIODS.times {|i|
    return i if i == LAST_PERIOD or t < @ranges[i+1]}
  end
end

TimesToList.new.list_of_periods("23:48:00", "10:15:00")
  # => ["Morning", "Day Time", "Late Night"] 
于 2013-09-15T21:27:40.977 回答
2

我将假设这个问题“......以有效的方式在代码中执行此操作?” 是关于如何提出一个更优雅的解决方案来解决这个问题,而不是获得一个更有效的运行时算法。

首先,我注意到您的嵌套 if 语句包含冗余条件检查,例如

elsif(end_time <= 200000 && end_time > 90000 && end_time > 170000)

此条件成立的唯一方法是 end_time <= 200000 且 end_time > 170000,因此无需检查条件 end_time > 90000。您只需检查这些语句的每个条件语句的上限和下限.

其次,您还可以大大减少 if 语句的数量,方法是先将开始时间推入数组,然后再推入各自的结束时间,而不是为每个条件硬编码数组的值。以这段代码为例

@timeSlot = []
# for each record r in csv file
if(start_time >= 50000 && start_time < 90000)
    @timeSlot.push "Morning"
elsif (start_time >= 90000 && start_time < 170000)
    @timeSlot.push "Day Time"
....
end

if(end_time <= 170000 && end_time > 90000)
    @timeSlot.push "Daytime"
elsif ...

然后使用函数删除 @timeSlot 数组中的所有重复项。现在您将看到您正在动态生成数组,而不是对所有组合进行硬编码,这通常不是程序员有时间做的事情。

你可以做的另一件事是让你的代码随着时间的推移更易于维护,那就是不要使用硬编码的文字。对于每个重要的时间片,您应该有常量变量,例如

TIME_5AM = 50000
TIME_9AM = 90000
...

然后在 if 语句中使用这些变量。这将减少错字错误可能 5000 意外超过 50000 等。

希望这是朝着正确方向的有益推动。

于 2013-09-15T16:47:54.087 回答
1

虽然你的业务规则很少有人提到的缺点,但很有趣。这是我的处理方式。

require 'time'

start_time = "20:00:00"
end_time = "20:45:00"

start_time = Time.strptime(start_time,"%H:%M:%S")
end_time = Time.strptime(end_time,"%H:%M:%S")

slot_times = { :late_night => {start: "23:00:00", end: "05:00:00", slot: "Late Night"},
    :morning => {start: "05:00:00", end: "09:00:00", slot: "Morning"},
    :day_time => {start: "09:00:00", end: "17:00:00", slot: "Day Time"},
    :evening => {start: "17:00:00", end: "20:00:00", slot: "Evening"},
    :prime => {start: "20:00:00", end: "23:00:00", slot: "Prime"} }

slot_times.each do |k,v|  
  x,y = Time.strptime(v[:start],"%H:%M:%S"), Time.strptime(v[:end],"%H:%M:%S")
  puts v[:slot] if start_time.between?(x,y) || end_time.between?(x,y) #=> Evening, Prime
end
于 2013-09-15T18:45:13.727 回答
0
class Time_Slot < Struct.new(:name, :begin, :end)
  def overlap?(range)
    range.include?(self.begin) || range.begin.between?(self.begin, self.end)
  end
end

TIME_SLOTS = [
Time_Slot.new(:Late_night, 0, 5),
Time_Slot.new(:Morning, 5, 9),
Time_Slot.new(:Day_time, 9, 17),
Time_Slot.new(:Evening, 17, 20),
Time_Slot.new(:Prime, 20, 23),
Time_Slot.new(:Late_night, 23, 24)]

def calc_slots(start, stop)
  range = start...stop
  TIMESLOTS.select{|ts| ts.overlap?(range)}.map(&:name)
end

p calc_slots(1,20) #=>[:Late_night, :Morning, :Day_time, :Evening]
于 2013-09-15T18:51:43.017 回答
0

有几种方法可以优化代码库。

  • 用下划线书写数字(ruby 在数字中忽略它们,但它们增强了可读性)。5_00_00等于早上 5 点。这样更容易将小时与分钟分开。
  • 您可以为 a 中的每个时隙制定条件Proc,这为条件提供了一个可读的名称。
  • 电视节目的插槽数组是可变的。因此,如果条件适用,我们可以缓慢添加匹配时间。show.slots << 'Day Time' if <day time condition>. 这样我们就不需要深网化if

它尝试在以下清单中应用上述步骤:

# we need some overhead to define valid data for this example
require 'ostruct'
require 'pp'

shows = [
  OpenStruct.new(:start_time => 12_35_00, :end_time => 13_00_00, :slots => [], :name => 'weather stuff'),
  OpenStruct.new(:start_time =>  4_00_00, :end_time => 07_15_00, :slots => [], :name => 'morning show'),
  OpenStruct.new(:start_time => 18_45_00, :end_time => 20_15_00, :slots => [], :name => 'vip news'),
  OpenStruct.new(:start_time => 06_12_00, :end_time => 23_59_00, :slots => [], :name => 'home shopping')
]

# overhead done, the show can begin :)
def ranges_overlap?(a, b)
  a.include?(b.begin) || b.include?(a.begin)
end

time_slots = {
  :late_night => Proc.new {|s| ranges_overlap?(23_00_00..23_59_59, s.start_time..s.end_time) or ranges_overlap?(0_00_00..5_00_00, s.start_time..s.end_time) },
  :morning    => Proc.new {|s| ranges_overlap?(5_00_00..9_00_00,   s.start_time..s.end_time) },
  :day_time   => Proc.new {|s| ranges_overlap?(9_00_00..17_00_00,  s.start_time..s.end_time) },
  :evening    => Proc.new {|s| ranges_overlap?(17_00_00..20_00_00, s.start_time..s.end_time) },
  :prime      => Proc.new {|s| ranges_overlap?(20_00_00..23_00_00, s.start_time..s.end_time) }
}

shows.each do |show|
  time_slots.each do |name, condition|
    show.slots << name if condition.call(show)
  end
end

pp shows
# [#<OpenStruct start_time=123500, end_time=130000, slots=[:day_time], name="weather stuff">,
# #<OpenStruct start_time=40000, end_time=29504, slots=[:late_night], name="morning show">,
# #<OpenStruct start_time=184500, end_time=201500, slots=[:evening, :prime], name="vip news">,
# #<OpenStruct start_time=25216, end_time=235900, slots=[:late_night, :morning, :day_time, :evening, :prime], name="home shopping">]

ranges_overlap?我从这个 SO 讨论中借来了。

于 2013-09-15T16:44:31.033 回答