Я работаю над функцией, которая проверяет, сталкиваются ли внешние события с внутренними событиями (в приложении для календаря). Процесс выглядит следующим образом:
$internalEvents
) $externalEvents
). Это существующие события с типом busy
. Я старался как можно меньше сократить его. Это ввод данных:
$internalEvents = array( array( "start" => "03/29/2016 12:00:00", "end" => "03/29/2016 13:00:00" ), array( "start" => "03/29/2016 12:30:00", "end" => "03/29/2016 13:30:00" ), array( "start" => "03/29/2016 13:00:00", "end" => "03/29/2016 14:00:00" ), array( "start" => "03/29/2016 13:30:00", "end" => "03/29/2016 14:50:00" ), array( "start" => "03/29/2016 14:00:00", "end" => "03/29/2016 15:00:00" ), array( "start" => "03/29/2016 14:30:00", "end" => "03/29/2016 15:30:00" ), array( "start" => "03/29/2016 15:00:00", "end" => "03/29/2016 16:00:00" ), array( "start" => "03/29/2016 15:30:00", "end" => "03/29/2016 16:30:00" ), array( "start" => "03/29/2016 16:00:00", "end" => "03/29/2016 17:00:00" ) ); $externalEvents = array( array( "start" => "03/29/2016 08:00:00", "end" => "03/29/2016 12:00:00", "type" => "busy" ), array( "start" => "03/29/2016 15:30:00", "end" => "03/29/2016 16:00:00", "type" => "busy" ), array( "start" => "03/29/2016 13:30:00", "end" => "03/29/2016 14:15:00", "type" => "busy" ) );
Теперь я пытаюсь найти любой конфликт, сравнивая внутреннее событие со всеми внешними событиями:
foreach($internalEvents as $internalEvent) { $internalEventStart = new DateTime($internalEvent['start']); $internalEventEnd = new DateTime($internalEvent['end']); $result = true; echo "\nverifying " . $internalEventStart->format('Ymd H:i') . " - " . $internalEventEnd->format('Ymd H:i') . "\n"; foreach($externalEvents as $externalEvent) { $externalEventStart = new DateTime($externalEvent['start']); $externalEventEnd = new DateTime($externalEvent['end']); // check if there are conflicts between internal and external events if ($internalEventStart >= $externalEventStart && $internalEventStart <= $externalEventEnd) { $result = false; echo " problem 1: event is between busy time: " . "\n"; } if ($internalEventStart >= $externalEventStart && $internalEventStart <= $externalEventEnd && $externalEventEnd <= $internalEventEnd) { $result = false; echo " problem 2: event starts during busy time: " . "\n"; } if ($internalEventStart <= $externalEventStart && $externalEventStart <= $internalEventEnd && $internalEventEnd <= $externalEventEnd) { $result = false; echo " problem 3: event stops during busy time: " . "\n"; } if (($internalEventStart <= $externalEventStart) && ($externalEventStart <= $externalEventEnd) && ($externalEventEnd <= $internalEventEnd)) { $result = false; echo " problem 4: event during busy time: " . "\n"; } if (($internalEventStart <= $internalEventEnd) && ($internalEventEnd <= $externalEventStart) && ($externalEventStart <= $externalEventEnd)) { $result = false; echo " problem 5: event during busy time: " . "\n"; } } if($result) { echo " result: OK\n"; } else { echo " result: NOT OK \n"; } }
Я ищу алгоритм, который может найти любой возможный конфликт перекрытия событий. Любой намек высоко ценится.
Прогон кода можно найти здесь (IDEone.com).
Когда сталкиваются два события? См. Эту схему:
----E---- CS/EE CE/ES --N-- < < --N-- > > --C-- < > --C-- < > --C-- < > -----C----- < > ·················································· E = Main Event N = Not Collide Event C = Collide Event CS = Compare Event Start EE = Main Event End CE = Compare Event End ES = Main Event Start
Как вы можете видеть, столкновение происходит только тогда, когда начало события C до конца события E, а конец события E – после начала события C. Знание этого помогает найти эффективный и короткий метод для сравнения событий.
О коде, предварительное примечание: вы конвертируете даты в ISO 8601, прежде чем сравнивать их несколько раз в своем коде, так почему бы не создать функцию для этого?
function eventsToDate( $row ) { $retval = array( 'start' => date_create( $row['start'] )->format('Ymd H:i:s'), 'end' => date_create( $row['end'] )->format('Ymd H:i:s') ); $retval['print'] = sprintf( '%s-%s', substr( $retval['start'],-8,5 ), substr( $retval['end'],-8,5 ) ); return $retval; }
Эта функция возвращает ассоциативный массив с 'start' и 'end' в вашем формате. Я добавил третий ключ, 'print', чтобы использовать его во время отладки. Обратите внимание, что в печати я рассматриваю только часы: минуты (все даты в вашем примере массива с одного дня), но сравнение производится в полные даты. Вы можете опустить этот «печатный» ключ или заменить его предпочтительным форматом вывода.
Вы выполняете два вложенных foreach
, и для каждого цикла пересчитываете формат даты. С образцами массива вы вызываете DateTime / DateTime :: format 36 раз. Создав временный массив со всеми преобразованными $externalEvents
, мы можем уменьшить эти вызовы до 12. Итак, перед запуском цикла foreach()
мы используем array_map
с array_map
функцией и массивом $externalEvents
для создания массива с отформатированными датами:
$externalDates = array_map( 'eventsToDate', $externalEvents );
Затем мы начинаем основной цикл foreach()
на $internalEvents
:
foreach( $internalEvents as $internalEvent ) { $internalDates = eventsToDate( $internalEvent ); echo $internalDates['print'] . PHP_EOL; $result = True; foreach( $externalDates as $externalDate ) {
На данный момент мы сравниваем даты. Как упоминалось выше, мы начинаем с начала и конца с начала. Чтобы упростить следующее сравнение, мы используем strcmp
, php, которая «возвращает <0, если str1 меньше str2,> 0, если str1 больше str2, а 0, если они равны»:
$startCmp = strcmp( $internalDates['start'], $externalDate['end'] ); $endCmp = strcmp( $internalDates['end'], $externalDate['start'] );
Теперь, сравнение:
if( $startCmp<0 && $endCmp>0 ) { $result = False; echo " {$externalDate['print']} COLLIDE\n"; } else { echo " {$externalDate['print']} OK\n"; } }
И, наконец, мы можем напечатать результат:
echo "Result: " . ( $result ? 'OK' : 'NOT OK') . "\n\n"; }
Демо-версия eval.in
Примечание: при сравнении выше, с первым $internalEvent
получаем следующий результат:
12:00-13:00 08:00-12:00 OK 15:30-16:00 OK 13:30-14:15 OK Result: OK
Вместо этого, если вы хотите получить этот результат:
12:00-13:00 08:00-12:00 COLLIDE 15:30-16:00 OK 13:30-14:15 OK Result: NOT OK
Вы должны заменить выше, if
условие:
if( $startCmp<=0 && $endCmp>=0 )
Над кодом будет работать, если вы хотите получить более подробную информацию о виде столкновения, вы можете проверить другие комбинации начала и конца внутри условия if
.
Альтернатива: Возвращающиеся сталкивающиеся события
Если вместо печати результатов вы хотите поймать встречные события, вы можете заменить вложенный foreach()
на array_filter
таким образом:
$result = array_filter ( $externalDates, function( $row ) use( $internalDates ) { $startCmp = strcmp( $internalDates['start'], $row['end'] ); $endCmp = strcmp( $internalDates['end'], $row['start'] ); return( $startCmp<0 && $endCmp>0 ); } );
На этом этапе встречные события находятся в массиве $result
. Очевидно, что массив пуст, конфликтов нет.