1,问题的提出
停车场管理中,有些月卡是按时段免费停车的,也就是在指定的时间区间内停车,免收停车费;而在此区间以外的时间,则累计为计费时长,需要缴费。
该问题的输入是车辆进出时间T1、T2、免费时段起止时间点sh,eh
输出是计费时长,分钟为单位。
2,问题分析
咋看该问题并不复杂,采用时间点比较法,无非是在T1、T2之间找到免费区间,然后予以扣除即可。
但事实上,需要考虑的因素有:
免费时段起止时间点sh,eh,可能在一天内,也可能分布在2天内,也就是要考虑跨天的因素。
T1到T2的时间跨度X,与sh、eh构造的时间段Y之间的关系较为复杂。
假如考虑sh、eh跨天,那么2天之内,免费的区段就有两个,记为Y1、Y2,其中 Y2在Y1后面
X和Y1、Y2的关系就有如下可能性:
- X在Y1左侧
- X在Y2右侧
- X包含Y1和Y2
- X与Y1交叉但不包含Y1
- 。。。
粗略估计,位置关系有10种以上
若再考虑比较时,跨天的时段还得以0点为界,分解为两段,那么算法将非常复杂,极易因考虑不周出错。
3,新的算法思路
由于采用时间点(一天之内的时刻数)比对统计非常复杂,新的思路是采用日期排序比对法。
把所有时间点换算成日期,然后比对,在免费时段就不统计,在非免费时段就加入统计。此法简单明了。
以下是详细步骤:
- 取出T1、T2时间对应的日期D1/D2
- (技巧)将D1减去一天,设置为计费起始日的前一天。目的是,判断计费起始时间是否处于免费时段。
- 将D1和D2之间的日期,连续插入,形成日期列表DL:【D1、C11、C12、…、D2】
- 将DL与sh和eh组合,形成两个时间列表
- TLs:【D1s、C11s、C12s、…、D2s】
- TLe:【D1e、C11e、C12e、…、D2e】
5. 将T1/T2/TLs/TLe,组合为一个列表CL:【t1,t2,…,tn】
- l 按时间排序
- l TLs的元素,附加s标记
- l TLe的元素,附加e标记
6. 初始化参数:
- 计费时长Tc=0,起始时点Ts=0,计费标记Tf=0
7. 对CL中的元素,顺序执行以下算法:
- 取出元素,ti
- 若ti>T1,则Tc +=(ti-Ts)*Tf,累计收费时间
- 若ti=T2,则 退出。
- 若ti=T1,则 Ts=ti
- 若ti的标记为s,则 Ts=ti,Tf=0,进入免费区间
- 若ti的标记为e,则 Ts=ti,Tf=1,进入收费区间
4,参考代码
以下delphi代码,实现了上述思路,算法简明,不易出错,对于跨天、跨月、跨年等均不存在问题,经测试计算结果OK。
function TForm1.CalcMCFeeMinutesV2(sh, eh:integer; sDate, eDate: PChar):integer;
var
d1, d2: TDatetime;
Tc: double;
Ts: TDatetime;
Tf: integer;
i: integer;
Date1, Date2, curTime: TDatetime;
DL, TL: TStringList;
s_sh, s_eh: string;
begin
memo1.Lines.Clear ;
memo1.Lines.Add('出入时间:' + sDate + '--' + eDate);
d1 := StrTodateTime(sDate);
d2 := StrTodateTime(eDate);
//月卡计费无免费时段,直接返回所有分钟数
if sh=eh then begin
Result := round((d2 -d1)*24*60);
Exit;
end;
Date1 := StrToDatetime(formatDatetime('YYYY-MM-DD', d1)); //取日期整数
Date2 := StrToDatetime(formatDatetime('YYYY-MM-DD', d2)); //取日期整数
//格式化时间,以便后面排序
s_sh := IntToStr(sh);
if length(s_sh)=1 then
s_sh := '0'+ s_sh;
s_eh := IntToStr(eh);
if length(s_eh)=1 then
s_eh := '0'+ s_eh;
DL := TStringList.Create;
TL := TStringList.Create;
//要多出一天来,以便判断起始时间处于收费时段还是免费时段
Date1 := Date1 -2;
for i:=0 to (trunc (d2 -d1)+1) do begin
Date1 := Date1 + 1;
if Date1>date2 then break;
DL.Add(formatDatetime('YYYY-MM-DD', Date1));
end;
for i:=0 to DL.Count-1 do begin
TL.AddObject(DL[i]+' '+ s_sh +':00' , Pointer(1));
TL.AddObject(DL[i]+' '+ s_eh +':00' , Pointer(2));
end;
TL.AddObject(formatDatetime('YYYY-MM-DD HH:MM', d1) , Pointer(3));
TL.AddObject(formatDatetime('YYYY-MM-DD HH:MM', d2) , Pointer(4));
TL.Sort;
for i:=0 to TL.Count-1 do begin
memo1.Lines.Add(TL[i] +'/'+ IntToStr(Integer(TL.Objects[i])));
end;
//初始化参数:计费时长Tc=0,起始时点Ts=0,计费标记Tf=0
Tc := 0;
Ts := StrToDatetime(TL[0]);
Tf := 1;
for i:=0 to TL.Count-1 do begin
curTime := StrToDatetime(TL[i]);
if Integer(TL.Objects[i])=4 then begin
Tc := Tc + (curTime- Ts)* Tf;
memo1.Lines.Add(TL[i]+ format('/累计:%f',[Tc]));
Result := Round(Tc*24*60);
WriteMemoToLogFile('', memo1.Text );
Exit;
end;
if Integer(TL.Objects[i])=3 then begin
//if curTime>d1 then Tc := Tc + (curTime- Ts)* Tf;
Ts := curTime;
end;
if Integer(TL.Objects[i])=1 then begin
if curTime>d1 then Tc := Tc + (curTime- Ts)* Tf;
Ts := curTime;
Tf := 0;
end;
if Integer(TL.Objects[i])=2 then begin
if curTime>d1 then Tc := Tc + (curTime- Ts)* Tf;
Ts := curTime;
Tf := 1;
end;
memo1.Lines.Add(TL[i]+ format('/累计:%f',[Tc]));
end;
end;