【问题标题】:How do i arrange into swimlanes我如何安排进入泳道
【发布时间】:2013-06-24 11:29:47
【问题描述】:

我正在尝试将 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。将不胜感激任何建议。

【问题讨论】:

  • 您对 5000 个事件的说明不是 Schlemiel 的问题,而只是一个糟糕的且可能非常不可能的输入案例。
  • @BorisStitnicky 我当前的数据集给了我这个问题。那就是说我应该删除参考。它只是分散了主要问题的注意力。它仍然是低效的。有什么我可以做的快速排序算法吗?
  • 你的问题基本上可以用贪心法解决。唯一需要注意的是你所说的,如果你在同一时间范围内遇到了许多事件。
  • 是否可以随时存储车道,或者规格是否不合理?
  • @Denis 我不认为我可以在我走的时候弄清楚车道。我获取按开始时间排序的事件。由于我不希望向用户发送所有 10k 事件(只是在 UX 方面减慢它的速度),我正在寻找适合查看区域的内容 - 特别是他可以看到的大约 5 条车道和他选择的时间窗口。

标签: ruby database multithreading algorithm


【解决方案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,有多种可能性将车道号播种到左侧。

带有一些窗口函数的有点难看的查询可能会让您确定何时需要重置车道号。问题是,重置成什么?...

了解后者的唯一方法是确定一个完全没有事件的时间点(在这种情况下,车道 = 1)。对于任何其他时间点,您可以判断的最好的结果是有一条空闲通道,或者您需要创建一条新通道。

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

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

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

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

【讨论】:

  • 谢谢@Denis。我已经完成了基于车道的 SortedSet(按 last_event_end_time 和位置排序)的初始实现。我会看看情况如何。性能可以接受。不得不处理 Ruby 中一个讨厌的默认实现 SortedSet。会更新。非常感谢您的宝贵时间。
【解决方案3】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-10-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-19
    相关资源
    最近更新 更多