我正在完全重写我的答案,以便它清晰并按正确的顺序流动。我对之前的方法做了一些小的改进,但没有什么大的改进。
首先是数据准备代码。我转换OPhh:mm
进出时间值设置为简单的分钟值,同时将员工 ID 保留为键。
// My test data in OP's format to start with:
$staff=[1,2,3];
$start=['11:00','13:00','17:00'];
$end=['21:00','15:00','19:00'];
// My data preparation method:
foreach($staff as $i=>$v){
$on=explode(':',$start[$i]); // separate hh from mm of start of shift
$on_minutes=$on[0]*60+$on[1]; // calculate total minutes from start of day
$off=explode(':',$end[$i]); // separate hh from mm of end of shift
$off_minutes=($off[0]+($on[0]>$off[0]?24:0))*60+$off[1]; // calculate minutes from start of day, factoring shift that run past midnight
$shifts[$v]=[$on_minutes,$off_minutes]; // store prepared data for future processes
}
/*
(new prepared array):
$shifts=[
1=>[660,1260],
2=>[780,900],
3=>[1020,1140]
];
*/
这是数据处理片段。我建立了一条捷径——如果一名员工与另一名员工共享相同的轮班,那么第一位员工将立即被视为独处时间为零(显然)。否则,将一名员工的轮班与其他员工的轮班进行一一比较,以确定他们独处的分钟数。
function whittle($colleague_shifts,$pieces_of_shift){ // initially, PoS is only one element
foreach($colleague_shifts as $k=>$cs){
foreach($pieces_of_shift as $i=>$ps){
if($cs[0]<=$ps[0] && $cs[1]>=$ps[1]){
unset($pieces_of_shift[$i]);
continue; // fully covered by coworker
}
$temp=[];
if($ps[0]<$cs[0] && $cs[0]<$ps[1]){
$temp[]=[$ps[0],$cs[0]]; // push new unmatched start into temp PoS array
}
if($ps[1]>$cs[1] && $cs[1]>$ps[0]){
$temp[]=[$cs[1],$ps[1]]; // push new unmatched end into temp PoS array
}
if($temp){
array_splice($pieces_of_shift,$i,1,$temp); // replace the current PoS with 1 or 2 new PoS subarrays
}
}
if(!$pieces_of_shift){
return 0; // no minutes alone
}
}
// subtract all end alone minutes from all start alone minutes
return array_sum(array_column($pieces_of_shift,1))-array_sum(array_column($pieces_of_shift,0));
}
foreach($shifts as $id=>$s){
$colleague_shifts=array_diff_key($shifts,[$id=>'']); // generate array excluding target worker's shift
if(in_array($s,$colleague_shifts)){ // check for same start and end times elsewhere
$alone[$id]=0; // exact duplicate allows shortcut as "never alone"
}else{
$alone[$id]=whittle($colleague_shifts,[$s]); // whittle down times where target employee is alone
}
}
var_export($alone);
Output:
array (
1 => 360, // alone from 11am-1pm, 3pm-5pm, and 7pm-9pm
2 => 0, // never alone
3 => 0, // never alone
)
帮助您了解内部发生的事情whittle()
- 工作人员 #1 从一整班开始
660
to 1260
. ($pieces_of_shift
是一个只有一个子数组的数组,子数组有两个元素 - 开始分钟和结束分钟)
$pieces_of_shift=[[660,1260]];
- 与 2 号员工进行比较后,原来的
$pieces_of_shift
子数组被两个新的子数组替换——轮班开始时的单独时间和轮班结束时的单独时间:660
to 780
and 900
to 1260
.
$pieces_of_shift=[[660,780],[900,1260]];
- 然后,将员工#3 的班次与员工#1 的两个剩余独处时间范围进行比较。员工 #3 的轮班不与第一个子阵列的任何部分重叠,但在第二个子阵列中却重叠。这意味着第二个时间范围将被替换,以有效地“消除”轮班时间的重叠。
$pieces_of_shift=[[660,780],[900,1020],[1140,1260]];
- 这导致员工 #1 的轮班有 3 个“独处”时间:
660
to 780
, 900
to 1020
, and 1140
to 1260
。这 3 个单独时间范围(每个 2 小时)相当于 6 小时的单独工作时间或 360 分钟。
Here is 带有附加评论的演示 http://sandbox.onlinephpfunctions.com/code/ca8e0ae26d8c562f35d763e1841be63acbc37dd2.
如果特定批次中存在高概率或大量重复班次,则内部的总迭代次数whittle()
可以通过写来减少$colleague_shifts=array_map('unserialize', array_unique(array_map('serialize', $shifts)))
在第一个之前foreach()
loop.
就此而言,同样的多功能方法could用于在调用之前快捷地执行多个重复的班次foreach($shifts...)
,但我选择不实现该方法,因为它可能不值得进行卷积。