13

我有一个函数,它需要 2 个数组($schedule,$remove),它们都是包含时间的天数组,它会从 schedule 中删除时间。

现在,如果我有 1 到 20 个用户,则此功能可以正常工作,生成日历需要 2-4 秒,这很好,但是当有 20 多个用户有很多日程表条目时,它会达到 15 秒以上。

我正在使用 CodeIgniter,并且我在一个被大量调用的助手中拥有这个函数。

所以我想知道你们是否能找到更好的方法来处理我的问题或我对算法所做的调整以使其更快。

注意: 在下面的代码中,我看到的最大问题是每次修改结构时递归调用和循环中断。

我在两个数组上循环并进行测试以查看缺席是否在可用性内部/重叠/相等/外部,然后如果结构被修改,则调用该函数,如果不返回最终结构。

笔记2 :

在本地 Apache 崩溃,因为递归函数有时被调用超过 100 次。

这是我的代码:

   function removeSessionsFromSchedule($schedule, $remove) {

    $modified = false;
    if (is_array($schedule) && count($schedule) > 0 && is_array($remove) && count($remove) > 0 && checkArrayEmpty($remove)) {

        // Minimise the iterations
        $remove = minimiseRemoveSchedule($remove);
        foreach ($schedule as $s => $dispo) {

            if ($modified) {
                break;
            }

            $pos        = 0;
            $countdispo = count($dispo);

            foreach ($dispo as $d) {

                $abs = isset($remove[$s]) ?  $remove[$s] :null;
                $counter = 0;
                // availability start/end
                $dis_s = strtotime($d['heure_debut']);
                $dis_e = strtotime($d['heure_fin']);
                if (is_array($abs) && count($abs) > 0) {
                    foreach ($abs as $a) {
                        // absence start/end
                        $abs_s = strtotime($a['heure_debut']);
                        $abs_e = strtotime($a['heure_fin']);
                        // Tests to see the if there is overlap between absence and availability
                        // (2) [a_s]---[ds - de]---[a_e]
                        if ($abs_s <= $dis_s && $abs_e >= $dis_e) {
                            // delete availability
                            unset($schedule[$s][$pos]);
                            $modified = true;
                            break;
                        }
                        // (7)[as == ds] && [ae < de]
                        else if ($abs_s == $dis_s && $abs_e < $dis_e) {
                            unset($schedule[$s][$pos]);
                            $schedule[$s][$pos] = $d;
                            $schedule[$s][$pos]['heure_debut'] = date("H:i", $abs_e);
                            $schedule[$s][$pos]['heure_fin'] = date("H:i", $dis_e);
                            $modified = true;
                            break;
                        }
                        // (6) [ds -de] --- [as  ae] return dispo as is
                        else if ($abs_s >= $dis_e) {
                            unset($schedule[$s][$pos]);
                            $schedule[$s][$pos] = $d;
                            $modified ?: false;
                        }
                        // (5)[as  ae] [ds -de] ---  return dispo as is
                        else if ($abs_e <= $dis_s) {
                            unset($schedule[$s][$pos]);
                            $schedule[$s][$pos] = $d;
                            $modified ?: false;
                        }
                        // (1)[ds] --- [as] --- [ae] --- [de] (duplicate dis with new times)
                        else if ($abs_s > $dis_s && $abs_e <= $dis_e) {
                            // new times as : // s1 = ds-as &&  s2 = ae-de
                            unset($schedule[$s][$pos]);
                            $schedule[$s][$pos] = $d;
                            $schedule[$s][$pos + 1] = $d;

                            $schedule[$s][$pos]['heure_debut'] = date("H:i", $dis_s);
                            $schedule[$s][$pos]['heure_fin'] = date("H:i", $abs_s);
                            $schedule[$s][$pos + 1]['heure_debut'] = date("H:i", $abs_e);
                            $schedule[$s][$pos + 1]['heure_fin'] = date("H:i", $dis_e);

                            // a revoir si ca ne cause pas d'autre problem qu'on fasse pos++ ...
                            $pos++;

                            $modified = true;
                            break;
                        }
                        // (3)[as] -- [ds] --- [ae] -- [de]
                        else if ($abs_s < $dis_s && $abs_e < $dis_e) {
                            unset($schedule[$s][$pos]);
                            $schedule[$s][$pos] = $d;
                            $schedule[$s][$pos]['heure_debut'] = date("H:i", $abs_e);
                            $schedule[$s][$pos]['heure_fin'] = date("H:i", $dis_e);
                            $modified = true;
                            break;
                        }
                        // (4) [ds]---[as]--- [de]--- [ae]
                        else if ($abs_s > $dis_s && $abs_s < $dis_e && $abs_e > $dis_e) {
                            unset($schedule[$s][$pos]);
                            $schedule[$s][$pos] = $d;
                            $schedule[$s][$pos]['heure_debut'] = date("H:i", $dis_s);
                            $schedule[$s][$pos]['heure_fin'] = date("H:i", $abs_s);
                            $modified = true;
                            break;
                        } else {
                            $modified ?: false;
                        }
                    }

                    // if($modified == true) { break;}


                } else {
                    $modified = false;
                }
                $pos++;
            }
        }
    } else {
        $modified = false;
    }

    if ($modified) {
        $schedule = resetIndexes($schedule);
        $schedule = sortByTime($schedule);
        $schedule = removeSessionsFromSchedule($schedule, $remove);
    }

    return $schedule;
}

