计算时间范围内事物的可用性

时间:2019-05-17 00:50:13

标签: php math

以下情况: 我们同时拥有3件物品。 第一本书的预定时间为09:00至11:00。 第二个预订时间为11:00至13:00 我想计算从10:00到12:00有多少东西可用。 我已经完成了计算,从10:00到12:00预订了多少东西。 那是2。所以可用的东西是1。 但是第一个和第二个可能是相同的。因此在指定时间内可用的事物为2,我的计算是错误的。

因此,我创建了以下算法来计算一个时间范围内的占用空间。结果来自数据库查询,并包含我所预定时间范围内的事物的预订(结果是一个数组,包含开始,结束和cnt,这是已预订事物的数量):

 $blocks = array();
    $times = array();
    foreach($results as $result){
     $block = array();
     $block['start'] = DateTime::createFromFormat("Y-m-d H:i:s",   $result['start'])->getTimestamp();
        $block['end'] = DateTime::createFromFormat("Y-m-d H:i:s", $result['end'])->getTimestamp();
        $block['things'] = $result['cnt'];
        $blocks[] = $block;
        $times[] = $block['start'];
        $times[] = $block['end'];
    }

    $times = array_unique($times);
    $times = array_values($times);
    $peak = 0;

    foreach($times as $time){
        $timePeak = 0;
        foreach($blocks as $block){

            if($time >= $block['start'] && $time <= $block['end']){
                $timePeak += $block['things'];
            }
        }
        if($timePeak > $peak){
            $peak = $timePeak;
        }
    }

    return $peak;
}

database

通过这种方法,我正在为该范围内每个预订的每个开始和结束时间创建时间戳。 然后,我计算了每个时间戳记的所有预订的总和。计算的最大值(峰值)是最大预订量。所以可用的东西是最大的-峰值。

有效。 但是,还有一种更优雅的计算方法吗?

2 个答案:

答案 0 :(得分:0)

类似于以下常见编码采访问题的声音:https://www.geeksforgeeks.org/find-the-point-where-maximum-intervals-overlap/

如果您的“最大物品”是硬编码的,是的,我想您会在给定时间内找到最大的物品预订量,并从最大数量中减去以获取整个范围内的最小可用性。为此,您需要考虑您范围内所有开始或结束的预订。正如该链接上的高效解决方案所建议的那样,您可以对起点和终点进行排序,然后仔细检查它们的运行情况。

要处理您所讨论的预订结束与预订开始在何处对齐的边界情况,请确保在时间为“开始”和“结束”之间的排序始终在“开始”之前对“结束”进行排序相同,这样就不会出现真正不存在的重叠外观。

我假设您正在处理这样的表:

CREATE TABLE `bookings` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `start` datetime NOT NULL,
  `end` datetime NOT NULL,
  PRIMARY KEY (`id`)
);

而且,您有这样的数据:

INSERT INTO `bookings` (`id`, `start`, `end`)
VALUES
    (1, '2019-05-05 09:00:00', '2019-05-05 11:00:00'),
    (2, '2019-05-05 11:00:00', '2019-05-05 13:00:00'),
    (3, '2019-05-05 07:00:00', '2019-05-05 10:00:00'),
    (4, '2019-05-05 12:00:00', '2019-05-05 14:00:00'),
    (5, '2019-05-05 14:00:00', '2019-05-05 17:00:00'),
    (6, '2019-05-05 10:30:00', '2019-05-05 11:30:00');

此数据包含您需要考虑的所有相关情况:

  1. 预定范围之外的预定
  2. 从您的范围开始的预订
  3. 与范围起点相交的预订
  4. 与范围末端相交的预订
  5. 预订完全包含在您的范围之内
  6. 在您的范围内“交出”的预订

我看着对此进行SQL查询,但是当它变得比您实际想要用SQL编写代码复杂时,它就停止了。我认为这可能是可行的,但需要非常难看的SQL。绝对可以在代码中做到这一点。我认为算法是通过查询来加载与您的范围相关的所有预订,

SELECT * FROM bookings 
WHERE start BETWEEN :start AND :end
     OR end BETWEEN :start AND :end

所有可能涉及的预订。然后,您将开始事件和结束事件按前面所述作为不同的事件进行排序,并在事件列表中循环,以使运行中的计数器继续运行以及迄今为止看到的最大值。运行计数将首先上升,最后它将下降至零,但中间可能会有多个峰。因此,您不能只在第一次发生故障时就停止它,而必须一直进行扫描,直到完成所有事件为止(或者至少要到结束时为止,但要一直进行下去就更简单且没有实际成本了)清单)。完成后,您将获得最多的并行预订数。

我尚未对此进行测试或编译,因此将其作为伪代码。我会将您的代码更改为以下内容:

//assumedly, prior to this point you have a
//$startTime and $endTime that you used to do the query mentioned above
//and the results of the query are in $results

$times = array();
foreach($results as $result){
    $time = array();
    $time['time'] = DateTime::createFromFormat("Y-m-d H:i:s",   $result['start'])->getTimestamp();
    $time['change'] = $result['cnt'];
    $times[] = $time;
    $time['time'] = DateTime::createFromFormat("Y-m-d H:i:s", $result['end'])->getTimestamp();
    $time['change'] = -1 * $result['cnt'];
    $times[] = $time;
}

usort($times, function($lh, $rh) {
    if ($lh['time'] === $rh['time']) {
        return $lh['change'] - $rh['change']
    } else {
        return $lh['time'] < $rh['time'] ? -1 : 1;
    }
}

$maxPeak = 0;
$curVal = 0;
foreach($times as $time){
    //breaking early here isn't so much about optimization as it is about
    //dealing with the instantaneous overlap problem where something starts
    //right at the end time.  You don't want that to look like a utilization
    //that counts against you.
    if ($time['time'] === $endTime) {
        break;
    }

    $curVal += $time['change'];
    if ($curVal > $maxPeak) {
        $maxPeak = $curVal;
    }
}

//$maxPeak is the max number of things in use during the period

答案 1 :(得分:0)

关于您的预订的第一件事是时间重叠。

如果记录为一小时的预订是从10:00:00开始到11:00:00结束,则实际上是1小时零1秒钟。

为解决这个问题,并防止不正确的重叠,在比较之前,我从日期中减去了1秒。这似乎是解决问题的最简单方法。

SELECT b.*
FROM bookings b
WHERE (DATE_SUB(@start, INTERVAL 1 SECOND) BETWEEN start AND DATE_SUB(end, INTERVAL 1 SECOND))
OR (DATE_SUB(@end, INTERVAL 1 SECOND) BETWEEN start AND DATE_SUB(end, INTERVAL 1 SECOND));

在上面的示例中,将@start@end替换为查询参数绑定的开始时间和结束时间。

DB fiddle example