以下情况: 我们同时拥有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;
}
通过这种方法,我正在为该范围内每个预订的每个开始和结束时间创建时间戳。 然后,我计算了每个时间戳记的所有预订的总和。计算的最大值(峰值)是最大预订量。所以可用的东西是最大的-峰值。
有效。 但是,还有一种更优雅的计算方法吗?
答案 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');
此数据包含您需要考虑的所有相关情况:
我看着对此进行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
替换为查询参数绑定的开始时间和结束时间。