自动安排会议

时间:2016-11-10 18:43:30

标签: ruby-on-rails

我正在尝试创建一个管理会议的Ruby on Rails网站。它应填充时隙,两者之间没有任何间隙。我已经达到了填补空位的程度。但在大多数情况下,它会留下一些空位。我无法在逻辑中找到流程。

应用程序/服务/ conference_service.rb

class ConferenceService
  def initialize(conference, temp_file)
    self.first_track = conference.tracks.first
    self.second_track = conference.tracks.last
    self.file = temp_file
    self.talks = []
  end

  def call
    create_talks
    set_track(1, 'Lunch')
    set_track(2, 'Lunch')
    set_track(1, 'Networking Event')
    # set_track(2, 'Networking Event')
    set_second_track_evening
  end

  private

  def create_talks
    file.read.split(/\n/).each do |line|
      next if line.blank?
      title = line.split(/\d|lightning/).first
      length = line.scan(/\d+/).first
      length = length.nil? ? 5 : length.to_i
      talks << Talk.create(title: title, length: length)
    end
  end

  attr_accessor :first_track, :second_track, :file, :talks

  def set_track(track_number, track_portion)
    track = track_number == 1 ? first_track : second_track
    time = track_portion == 'Lunch' ? Time.zone.now.change(hour: 9) : Time.zone.now.change(hour: 13)
    minutes = track_portion == 'Lunch' ? 180 : 240
    talks.shuffle!
    local_talks = []
    n = 0
    while local_talks.map(&:length).inject(0, &:+) < minutes
      local_talks << talks[n]
      n += 1
    end
    if local_talks.map(&:length).inject(0, &:+) == minutes
      local_talks.each do |talk|
        talk.start_time = time
        track.talks << talk
        time = time.advance(minutes: talk.length)
      end
      track.talks << Talk.create(title: track_portion, start_time: time, length: 60)
      track.save
      (0..local_talks.count - 1).each do |i|
        talks.delete_at(i)
      end
    else
      set_track(track_number, track_portion)
    end
  end      



   def set_second_track_evening
    time = Time.zone.now.change(hour: 13)
    talks.each do |talk|
      talk.start_time = time
      time = time.advance(minutes: talk.length)
    end
    second_track.talks << talks
    second_track.talks << Talk.create(title: 'Networking Event', start_time: time.change(hour: 17), length: 60)
  end
end

应用程序/控制器/ conference_controller.rb

  def create
    @conference = Conference.new(conference_params)
    build_tracks
    conference_service = ConferenceService.new(@conference, input_file)
    conference_service.call
    respond_to do |format|
      if @conference.save
        format.html { redirect_to @conference, notice: 'Conference was successfully created.' }
        format.json { render :show, status: :created, location: @conference }
      else
        format.html { render :new }
        format.json { render json: @conference.errors, status: :unprocessable_entity }
      end
    end
  end
  def input_file
    params['conference']['input_file']
  end

输入文件

Writing Fast Tests Against Enterprise Rails 60min
Overdoing it in Python 45min
Lua for the Masses 30min
Ruby Errors from Mismatched Gem Versions 45min
Common Ruby Errors 45min
Rails for Python Developers lightning
Communicating Over Distance 60min
Accounting-Driven Development 45min
Woah 30min
Sit Down and Write 30min
Pair Programming vs Noise 45min
Rails Magic 60min
Ruby on Rails: Why We Should Move On 60min
Clojure Ate Scala (on my project) 45min
Programming in the Boondocks of Seattle 30min
Ruby vs. Clojure for Back-End Development 30min
Ruby on Rails Legacy App Maintenance 60min
A World Without HackerNews 30min
User Interface CSS in Rails Apps 30min
调用set_track时出现

错误(2,&#39;网络事件&#39;)

undefined method `length' for nil:NilClass #line 42

1 个答案:

答案 0 :(得分:1)

建议您在担心算法之前先做一些事情:

  1. 单独关注/单一责任。解析文件的代码应独立于运行业务逻辑的代码,该代码应独立于保存到数据库的代码。对于简单的逻辑(也可能是)来说,分离这些东西似乎是不必要的,但随着应用程序复杂性的增加,必要

  2. 编写测试。当您重构代码时,您将希望确保它仍然有效。奖励:编写可以测试的代码会强制您创建可以理解的界面,这可以使代码更容易理解!

  3. 首先想出一个设计。阅读这段代码我不知道这些部分的意图是什么。我最喜欢的方法之一是使用班级,责任,合作者明信片(见https://en.wikipedia.org/wiki/Class-responsibility-collaboration_cardhttp://agilemodeling.com/artifacts/crcModel.htm)。

  4. 您似乎可以将此代码分解为:

    • 将输入文件解析为具有长度(以分钟为单位)和名称的通用“Talk”对象。我不会有这些DB支持。如果它与ActiveRecord模型的概念相同,我们通常将其命名为TalkDouble(或类似)。我还建议您只使用CSV而不是自己的自定义(并且难以解析)格式。
    • 将谈话对象安排到曲目中。看起来你正试图将谈话随机分成两条轨道,一些内置的午休时间(?)。无论期望的行为如何,除了普通的旧红宝石物体外,这也不需要使用任何东西。我发现最好让逻辑无状态/幂等,并在每次运行时返回一个新对象。

    例如:

    class TalkScheduler
    
      def schedule(talks, number_of_tracks: 2)
        # Logic goes here, returns an array of `Tracks`
        # each with a set of talks.
        tracks = build_tracks(number_of_tracks)
        talks.each do |talk|
          tracks.sample.add_talk(talk)
        end
        tracks
      end
    
      def build_tracks(number)
        (0..number).times.map do { Track.new }
      end
    end
    

    但是,如果你正在寻找一种能够在开放空间中选择“最适合”可用会话的算法,那么你实际上是在尝试解决背包问题(https://en.wikipedia.org/wiki/Knapsack_problem)。由于通话长度有限(例如只有30,45和60),它可能不会变得组合硬,但是你会意识到你正在进入一个充满挑战的领域。

    我也质疑任何人有能力以随机的谈话顺序创建会议而不是手工整理会议的价值。

    在任何情况下,您都可以解决在给定时间范围内确定(随机?)会话选择的问题,具体如下:

    class Schedule
      SLOT_LENGTH = 15
      attr_accessor :start, :length, :talks
      def initialize(start:, length:)
        @start = start
        @length = length
        @slots = length / SLOT_LENGTH
        @talks = []
      end
    
      def add_talk(talk)
        talks.push(talk)
      end
    
      def slots_remaining
        slots - talks.map(&:length).sum / SLOT_LENGTH
      end
    
      def can_fit?(talk)
        talk.length / SLOT_LENGTH <= slots_remaining
      end
    end
    
    class TalkScheduler
      def schedule(talks, schedules)
        unscheduled_talks = talks.dup.shuffle # Always dup, even if you don't shuffle
        schedules.each do |schedule|
          while(talks.any?)
            index = unscheduled_talks.index{|t| schedule.can_fit?(t) }
            break unless index
            talk = unscheduled_talks.delete_at(index)
            schedule.add_talk(talk)
          end
        end
      end
    end
    

    在决定将它们建模为会话或其他内容之前,我想更多关于模拟午餐,网络休息等等,但是使用这种类型的模式(存储由{{操纵的数据的简单ruby对象) 1}}包含复杂业务逻辑的类对我来说非常有帮助,可以简化处理复杂的工作流程,就像你在这里所做的一样。

    祝你好运!