從錯誤中學習

bnuwangly發表於2014-06-09

那天閒逛,發現了這個帖子,http://www.ituring.com.cn/Article/111574
就用Delphi寫了一遍GetMaxAdjacentProdouct,寫完後發現執行速度還是很快的。
用GetTickCount用時總是0 ms,用精度更高的QueryPerformanceCounter則耗時0.60 ms;
比原帖的Haskell以及之前回復的Java、JavaScript都快。 題目中給出了連續數的個數為4時的正確答案,程式執行正確,便以為程式碼沒有錯誤。
回了原帖,並給出了連續數為13時的答案。然後洗洗睡了。

第二天再次看帖,卻發現樓主說答案錯誤,於是又仔細地看了看程式碼,的確發現一處bug。
為了提高效率,不用每次都重新計算連續12個數的乘積,
將第一次計算時的後12個數乘積結果保留下來作為nCommonProduct, 計算2-14的連續乘積時,便只需要nCommonProduct * 14th;
如何判斷是否第一次計算,為此引入了布林變數bIsFirst,初始賦值True,
每迴圈一次bIsFirst取反(bIsFirst := not bIsFirst)。
設連續數的個數為AdjacentNum,當前連續乘積的第一個數的索引為nIndex,
第1或者第AdjacentNum + 1個數的值賦值給nRemain,
則 nRemain := IfThen(bIsFirst, nIndex, nIndex + AdjacentNum)。
此處有一bug,bIsFirst取反後,已經迴圈了一次,即Index已經增加了1,
所以第二次的下標為nIndex + AdjacentNum - 1,
即 nRemain := IfThen(bIsFirst, nIndex, nIndex + AdjacentNum - 1)。
改完後自以為沒有問題了,又新增了回覆,並再次給出了AdjacentNum為[4,13]時的答案。

沒有想到的是,樓主再次告訴我“這題的正確答案仍然不是”。
不敢馬虎,再次仔細讀程式碼。可是的確沒有發現什麼錯誤。
一切回到起點,用最簡單的方法寫程式碼GetMaxAdjacentProdouct_Easy,
先得到正確答案,再優化效率,卻發現得到的答案還是和之前一樣……,
於是就在尤拉計劃的網站https://projecteuler.net/註冊了一個使用者,找到第8題,提交了答案,
依然顯示我提交的答案錯誤。看來,我的確是錯了。
再次仔細閱讀程式碼,依然沒有發現錯誤。
過了一段時間,想到是不是整型溢位了,於是將型別由Integer改為Int64,果然得到了一個新的答案。 再次在尤拉計劃的網站上提交,這次終於正確。

想起以前一位數學老師說過的話,再簡單的試卷,得滿分也是不容易的……
執行10000次,GetMaxAdjacentProdouct 耗時 620 ms左右, GetMaxAdjacentProdouct_Easy 耗時 1380 ms左右,是前者的兩倍,和理論相符。

procedure EulerPlan_008;
var
  nBegin, nEnd, nInterval, nFrequency, nProduct: Int64;
  I: Integer;
  oFile: TextFile;
  sNumbers, sLine: string;
  bIsSupport: Boolean;
  arrNumbers: TByteDynArray;
begin
  QueryPerformanceCounter(nBegin);
  AssignFile(oFile, '..\..\008.txt');
  Reset(oFile);
  try
    while not Eof(oFile) do
    begin
      Readln(oFile, sLine);
      sNumbers := sNumbers + sLine;
    end;
  finally
    CloseFile(oFile);
  end;

  //去掉空格及換行符號
  sNumbers := StringReplace(sNumbers, ' ', '', [rfReplaceAll]);
  sNumbers := StringReplace(sNumbers, #13#10, '', [rfReplaceAll]);
  sNumbers := StringReplace(sNumbers, #13, '', [rfReplaceAll]);

  Assert(Length(sNumbers) = 1000);
  SetLength(arrNumbers, Length(sNumbers) + 1);
  for I := 1 to High(arrNumbers) do
  begin
    arrNumbers[I] := StrToInt(sNumbers[I]);
  end;

  for I := 0 to 9999 do
  begin
    nProduct := GetMaxAdjacentProdouct_Easy(13, arrNumbers);
//    nProduct := GetMaxAdjacentProdouct(13, arrNumbers);
  end;
  QueryPerformanceCounter(nEnd);
  bIsSupport := QueryPerformanceFrequency(nFrequency);
  nInterval := nEnd - nBegin;
  nFrequency := IfThen(bIsSupport, nFrequency, 1);

  ShowMessage(Format('乘積為 %d , 用時 %f ms.', [nProduct, (nInterval * 1000 / nFrequency)]));
end;


function GetMaxAdjacentProdouct(AdjacentNum: Integer;
  ASrcNumbers: TByteDynArray): Int64;
var
  nIndex, I: Integer;
  bIsFirst: Boolean;
  nRemain, nCommonProduct: Int64;
begin
  Result := 0;
  nIndex := 1;
  bIsFirst := True;
  nCommonProduct := -1;
  while nIndex <= High(ASrcNumbers) - AdjacentNum + 1 do
  begin
    nRemain := IfThen(bIsFirst, ASrcNumbers[nIndex], ASrcNumbers[nIndex + AdjacentNum - 1]);
    if nRemain = 0 then
    begin
      nIndex := IfThen(bIsFirst, nIndex + 1, nIndex + AdjacentNum);
      bIsFirst := True;
      Continue;
    end;

    if bIsFirst then
    begin
      nCommonProduct := 1;
      for I := nIndex + 1 to nIndex + AdjacentNum - 1 do
      begin
        nCommonProduct := nCommonProduct * ASrcNumbers[I];
      if nCommonProduct = 0 then
        Break;
      end;
    end;

    Result := Max(Result, nCommonProduct * nRemain);
    Inc(nIndex);
    bIsFirst := not bIsFirst;
  end;
end;

function GetMaxAdjacentProdouct_Easy(AdjacentNum: Integer;
  ASrcNumbers: TByteDynArray): Int64;
var
  I, nIndex: Integer;
  nProduct: Int64;
begin
  Result := -1;
  nIndex := 1;
  while nIndex <= High(ASrcNumbers) - AdjacentNum + 1 do
  begin
    nProduct := 1;
    for I := nIndex to nIndex + AdjacentNum - 1 do
    begin
      nProduct := nProduct * ASrcNumbers[I];
      if nProduct = 0 then
        Break;
    end;
    Inc(nIndex);
    Result := Max(Result, nProduct);
  end;
end;

相關文章