TStringList启用二进制搜索而不求助?

时间:2017-10-13 13:04:00

标签: delphi delphi-xe2

我正在从ADO查询构建一个字符串列表,在查询中返回排序结果然后按顺序添加它们要快得多。这给了我一个已经排序的列表,然后调用Sort或设置sorted true花费我的时间,因为Quicksort算法在已经排序的列表上没有很好地预先形成。有没有办法设置TStringList使用二进制搜索而不运行排序? 在你问我之前我无法访问CustomSort属性。

3 个答案:

答案 0 :(得分:2)

我不确定我理解你担心的是什么,假设StringList的所需排序顺序与AdoQuery的ORDER BY相同。

当然要做的是将StringList上的Sorted设置为True,同时它仍为空,然后然后插入AdoQuery中的行。这样,当StringList即将添加一个条目时,它将使用IndexOf搜索它,而IndexOf将使用Find进行二进制搜索,以检查重复项。但是以这种方式使用Add并不涉及快速排序,因为StringList已将自身视为已排序。

鉴于您的评论和您自己的答案,我通过NexusDB质量套件中的Line Timer分析器运行了以下程序。结果是尽管使用二进制搜索与TStringList.IndexOf的执行速度存在可检测的差异,但它们与TStringList的QuickSort的使用(或不使用)无关。此外,差异可以通过我使用的二进制搜索和TStringList.Find中的二进制搜索之间的细微差别来解释 - 请参阅下面的更新#2。

程序生成200k个100个字符的字符串,然后将它们插入到StringList中。 StringList以两种方式生成,首先在添加任何字符串之前将Sorted设置为True,然后在添加字符串之后将Sorted设置为True。 StringList.IndexOf然后您的BinSearch用于查找已添加的每个字符串。结果如下:

Line    Total Time  Source
80      procedure Test;
119    0.000549 begin
120 2922.105618   StringList := GetList(True);
121 2877.101652   TestIndexOf;
122 1062.461975   TestBinSearch;
123   29.299069   StringList.Free;
124     
125 2970.756283   StringList := GetList(False);
126 2943.510851   TestIndexOf;
127 1044.146265   TestBinSearch;
128   31.440766   StringList.Free;
129             end;
130     
131     begin
132       Test;
133     end.

我遇到的问题是你的BinSearch实际上从未返回1,失败次数等于搜索到的字符串数。如果你能解决这个问题,我会很乐意重新做这个测试。

program SortedStringList2;
[...]
const
  Rows = 200000;
  StrLen = 100;

function ZeroPad(Number : Integer; Len : Integer) : String;
begin
  Result := IntToStr(Number);
  if Length(Result) < Len then
    Result := StringOfChar('0', Len - Length(Result)) + Result;
end;

function GetList(SortWhenEmpty : Boolean) : TStringList;
var
  i : Integer;
begin
  Result := TStringList.Create;
  if SortWhenEmpty then
    Result.Sorted := True;
  for i := 1 to Rows do
    Result.Add(ZeroPad(i, StrLen));
  if not SortWhenEmpty then
    Result.Sorted := True;
end;

Function BinSearch(slList: TStringList; sToFind : String) : integer;
var
 i, j, k  : integer;
begin
  try
    i := slList.Count div 2;
    k := i;
    if i = 0 then
    begin
      Result := -1;
      // SpendLog('BinSearch List Empty, Exiting...');
      exit;
    end;

while slList.Strings[i] <> sToFind do
begin
  if CompareText(slList.Strings[i], sToFind) < 0 then
  begin
    j := i;
    k := k div 2;
    i := i + k;
    if j=i then
      break;
  end else
  if CompareText(slList.Strings[i], sToFind) > 0 then
  begin
    j := i;
    k := k div 2;
    i := i - k;
    if j=i then
      break;
  end else
    break;
end;

if slList.Strings[i] = sToFind then
  result := i
else
  Result := -1;

 except
    //SpendLog('<BinSearch> Exception: ' + ExceptionMessage + ' At Line: ' + Analysis.LastSourcePos);
 end;

end;

