3

我正在为一组几个场地开发一个预订可用性系统,并且很难生成给定月份中几天的时间块可用性。这发生在 PHP 的服务器端,但这个概念本身与语言无关——我可以在 JS 或其他任何东西中这样做。

给定一个venue_id、月份和年份(例如6/2012),我有一个在该地点范围内发生的所有事件的列表,表示为unix时间戳startend. 该数据来自数据库。我需要确定每天存在什么(如果有)最短长度的连续时间段(每个场地不同)。

例如,在 6 月 1 日下午 2:00 到 7:00 之间我有一个活动。最短时间为 5 小时,因此上午 9 点至下午 2 点和晚上 7 点至 12 点之间有一个街区开放。这将在六月的每一天持续到第二、第三等等。有些(大多数)日子根本没有发生任何事情,有些日子有 1 - 3 个事件。

我想出的解决方案有效,但生成数据也需要很长时间。基本上,我在每个月的每一天循环,并为当天的每 15 分钟创建一组时间戳。然后,我将当天事件的时间跨度循环 15 分钟,将任何“已采取”时间段标记为错误。剩下的,我有一个数组,其中包含空闲时间与所用时间的时间戳:

//one day's array after processing through loops (not real timestamps)
array(
  12345678=>12345678,   // <--- avail
  12345878=>12345878,
  12346078=>12346078,
  12346278=>false,      // <--- not avail
  12346478=>false,
  12346678=>false,
  12346878=>false,
  12347078=>12347078,   // <--- avail
  12347278=>12347278
)

现在我需要循环这个数组来找到连续的时间块,然后检查它们是否足够长(每个场地都有一个最小值),如果是,那么为它们的开始和结束建立描述性文本(即上午 9 点 - 下午 2 点) . 哇!当所有这些循环完成时,用户已经厌倦了,并徘徊在 Youtube 上观看小狗的视频;这样检查需要很长时间,大约需要 30 天。

有没有更快的方法来解决这个问题?总结这个问题,给定 d 天的时间范围t1t2如何确定d中剩余的剩余时间比最小时间块m长。

当用户在日历月之间移动时,这些数据通过 AJAX 按需组装。结果是按页面加载缓存的,因此如果用户第二次进入 7 月,第一次生成的数据将被重用。

任何其他有帮助的细节,请告诉我。


编辑

根据请求,数据库结构(或此处相关的部分)

*events*
id        (bigint)
title     (varchar)

*event_times*
id        (bigint)
event_id  (bigint)
venue_id  (bigint)
start     (bigint)
end       (bigint)

*venues*
id        (bigint)
name      (varchar)
min_block (int)
min_start (varchar)
max_start (varchar)

事件总是在 15 日开始 -- :00, :15, :30, :45

一些实际时间戳的数据转储:http: //pastebin.com/k1PRkj44

4

2 回答 2

1

这应该会让你朝着正确的方向前进(我希望如此)。它遍历一个时期(例如一个月)内的数据库记录。

从该集合中,它将找到预订之间的“空白”并填充一个数组(以日期为键)。

$days = array();

$stmt = $db->prepare('SELECT
    DATE(FROM_UNIXTIME(start)) AS sdate,
    GROUP_CONCAT(HOUR(FROM_UNIXTIME(start)),",", MINUTE(FROM_UNIXTIME(start)) ORDER BY start ASC SEPARATOR ",") AS from_hours,
    GROUP_CONCAT(HOUR(FROM_UNIXTIME(end)), ",", MINUTE(FROM_UNIXTIME(end)) ORDER BY start ASC SEPARATOR ",") AS to_hours
    FROM event_time
    WHERE start >= ? AND end < ? AND start < end
    GROUP BY sdate
    ORDER BY sdate');

$stmt->execute(array($from, $to));
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
    // from and to are formatted as: [hh,mm,hh,mm,hh,mm,...]
    $from = explode(',', $row['from_hours']);
    $to = explode(',', $row['to_hours']);

    // skew the two arrays:
    // - add 00:00 in the front of $to
    // - add 23:59 at the back of $from
    array_unshift($to, 0, 0);
    array_push($from, 23, 59);

    for ($i = 0, $n = count($from); $i != $n; $i += 2) {
        // create time values
        $start = new DateTime("{$to[$i]}:{$to[$i+1]}");
        $end = new DateTime("{$from[$i]}:{$from[$i+1]}");

        // calculate difference
        $diff = $start->diff($end);
        // difference must be positive and at least 5 hours apart (depending on venue)
        if (!$diff->invert && $diff->h >= 5) {
            $days[$row['sdate']][] = array($start->format('H:i'), $end->format('H:i'));
        }
    }
}

最后,$days 将包含:

[2012-06-30] => Array
    (
        [0] => Array
            (
                [0] => 00:00
                [1] => 05:30
            )

        [1] => Array
            (
                [0] => 11:30
                [1] => 23:59
            )

    )

您应该更改一些变量以进行计算:

  1. 最短时间(例如从早上多早)
  2. 最长时间(例如直到深夜)
  3. 最短预订时间(取决于场地)

此外,结果数组中缺少的键在一整天内都可用,因此您应该$days在开始循环之前为您查询期间内的所有日期准备数组。

让我知道这是否对您有帮助:)

于 2012-06-07T08:13:37.940 回答
0

创建可用时间列表。每个条目都有一个开始时间和一个结束时间。从一个从开始到结束的条目开始。从数据库中读取使用时间。如果一个落在现有条目的开头或结尾,请适当缩短它。如果它落在中间,你必须缩短一个并添加一个新的(覆盖相同的时间但中间有一个间隙)。这使您不必在长达数小时的活动中查看 15 分钟的时段。如果您的插槽变为 5 分钟而不是 15 分钟,它仍然可以工作。

一旦您阅读了数据库,您就会在一个按时间顺序排列的列表中找到所有空闲时间。您可能还希望将它们放在按大小排序的单独列表中。

链表可能是最合乎逻辑的选择,因为您将主要按顺序访问它。它允许快速添加和删除。某种数组应该会更慢,但是现在数组非常快,并且还允许二进制搜索。对于真正大量使用,某种基于树的(用于排序顺序访问)的字典或映射将为您提供两全其美的方法(快速添加和删除以及随机访问)。我想在这种情况下,我会选择某种数组。

这有点工作,但它可以给你一些真正的速度。

于 2012-06-11T20:00:26.187 回答