相关助手

function checkArrayEmpty($array) {
    if(is_array($array) && !empty($array)) {
        foreach($array as $arr) {
            if(is_array($arr) && !empty($arr)) {
                return true;
            }
        }
    }
    return false;
}

function subval_sort_by_time($a, $subkey) {
    if (is_array($a) && count($a) > 0) {
        foreach ($a as $k => $v) {
            $b[$k] = strtotime($v[$subkey]);
        }
        asort($b);
        foreach ($b as $key => $val) {
            $c[] = $a[$key];
        }
        return $c;
    }
    else
        return $a;
}



// Reset Index function 
function resetIndexes($array) {
        $new = array();
        foreach($array as $date => $arr) {
            //$new[$date]= array_values($arr);
            $new[$date]= array_merge(array(),$arr);
        }
        return $new;
    }

// sort by time
function sortByTime($array) {
    $sorted = array();
    if(is_array($array) && !empty($array)){
        foreach ($array as $s => $val) {
            $sorted[$s] = subval_sort_by_time($val, 'heure_debut');
        }
    }
    return $sorted;
  }


 function minimiseRemoveSchedule($array) {
    $new = array();
    foreach($array as $date => $arr) {
        $i=0;
        if(is_array($arr) && !empty($arr)) {

            foreach($arr as $a) {

                if(isset($new[$date][$i])) {
                    if($new[$date][$i]['heure_fin'] == $a['heure_debut']) {
                        $new[$date][$i]['heure_fin']  = $a['heure_fin'];
                    }
                    else {
                        $i++;
                        $new[$date][$i]['heure_debut'] = $a['heure_debut'];
                        $new[$date][$i]['heure_fin']   = $a['heure_fin'];
                    }

                } else {
                    $new[$date][$i]['heure_debut'] = $a['heure_debut'];
                    $new[$date][$i]['heure_fin']   = $a['heure_fin'];
                }
            }
        }
    }
    return $new;
}

我通过的数组示例:

$schedule = Array(
    '2012-11-12' => Array(),
    '2012-11-13' => Array(),
    '2012-11-14' => Array( 0 => Array("employe_id" => 8 , "heure_debut" => '16:00' ,"heure_fin" => '20:00' ,"date_seance" => 2012-11-14 , "jour_id" => 3)),
    '2012-11-15' => Array( 
        0 => Array("employe_id" => 8 , "heure_debut" => '09:00' ,"heure_fin" => '15:00' ,"date_seance" => 2012-11-15 , "jour_id" => 4),
        1 => Array("employe_id" => 8 , "heure_debut" => '16:00' ,"heure_fin" => '21:00' ,"date_seance" => 2012-11-15 , "jour_id" => 4)
    ),
    '2012-11-16' => Array(),
    '2012-11-17' => Array(),
    '2012-11-18' => Array(),
    '2012-11-19' => Array(0 => Array("employe_id" => 8 ,"heure_debut" => '10:00' ,"heure_fin" => '22:00' ,"date_seance" => 2012-11-19 ,"jour_id" => 1)),
    '2012-11-20' => Array(
        0 => Array("employe_id" => 8 ,"heure_debut" => '09:00' ,"heure_fin" => '15:00' ,"date_seance" => 2012-11-20 ,"jour_id" => 2),
        1 => Array("employe_id" => 8 ,"heure_debut" => '16:00' ,"heure_fin" => '20:00' ,"date_seance" => 2012-11-20 ,"jour_id" => 2)
    )
);

