Delphi TStringList是否启用二进制搜索而不诉诸?

Delphi TStringList是否启用二进制搜索而不诉诸?,delphi,delphi-xe2,Delphi,Delphi Xe2,我正在从ADO查询构建一个stringlist,在查询中,返回排序结果并按顺序添加它们要快得多。这会给我一个已经排序的列表,然后调用Sort或设置sorted true会花费我很多时间,因为快速排序算法在已经排序的列表上执行得不好。有没有办法设置TStringList以使用二进制搜索而不运行排序? 在您询问之前,我无权访问CustomSort属性 假设StringList所需的排序顺序与AdoQuery的order BY相同,我不确定我是否理解您所担心的问题 当然,要做的事情是在StringLi

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

假设StringList所需的排序顺序与AdoQuery的order BY相同,我不确定我是否理解您所担心的问题

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

鉴于您的评论和您自己的答案,我通过NexusDB质量套件中的测线计时器分析器运行了下面的程序。结果是,尽管使用二进制搜索与
TStringList.IndexOf
在执行速度上存在明显差异,但它们与
TStringList
的快速排序的使用(或不使用)无关。此外,我使用的二进制搜索方式与
TStringList.Find
work中的二进制搜索方式之间的细微差异可以解释这种差异-请参见下面的更新#2

程序生成200K100个字符串,然后将它们插入到字符串列表中。StringList以两种方式生成,首先在添加任何字符串之前将Sorted设置为True,然后仅在添加字符串之后将Sorted设置为True<然后使用code>StringList.IndexOf和您的
bin搜索
查找已添加的每个字符串。结果如下:

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.
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;
我遇到的问题是,您的
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.
在这一点上,二进制搜索的性能明显优于
TStringList.IndexOf
,与我的预期相反,在添加字符串之前或之后将
TStringList.Sorted
设置为True并没有真正的区别


更新#2原来原因是
BinSearch
TStringList快。IndexOf
纯粹是因为
BinSearch
使用
CompareText
TStringList.IndexOf
使用
AnsiCompareText
(通过
.Find
)。如果我将
BinSearch
更改为使用
AnsiCompareText
,它将比
TStringList.IndexOf
慢1.6倍

最后,我刚刚完成了一个二进制搜索,以像数组一样检查字符串列表:

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;
函数bin搜索(slList:TStringList;sToFind:String):整数;
变量
i、 j,k:整数;
开始
尝试
尝试
i:=slList.Count div 2;
k:=i;
如果i=0,那么
开始
结果:=-1;
SpendLog('bin搜索列表为空,正在退出…');
出口
结束;
而slList.Strings[i]sToFind do
开始
如果CompareText(slList.Strings[i],sToFind)<0,则
开始
j:=i;
k:=k第2部分;
i:=i+k;
如果j=i,那么
打破
结束其他
如果CompareText(slList.Strings[i],sToFind)>0,则
开始
j:=i;
k:=k第2部分;
i:=i-k;
如果j=i,那么
打破
结束其他
打破
结束;
如果slList.Strings[i]=sToFind,则
结果:=i
其他的
结果:=-1;
除了
SpendLog('Exception:'+ExceptionMessage+'位于第行:'+Analysis.LastSourcePos);
结束;
最后
结束;
结束;

如果需要的话,我稍后会清理这个问题。

我打算建议使用一个interposer类直接更改FSorted字段,而不调用其setter方法,作为副作用,setter方法调用Sort方法。但是在Delphi2007中查看TStringList的实现时,我发现Find总是执行二进制搜索,而不检查排序属性。当然,如果列表项没有排序,这将失败,但在您的情况下,它们是排序的。因此,只要您记得调用Find而不是IndexOf,您就不需要做任何事情。

TStringLsit
速度很慢,我认为您最好使用
TList
,它具有二进制搜索功能:另一个可能更快的选项是
TDictionary
是,但是,以这种方式插入比插入到未排序的列表需要更长的时间,而且ADO查询order by的排序方式与stringlist的排序方式相同。使用我的200k测试数据库,插入到排序的列表需要约2000毫秒,而插入到未排序的列表需要约1500毫秒。插入排序正在消耗这额外的500毫秒为什么
TStringList.Find
工作得非常好(如果列表已排序,则使用二进制搜索),并且二进制搜索仅在项目已排序的情况下才有意义。您使用
CompareText
两次也不是很理想:只使用
CompareText
一次,将结果保存在变量中,然后决定保存的值。@rudyvelthui这是正确的,但我需要先排序。这需要时间。如果我有权访问自定义排序id,请实现一种比排序列表上的快速排序更快的算法。我在测试您的BinSearch时发现一个错误,但它仍然会在200k中重复68000多次失败。我已经更新了我的答案,添加了对Wikipedia二进制搜索实现的测试,它返回0个失败。嗯,我尝试了这个,但Find似乎返回0或列表大小,而不是负结果,这可能会导致问题。如果找不到条目,函数将返回false。out参数Idx将是inde
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;