一種計費時長演算法
阿新 • • 發佈:2021-11-01
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;