Algorithm to find middle of largest free time slot in period?

泄露秘密 提交于 2019-12-09 18:40:58

问题


Say I want to schedule a collection of events in the period 00:00–00:59. I schedule them on full minutes (00:01, never 00:01:30).

I want to space them out as far apart as possible within that period, but I don't know in advance how many events I will have total within that hour. I may schedule one event today, then two more tomorrow.

I have the obvious algorithm in my head, and I can think of brute-force ways to implement it, but I'm sure someone knows a nicer way. I'd prefer Ruby or something I can translate to Ruby, but I'll take what I can get.

So the algorithm I can think of in my head:

Event 1 just ends up at 00:00.

Event 2 ends up at 00:30 because that time is the furthest from existing events.

Event 3 could end up at either 00:15 or 00:45. So perhaps I just pick the first one, 00:15.

Event 4 then ends up in 00:45.

Event 5 ends up somewhere around 00:08 (rounded up from 00:07:30).

And so on.

So we could look at each pair of taken minutes (say, 00:00–00:15, 00:15–00:30, 00:30–00:00), pick the largest range (00:30–00:00), divide it by two and round.

But I'm sure it can be done much nicer. Do share!


回答1:


Since you can have only 60 events at maximum to schedule, then I suppose using static table is worth a shot (compared to thinking algorithm and testing it). I mean for you it is quite trivial task to layout events within time. But it is not so easy to tell computer how to do it nice way.

So, what I propose is to define table with static values of time at which to put next event. It could be something like:

00:00, 01:00, 00:30, 00:15, 00:45...



回答2:


You can use bit reversing to schedule your events. Just take the binary representation of your event's sequential number, reverse its bits, then scale the result to given range (0..59 minutes).

An alternative is to generate the bit-reversed words in order (0000,1000,0100,1100,...).

This allows to distribute up to 32 events easily. If more events are needed, after scaling the result you should check if the resulting minute is already occupied, and if so, generate and scale next word.

Here is the example in Ruby:

class Scheduler
  def initialize
    @word = 0
  end

  def next_slot
    bit = 32
    while  (((@word ^= bit) & bit) == 0) do
      bit >>= 1;
    end
  end

  def schedule
    (@word * 60) / 64
  end
end


scheduler = Scheduler.new

20.times do
  p scheduler.schedule
  scheduler.next_slot
end

Method of generating bit-reversed words in order is borrowed from "Matters Computational ", chapter 1.14.3.


Update:

Due to scaling from 0..63 to 0..59 this algorithm tends to make smallest slots just after 0, 15, 30, and 45. The problem is: it always starts filling intervals from these (smallest) slots, while it is more natural to start filling from largest slots. Algorithm is not perfect because of this. Additional problem is the need to check for "already occupied minute".

Fortunately, a small fix removes all these problems. Just change

while  (((@word ^= bit) & bit) == 0) do

to

while  (((@word ^= bit) & bit) != 0) do

and initialize @word with 63 (or keep initializing it with 0, but do one iteration to get the first event). This fix decrements the reversed word from 63 to zero, it always distributes events to largest possible slots, and allows no "conflicting" events for the first 60 iteration.


Other algorithm

The previous approach is simple, but it only guarantees that (at any moment) the largest empty slots are no more than twice as large as the smallest slots. Since you want to space events as far apart as possible, algorithm, based on Fibonacci numbers or on Golden ratio, may be preferred:

  1. Place initial interval (0..59) to the priority queue (max-heap, priority = interval size).
  2. To schedule an event, pop the priority queue, split the resulting interval in golden proportion (1.618), use split point as the time for this event, and put two resulting intervals back to the priority queue.

This guarantees that the largest empty slots are no more than (approximately) 1.618 times as large as the smallest slots. For smaller slots approximation worsens and sizes are related as 2:1.

If it is not convenient to keep the priority queue between schedule changes, you can prepare an array of 60 possible events in advance, and extract next value from this array every time you need a new event.




回答3:


Since you can't reschedule events and you don't know in advance how many events will arrive, I suspect your own proposal (with Roman's note of using 01:00) is the best.

However, if you have any sort of estimation on how many events will arrive at maximum, you can probably optimize it. For example, suppose you are estimating at most 7 events, you can prepare slots of 60 / (n - 1) = 10 minutes and schedule the events like this:

  • 00:00
  • 01:00
  • 00:30
  • 00:10
  • 00:40
  • 00:20
  • 00:50 // 10 minutes apart

Note that the last few events might not arrive and so 00:50 has a low probability to be used.

which would be fairer then the non-estimation based algorithm, especially in the worst-case scenario were all slots are used:

  • 00:00
  • 01:00
  • 00:30
  • 00:15
  • 00:45
  • 00:07
  • 00:37 // Only 7 minutes apart



回答4:


I wrote a Ruby implementation of my solution. It has the edge case that any events beyond 60 will all stack up at minute 0, because every free space of time is now the same size, and it prefers the first one.

I didn't specify how to handle events beyond 60, and I don't really care, but I suppose randomization or round-robin could solve that edge case if you do care.

each_cons(2) gets bigrams; the rest is probably straightforward:

class Scheduler
  def initialize
    @scheduled_minutes = []
  end

  def next_slot
    if @scheduled_minutes.empty?
      slot = 0
    else
      circle = @scheduled_minutes + [@scheduled_minutes.first + 60]
      slot = 0
      largest_known_distance = 0

      circle.each_cons(2) do |(from, unto)|
        distance = (from - unto).abs
        if distance > largest_known_distance
          largest_known_distance = distance
          slot = (from + distance/2) % 60
        end
      end
    end

    @scheduled_minutes << slot
    @scheduled_minutes.sort!
    slot
  end

  def schedule
    @scheduled_minutes
  end
end


scheduler = Scheduler.new

20.times do
  scheduler.next_slot
  p scheduler.schedule
end


来源:https://stackoverflow.com/questions/11760179/algorithm-to-find-middle-of-largest-free-time-slot-in-period

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!