我如何安排泳道

时间:2013-06-24 11:29:48

标签: ruby database multithreading algorithm

我正在尝试将10000个事件安排到不同的车道。每个活动都有一个开始和结束日期。没有事件可以在车道上重叠。

    ======================================================================
Lane 1   [Event1]      [Event4       ]  [Event7   ]
    ======================================================================
    ======================================================================
Lane 2         [Event2]            [Event 5]      [Event 8]
    ======================================================================
    ======================================================================
Lane 3     [Event3        ]       [Event6]      
    ======================================================================
    ========time along x axis        >>>>>>>>>>>>>>>>>>>>>>>>>>

所以我的问题是有效地确定事件的适当通道。我从数据库中获取按开始时间排序的事件。我采取的第一种方法是每个车道都有一个last_end_time。对于每个新事件,我检查每个车道,如果事件的start_time在车道的last_end_time之前,我向下移动并检查下一个车道。如果我找不到可以装入它的车道,我会创建一条新车道。

class LaneManager
  def initialize
    @lanes = []
  end

  # Find the free lane given start and end of an event
  def nextFreeLane start_date, end_date
    @lanes.each_with_index do |lane, index|
      if start_date > lane.last_date
        lane.last_date = end_date
        return index
      end
    end
    lane = Lane.new
    lane.last_date = end_date
    @lanes << lane
    @lanes.length - 1
  end
end

class Lane
  attr_accessor :last_date
end
然而,这会遇到另一个问题。如果我有5000个具有相同开始和结束的事件,那么为了找到5001事件的插槽,我最终检查以前的5000个通道,依此类推......性能只会降低。

有关如何有效存储,查询事件的任何建议?我需要在网页上呈现它们。我有水平和垂直滚动来平移事件。对于垂直滚动,我告诉我的服务器 - 这些是我需要的通道(比如第5道到第10道)。对于水平滚动,我只使用新的时间窗口和所需的通道进行新查询,这基本上是新的事件集。

我的问题是垂直滚动,我需要有效地将所有事件插入正确的通道。如果我能够有效地做到这一点,那么我可以只查询我的服务器中的事件,如26-30。将不胜感激任何建议。

3 个答案:

答案 0 :(得分:2)

首先,将开始和结束时间一起倒入一组,然后按时间排序。对于每个结尾,找到最近的下一个开头,并建立一个链接。完成后,解开线程:取出第一个开头,将它从束中取出,连同与之链接的整个事件链(记住每个末端都链接到最近的开头)。重复此过程,直到捆绑中没有事件开始(因此没有事件)。

答案 1 :(得分:2)

我会将我的评论问题扩展为答案,因为样本数据太长了。

示例数据是:

create table events (
    id          serial primary key,
    startend    tsrange not null
);

insert into events (startend)
select tsrange(now()::timestamp(0) + i * interval '1 minute', now()::timestamp(0) + i * interval '1 minute 30 second' + (i % 6) * interval '10 second')
from generate_series(1,100) i;

一个简单的连接,例如:

with events as (
select  evt.id as evt_id,
        evt.startend as evt_startend,
        chk.id as chk_id,
        chk.startend as chk_startend
from    events evt
join    events chk on evt.startend && chk.startend
)
select  *
from events
order by evt_startend, chk_startend
limit 10;

产生如下集合:

 evt_id |                 evt_startend                  | chk_id |                 chk_startend                  
--------+-----------------------------------------------+--------+-----------------------------------------------
      1 | ["2013-06-24 15:03:27","2013-06-24 15:04:07") |      1 | ["2013-06-24 15:03:27","2013-06-24 15:04:07")
      2 | ["2013-06-24 15:04:27","2013-06-24 15:05:47") |      2 | ["2013-06-24 15:04:27","2013-06-24 15:05:47")
      2 | ["2013-06-24 15:04:27","2013-06-24 15:05:47") |      3 | ["2013-06-24 15:05:27","2013-06-24 15:07:27")
      3 | ["2013-06-24 15:05:27","2013-06-24 15:07:27") |      2 | ["2013-06-24 15:04:27","2013-06-24 15:05:47")
      3 | ["2013-06-24 15:05:27","2013-06-24 15:07:27") |      3 | ["2013-06-24 15:05:27","2013-06-24 15:07:27")
      3 | ["2013-06-24 15:05:27","2013-06-24 15:07:27") |      4 | ["2013-06-24 15:06:27","2013-06-24 15:09:07")
      4 | ["2013-06-24 15:06:27","2013-06-24 15:09:07") |      3 | ["2013-06-24 15:05:27","2013-06-24 15:07:27")
      4 | ["2013-06-24 15:06:27","2013-06-24 15:09:07") |      4 | ["2013-06-24 15:06:27","2013-06-24 15:09:07")
      4 | ["2013-06-24 15:06:27","2013-06-24 15:09:07") |      5 | ["2013-06-24 15:07:27","2013-06-24 15:10:47")
      4 | ["2013-06-24 15:06:27","2013-06-24 15:09:07") |      6 | ["2013-06-24 15:08:27","2013-06-24 15:11:27")
(10 rows)

正如您在上面所看到的,对于任何给定的事件,您可以轻松确定潜在的并发事件数(给定的右侧列),从而确定给定事件的潜在通道数。但是,它只是一个范围:对于右边的任何给定行ID,有多种可能将车道号播种到左侧。

使用某些窗口函数进行某种丑陋的查询可能会让您确定何时需要重置通道编号。问题是,重置为什么?...

了解后者的唯一方法是确定一个没有任何事件的时间点(在这种情况下,lane = 1)。对于任何其他时间点,你可以说最好的是有一条免费车道,或者你需要创建一条新车道。

因此,假设这是您的选项,我建议实际存储车道号,并使用触发器自动生成。在触发器中,对并发事件的简单查询会生成繁忙通道列表,允许您安全地选择或创建一个不忙的通道 - 如果在该时间点没有指定通道,则可以选择通道1。

您的架构看起来更像是这样:

create table events (
    id          serial primary key,
    startend    tsrange not null,
    lane        int not null
);

水平和垂直滚动的查询变得微不足道。

答案 2 :(得分:1)

这称为间隔分区或间隔图着色。请参阅示例here。您的方法几乎与最优算法相同,只是您需要从正确数量的通道开始,而不是根据需要添加它们。要有效地实现它,请保留一组当前空闲的通道(例如,作为平衡搜索树),并在间隔开始或结束时更新它。