procedure Test;
var
  i : Integer;
  StringList : TStringList;

  procedure TestIndexOf;
  var
    i : Integer;
    Index : Integer;
    Failures : Integer;
    S : String;
  begin
    Failures := 0;
    for i := 1 to Rows do begin
      S := ZeroPad(i, StrLen);
      Index := StringList.IndexOf(S);
      if Index < 0 then
        Inc(Failures);
    end;
    Assert(Failures = 0);
  end;

  procedure TestBinSearch;
  var
    i : Integer;
    Index : Integer;
    Failures : Integer;
    S : String;
  begin
    Failures := 0;
    for i := 1 to Rows do begin
      S := ZeroPad(i, StrLen);
      Index := BinSearch(StringList, S);
      if Index < 0 then
        Inc(Failures);
    end;
    //Assert(Failures = 0);
  end;

begin
  StringList := GetList(True);
  TestIndexOf;
  TestBinSearch;
  StringList.Free;

  StringList := GetList(False);
  TestIndexOf;
  TestBinSearch;
  StringList.Free;
end;

begin
  Test;
end.

更新我在维基百科文章https://en.wikipedia.org/wiki/Binary_search_algorithm中编写了我自己的搜索算法实现,如下所示:

function BinSearch(slList: TStringList; sToFind : String) : integer;
var
  L, R, m : integer;
begin
  L := 0;
  R := slList.Count - 1;
  if R < L then begin
    Result := -1;
    exit;
  end;

  m := (L + R) div 2;
  while slList.Strings[m] <> sToFind do begin
    m := (L + R) div 2;
    if CompareText(slList.Strings[m], sToFind) < 0 then
      L := m + 1
    else
      if CompareText(slList.Strings[m], sToFind) > 0 then
        R := m - 1;
    if L > R then
      break;
  end;

  if slList.Strings[m] = sToFind then
    Result := m
  else
    Result := -1;
end;

这似乎工作正常,并使用这个重新分析测试应用程序给出了这些结果:

Line    Total Time  Source
113     procedure Test;
153    0.000490 begin
154 3020.588894   StringList := GetList(True);
155 2892.860499   TestIndexOf;
156 1143.722379   TestBinSearch;
157   29.612898   StringList.Free;
158     
159 2991.241659   StringList := GetList(False);
160 2934.778847   TestIndexOf;
161 1113.911083   TestBinSearch;
162   30.069241   StringList.Free;

在该显示中,二进制搜索明显优于TStringList.IndexOf,与我的期望相反,在添加字符串之前或之后,TStringList.Sorted是否设置为True没有任何实际区别。

更新#2 结果表明,BinSearchTStringList.IndexOf快的原因纯粹是因为BinSearch使用CompareText而{{1}使用TStringList.IndexOf(通过AnsiCompareText)。如果我将.Find更改为使用BinSearch,则会比AnsiCompareText慢1.6倍!

答案 1 :(得分:0)

我打算建议使用插入器类来直接更改FSorted字段而不调用其setter方法,该方法作为副作用调用Sort方法。但是看看Delphi 2007中TStringList的实现,我发现Find总是在不检查Sorted属性的情况下进行二进制搜索。如果列表项没有排序,这当然会失败,但在你的情况下它们是。所以,只要你记得打电话给Find而不是IndexOf,你就不需要做任何事情。

答案 2 :(得分:-1)

最后,我只是修改了二进制搜索来检查字符串列表,如数组:

Function BinSearch(slList: TStringList; sToFind : String) : integer;
var 
 i, j, k  : integer;
begin
 try
  try 
    i := slList.Count div 2;    
    k := i;
    if i = 0 then
    begin
      Result := -1;
      SpendLog('BinSearch List Empty, Exiting...');
      exit;
    end;

while slList.Strings[i] <> sToFind do
begin  
  if CompareText(slList.Strings[i], sToFind) < 0 then  
  begin    
    j := i; 
    k := k div 2;
    i := i + k;
    if j=i then
      break; 
  end else
  if CompareText(slList.Strings[i], sToFind) > 0 then 
  begin
    j := i;
    k := k div 2;
    i := i - k;
    if j=i then
      break; 
  end else
    break;
end;

if slList.Strings[i] = sToFind then
  result := i
else
  Result := -1;


except
    SpendLog('<BinSearch> Exception: ' + ExceptionMessage + ' At Line: ' + Analysis.LastSourcePos);

  end;

 finally

 end;  


end;

如果需要,我会在以后清理它。