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