1. 程式人生 > 其它 >一種計費時長演算法

一種計費時長演算法

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,新的演算法思路

由於採用時間點(一天之內的時刻數)比對統計非常複雜,新的思路是採用日期排序比對法。

把所有時間點換算成日期,然後比對,在免費時段就不統計,在非免費時段就加入統計。此法簡單明瞭。

以下是詳細步驟:

  1. 取出T1、T2時間對應的日期D1/D2
  2. (技巧)將D1減去一天,設定為計費起始日的前一天。目的是,判斷計費起始時間是否處於免費時段。
  3. 將D1和D2之間的日期,連續插入,形成日期列表DL:【D1、C11、C12、…、D2】
  4. 將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;