对于第二个数组:

$remove = array(
    '2012-11-12' => Array(),
    '2012-11-13' => Array(),
    '2012-11-14' => Array(),
    '2012-11-15'  => Array(),
    '2012-11-16' => Array(),
    '2012-11-17' => Array(),
    '2012-11-18' => Array(),
    // in this example i only have 1 absence ... I could have N absences
    '2012-11-19' => Array(0 => Array("employe_id" => 8 ,"date_debut" => 2012-11-19,"date_fin" => 2012-11-19  ,"heure_debut" => '12:00:00',"heure_fin"   => '14:00:00')),
    '2012-11-20' => Array(),
    '2012-11-21' => Array()
);

结果数组将是:

$result = array(
Array
(
       [2012-11-12] => Array()
       [2012-11-13] => Array()
       // no change 
       [2012-11-14] => Array( [0] => Array("employe_id" => 8 , "heure_debut" => 16:00 ,"heure_fin" => 20:00 ,"date_seance" => 2012-11-14 , "jour_id" => 3))
       // no change
       [2012-11-15] => Array( 
                              [0] => Array("employe_id" => 8 , "heure_debut" => 09:00 ,"heure_fin" => 15:00 ,"date_seance" => 2012-11-15 , "jour_id" => 4),
                              [1] => Array("employe_id" => 8 , "heure_debut" => 16:00 ,"heure_fin" => 21:00 ,"date_seance" => 2012-11-15 , "jour_id" => 4)
                            )
       [2012-11-16] => Array()
       [2012-11-17] => Array()
       [2012-11-18] => Array()
       // since absence from 12 to 14 and  we had availability from 8 to 22 instead we will have 8->12 and 14->22
       [2012-11-19] => Array(
                          [0] => Array("employe_id" => 8 ,"heure_debut" => 08:00 ,"heure_fin" => 12:00 ,"date_seance" => 2012-11-20 ,"jour_id" => 1),
                          [1] => Array("employe_id" => 8 ,"heure_debut" => 14:00 ,"heure_fin" => 22:00 ,"date_seance" => 2012-11-20 ,"jour_id" => 1)
                        )
       // no changes since no absence during those time
       [2012-11-20] => Array(
                          [0] => Array("employe_id" => 8 ,"heure_debut" => 09:00 ,"heure_fin" => 15:00 ,"date_seance" => 2012-11-20 ,"jour_id" => 2),
                          [1] => Array("employe_id" => 8 ,"heure_debut" => 16:00 ,"heure_fin" => 20:00 ,"date_seance" => 2012-11-20 ,"jour_id" => 2)
                        )
)
4

6 回答 6

5

我不明白为什么您需要指数时间递归来执行此任务。您可以通过嵌套循环摆脱 O(r * e^2) 解决方案(其中 e 是每天的平均可用性/移除次数,r 是移除次数的大小)。伪代码如下:

for removeday in remove:
    define scheduleday := schedule[removeday.date]
    if scheduleday not found:
        continue
    for removesegment in removeday:
        define temparray := empty
        for availsegment in scheduleday:
            if availsegment.employeid != removesegment.employeid:
                continue
            if no overlap:
                temparray.add(availsegment)
            if partial overlap:
                temparray.add(availsegment.split(removesegment))
        scheduleday = temparray
    schedule[removeday.date] := scheduleday
return schedule
于 2012-11-19T16:19:17.527 回答
2

如果您不想在函数中添加递归,那么您必须首先将其转换为可用调度数组矩阵的秒数。这里的想法:

function scheduleToSecondsMatrix($value, $available=true){

    if(!is_array($value) || empty($value))
        return false;

    $object = array();

    foreach($value as $v) {
        $s = strtotime('1970-01-01 ' . $v['heure_debut'] . (!$available ? ' +1 seconds' : '')); // ref. http://stackoverflow.com/questions/4605117/how-to-convert-a-hhmmss-string-to-seconds-with-php
        $e = strtotime('1970-01-01 ' . $v['heure_fin'] . (!$available ? ' -1 seconds' : ''));

        if($e < $s) continue; // logically end time should be greater than start time

        while($s <= $e) {
            // i use string as key as this result will be merged: http://php.net/manual/en/function.array-merge.php
            $object["in_" . $s] = $available; // means in this seconds range is available
            $s++;
        }
    }

    return $object;
}

/**
 * This function assume: 
 * - all parameters refer to only one employee
 */
function removeSessionsFromScheduleRev($schedule, $remove) {

    if(!is_array($schedule) || !is_array($remove) || empty($schedule) || empty($remove)) return false;

    foreach($schedule as $s => &$dispo){

        if(empty($remove[$s]))
            continue;

        // convert the schedule to seconds array matrix, that's i call it :)
        $seconds_available = scheduleToSecondsMatrix($dispo, true);
        $seconds_not_available = scheduleToSecondsMatrix($remove[$s], false);

        if( !$seconds_available || !$seconds_not_available ) continue; // nothing changed

        $seconds_new = array_merge($seconds_available, $seconds_not_available);
        $seconds_new = array_filter($seconds_new); // remove empty/false value

        $new_time_schedule = array();
        $last_seconds = 0;

        $i=0;

        foreach($seconds_new as $in_seconds => $val){

            $in_seconds = intval(str_replace('in_', '', $in_seconds));

            if($in_seconds > ($last_seconds+1)){
                if(!empty($new_time_schedule)) $i++;
            }

            if(empty($new_time_schedule[$i]['start'])) $new_time_schedule[$i]['start'] = $in_seconds;
            $new_time_schedule[$i]['end'] = $in_seconds;

            $last_seconds = $in_seconds;
        }

        foreach($new_time_schedule as $idx => $val){
            if($idx && empty($dispo[$idx])) $dispo[$idx] = $dispo[$idx-1];

            $dispo[$idx]['heure_debut'] = date('H:i:s', $val['start']);
            $dispo[$idx]['heure_fin'] = date('H:i:s', $val['end']);
        }
    }

    return $schedule;
}

我尚未对性能进行基准测试,因此您可以在自己的代码上尝试此代码。我希望它有效。

于 2012-11-24T02:07:27.607 回答
2

下面的代码为给定的示例生成相同的输出,但我没有测试所有可能的情况。

工作演示

function removeSessionsFromScheduleHelper(&$schedule,&$remove) {

    $change = false;

    foreach($remove as $date => &$remove_ranges) {

        if(empty($remove_ranges) || !isset($schedule[$date]))
            continue;

        foreach($remove_ranges as &$remove_range) {
            foreach($schedule[$date] as $day_key => &$time) {

                //start after finish, no overlap and because schedules are sorted
                //next items in schedule loop will also not overlap
                //break schedule loop & move to next remove iteration
                if($time['heure_debut'] >= $remove_range['heure_fin'])
                    break;

                //finish before start, no overlap
                if($time['heure_fin'] <= $remove_range['heure_debut'])
                    continue;

                //complete overlap, remove
                if($time['heure_debut'] >= $remove_range['heure_debut']
                  && $time['heure_fin'] <= $remove_range['heure_fin']) {
                    unset($schedule[$date][$day_key]);
                    continue;
                }

                //split into 2 ranges
                if($time['heure_debut'] < $remove_range['heure_debut']) {

                    if($time['heure_fin'] > $remove_range['heure_fin']) {
                        $schedule[$date][] = array(
                            'heure_debut' => $remove_range['heure_fin'],
                            'heure_fin' => $time['heure_fin']
                        );
                    }

                    $change = true;
                    $time['heure_fin'] = $remove_range['heure_debut'];                     
                    continue;
                }

                if($time['heure_debut'] >= $remove_range['heure_debut']) {
                    $change = true;
                    $time['heure_debut'] = $remove_range['heure_fin'];
                }                
            }
        }
    }

    if($change) {    
       foreach($schedule as &$values) {
          usort($values,'compare_schedule');
       }
    }

    return $change;
}

function compare_schedule($a,$b) {
    return strtotime($a['heure_debut']) - strtotime($b['heure_debut']);
}

function removeFromSchedule(&$schedule,$remove) {

    foreach($remove as $k => &$v) {
        foreach($v as $k2 => &$v2) {
            $v2['heure_debut'] = substr($v2['heure_debut'],0,5);
            $v2['heure_fin'] = substr($v2['heure_fin'],0,5);
        }
    }

    while(removeSessionsFromScheduleHelper($schedule,$remove));    
}

removeFromSchedule($schedule,$remove);
print_r($schedule);
于 2014-07-20T09:33:19.903 回答
1

我认为jma127的伪代码是正确的。让我用一些评论来补充他们的回答。

您的基本结构是遍历 的条目$schedule,然后为每个条目从 中拉出相应的条目$remove,并进行一些更改。一旦发生变化,你就会跳出循环,重新开始。您用来重新开始的控制结构是递归调用。当您重新开始时,您将再次循环浏览$schedule您已经检查过且不再需要更改的所有条目。

数组$schedule和数组$remove通过共享下标关联。对于给定的索引i$remove[i]仅影响$schedule[i]而不影响其他部分。如果没有条目$remove[i],则$schedule[i]不变。因此, jma127正确地重组循环以首先遍历 的条目$remove,并具有一个内部代码块来组合 和 的$remove[i]条目$schedule[i]。不需要递归。无需反复迭代$schedule

我相信这是您的代码随着条目数量的增加而变慢的主要原因。

对于 和 中给定日期的条目,$remove组合$schedule它们的方式基于开始时间和结束时间。jma127正确地指出,如果您按时间对当天的条目进行排序(首先是开始时间,然后是结束时间),那么您可以一次通过两个数组,并最终得到正确的结果。无需递归或重复循环。

我相信这是您的代码变慢的次要原因。

关于您的代码,我注意到的另一件事是您经常将代码放在不受循环影响的循环中。将它放在循环之外会更有效。例如,您对$removeand的有效性检查$schedule

if (is_array($schedule) && count($schedule) > 0 \
  && is_array($remove) && count($remove) > 0)...

每次递归调用例程时都会重复。$remove您可以改为将此检查移至调用内部函数的外部函数,并且内部函数不需要$schedule再次检查:

function removeSessionsFromSchedule_outer($schedule, $remove) {

    if (   is_array($schedule) && count($schedule) > 0 
        && is_array($remove) && count($remove) > 0     ) {
        $schedule = removeSessionsFromSchedule($schedule, $remove);
    }

    return $schedule;
}

相似地,

foreach ($dispo as $d) {
    if (isset($remove[$s])) {
        $abs = $remove[$s];
    } else
        $abs = null;
    // rest of loop....
}/*foreach*/

可以改写为:

if (isset($remove[$s])) {
    $abs = $remove[$s];
} else
     $abs = null;
foreach ($dispo as $d) {
    // rest of loop....
}/*foreach*/

另一个小的低效率是您的数据结构不包含您需要的格式的数据。而不是接收包含以下数据的结构:

[2012-11-14] => Array( [0] => Array(..."heure_debut" => 16:00 ...))

并且每次在循环期间,进行数据转换,如:

$abs_s = strtotime($a['heure_debut']);

让上游调用者自己转换数据怎么样:

["2012-11-14"] => Array([0]=>Array(..."heure_debut"=>strtotime("16:00") ...))

另一个小细节是您使用类似2012-11-14and的语法16:00。PHP 将它们视为字符串,但如果您将它们放在引号中以明确它们是字符串,您的代码会更清晰。请参阅为什么 $foo[bar] 错误?在 PHP 文档数组中。

我不会尝试重写您的代码来进行所有这些更改。我怀疑你可以自己弄清楚,看看我的评论和jma127的回答。

于 2012-11-24T23:45:06.780 回答
1

您有一个可用性时间表,实现为日期和条目编号的二维数组,以及一个缺勤时间表,以相同的方式实现,都按时间排序,并希望首先使用第二个进行更新。

两个数组在其主要维度上的索引方式相同(使用日期),因此我们可以安全地处理这些行中的每一行,而不必担心修改其余数组。

对于给定的一天:

在一天之内,最简单的方法是遍历所有$remove条目,对于employee_id 上的每个匹配项,检查时间并相应地修改时间表(您已经实现了一些,因此我们可以重用其中的一些)。您希望按时间顺序保持日程安排。原始数组排序良好,如果我们在创建顺序时将修改存储在新数组中,之后就不必对其进行排序。

<?php

// create a schedule entry from template, with begin & end time
function schedule($tmpl, $beg, $end) {
    $schedule = $tmpl;
    $schedule['heure_debut'] = date("H:i", $beg);
    $schedule['heure_fin'] = date("H:i", $end);
    return $schedule;
}

// return one updated entry of a schedule day, based on an absence
function updateAvailability($d, $a){
    // absence start/end
    $dis_s = strtotime($d['heure_debut']);
    $dis_e = strtotime($d['heure_fin']);
    $abs_s = strtotime($a['heure_debut']);
    $abs_e = strtotime($a['heure_fin']);
    // Tests to see the if there is overlap between absence and availability
    // (2) [a_s]---[ds - de]---[a_e]
    if ($abs_s <= $dis_s && $abs_e >= $dis_e) {
        return array();
    }
    // (7)[as == ds] && [ae < de]
    else if ($abs_s == $dis_s && $abs_e < $dis_e) {
        return array(schedule($d,$abs_e,$dis_e));
    }
    // (1)[ds] --- [as] --- [ae] --- [de] (duplicate dis with new times)
    else if ($abs_s > $dis_s && $abs_e <= $dis_e) {
        // new times as : 
        // s1 = ds-as &&  s2 = ae-de
        return array(schedule($d,$dis_s,$abs_s), schedule($d,$abs_e,$dis_e));
    }
    // (3)[as] -- [ds] --- [ae] -- [de]
    else if ($abs_s < $dis_s && $abs_e < $dis_e) {
        return array(schedule($d,$abs_e,$dis_e));
    }
    // (4) [ds]---[as]--- [de]--- [ae]
    else if ($abs_s > $dis_s && $abs_s < $dis_e && $abs_e > $dis_e) {
        return array(schedule($d,$dis_s,$abs_s));
    }
    return array($d);
}

// move through all the entries of one day of schedule, and change
function updateDaySchedule($day, $absence){
    $n = array();
    foreach($day as $avail){
        // intersect availability with absence
        $a = updateAvailability($avail,$absence);
        // append new entries
        $n = array_merge($n, $a);
    }
    return $n;
}    

function removeSessionsFromSchedule($schedule, $remove) {
    if (!checkValidScheduleInput($schedule,$remove) 
        return $schedule;
    foreach($remove as $day => $absences) {
        // only update matching schedule day
        if (isset($schedule[$day])) {
            foreach ($absences as $abs)
                $schedule[$day] = updateDaySchedule($schedule[$day], $abs);
        }
    }
    return $schedule;
}

?>

还有一些改进的空间:

  • 中的$dis_s,$dis_e等值updateAvailability每次都重新计算,而有些可以计算一次,并作为参数传递给函数。不过,这可能不值得麻烦。

  • 'heure_debut'等常量可以作为定义的常量:

    define('HD','heure_debut');

    这避免了可能的拼写错误(如果一个常量拼错了,php 会告诉你,但它不会告诉你字符串字面量),并且如果键名必须更改,则更容易重构。

于 2012-11-25T00:24:30.223 回答
0

函数的递归性质是你的问题,你的函数中没有其他东西需要太多的处理能力,所以这应该很快。您确实需要找到一种方法来进行此处理而无需递归。

于 2012-11-16T17:59:15.923 回答