我必须检查FileListBox中是否有重复的路径(FileListBox具有某种作业列表或播放列表的作用)。 使用Delphi的SameText,CompareStr,CompareText需要6秒。所以我带了我自己的比较功能,它(更快)但速度不够快。任何想法如何改进它?
function SameFile(CONST Path1, Path2: string): Boolean;
VAR i: Integer;
begin
Result:= Length(Path1)= Length(Path2); { if they have different lenghts then obviously are not the same file }
if Result then
for i:= Length(Path1) downto 1 DO { start from the end because it is more likely to find the difference there }
if Path1[i]<> Path2[i] then
begin
Result:= FALSE;
Break;
end;
end;
我这样用:
for x:= JList.Count-1 downto 1 DO
begin
sMaster:= JList.Items[x];
for y:= x-1 downto 0 DO
if SameFile(sMaster, JList.Items[y]) then
begin
JList.Items.Delete (x); { REMOVE DUPLICATES }
Break;
end;
end;
注意:重复的可能性很小,因此不经常调用Delete。此外,列表无法排序,因为项目是由用户添加的,有时订单可能很重要。
更新
问题是我失去了代码的优势,因为它是Pascal。
如果比较循环(Path1 [i]&lt;&gt; Path2 [i])将被优化以使用Borland的ASM代码将是很好的。
Delphi 7,Win XP 32位,测试完成了列表中的577项。从列表中删除项目 不是问题 ,因为它很少发生。
结论
正如Svein Bringsli指出的那样,我的代码很慢,不是因为比较算法,而是因为TListBox。最佳解决方案由Marcelo Cantos提供。非常感谢Marcelo
我接受了Svein的答案,因为它直接回答了我的问题“如何使我的比较功能更快”,“没有必要让它更快”。
暂时我实现了脏和快速实现解决方案:当我有200个文件时,我使用慢速代码检查重复项。如果有超过200个文件,我使用dwrbudr的解决方案(这是快死的),考虑到如果用户有这么多文件,那么顺序无关紧要(人脑无法跟踪这么多项目)。
我想感谢大家的想法,特别是Svein揭露真相:( Borland的)视觉控制很慢!
答案 0 :(得分:13)
不要浪费时间优化汇编程序。您可以从O(n 2 )到O(n log(n)) - 将时间缩短到毫秒 - 通过对列表进行排序,然后对重复项进行线性扫描。
当你在它时,忘记SameFile函数。算法改进将使你在那里实现的任何事情都相形见绌。
编辑:根据评论中的反馈......
您可以按如下方式执行保留订单的O(n log(n))重复数据删除:
for y := ...
)循环中,遍历重复列表。如果外部项匹配,则删除它,减少重复计数,如果计数达到零,则删除重复项。这显然更复杂,但它仍然会快几个数量级,即使你做了可怕的脏事,比如将重复计数存储为字符串,C:\path1\file1=2
,并使用如下代码:
y := dupes.IndexOfName(sMaster);
if y <> -1 then
begin
JList.Items.Delete(x);
c := StrToInt(dupes.ValueFromIndex(y));
if c > 1 then
dupes.Values[sMaster] = IntToStr(c - 1);
else
dupes.Delete(y);
end;
旁注:二进制切割比for y := ...
循环更有效,但鉴于重复次数很少,差异应该可以忽略不计。
答案 1 :(得分:9)
使用您的代码作为起点,我在搜索重复项之前修改了它以获取列表的副本。时间从5,5秒到大约0.5秒。
vSL := TStringList.Create;
try
vSL.Assign(jList.Items);
vSL.Sorted := true;
for x:= vSL.Count-1 downto 1 DO
begin
sMaster:= vSL[x];
for y:= x-1 downto 0 DO
if SameFile(sMaster, vSL[y]) then
begin
vSL.Delete (x); { REMOVE DUPLICATES }
jList.Items.Delete (x);
Break;
end;
end;
finally
vSL.Free;
end;
显然,这不是一个好方法,但它表明TFileListBox本身很慢。我不相信你可以通过优化比较函数来获得更多收益。
为了证明这一点,我用以下代码替换了你的SameFile函数,但保留了你的其余代码:
function SameFile(CONST Path1, Path2: string): Boolean;
VAR i: Integer;
begin
Result := false; //Pretty darn fast code!!!
end;
时间从5,6秒到5.5秒。我不认为在那里获得更多: - )
答案 2 :(得分:1)
使用sortedList.Duplicates创建另一个排序列表:= dupIgnore并将字符串添加到该列表中,然后再返回。
vSL := TStringList.Create;
try
vSL.Sorted := true;
vSL.Duplicates := dupIgnore;
for x:= 0 to jList.Count - 1 do
vSL.Add(jList[x]);
jList.Clear;
for x:= 0 to vSL.Count - 1 do
jList.Add(vSL[x]);
finally
vSL.Free;
end;
答案 3 :(得分:1)
绝对最快的方式,bar none(如之前提到的)是使用为字符串生成唯一的64/128/256位哈希码的例程(我在C#中使用SHA256Managed类)。运行字符串列表,生成字符串的哈希码,在排序的哈希码列表中检查它,如果找到则字符串是重复的。否则,将哈希码添加到已排序的哈希码列表中。
这适用于字符串,文件名,图像(您可以获取图像的唯一哈希码)等,我保证这将比任何其他实现更快或更快。
PS您可以通过将哈希码表示为字符串来使用字符串列表作为哈希码。我过去使用过十六进制表示法(256位 - > 64个字符),但从理论上讲,你可以按照自己喜欢的方式进行。
答案 4 :(得分:0)
拨打多少电话4秒?如果你称之为十亿次,表现出色......
无论如何,每次循环都会评估Length(Path1)吗?如果是这样,请在循环之前将其存储在Integer变量中。
指针可能会比字符串产生一些速度。
尝试使用以下函数内嵌函数: function SameFile(blah blah):Boolean;内联;
如果每秒调用数千次,那将节省一些时间。我会从那开始,看看它是否可以保存任何东西。
编辑:我没有意识到你的列表没有排序。显然,你应该先做到这一点!然后,您不必与列表中的所有其他项目进行比较 - 只是前一个或下一个项目。答案 5 :(得分:0)
我使用修改后的三元搜索树(TST)来重复列表。您只需将项目加载到树中,使用整个字符串作为键,并且在每个项目上,如果键已经存在,您可以返回指示(并删除您的可见条目)。然后扔掉树。我们的TST加载功能通常可以在一秒钟内加载100000个80字节的项目。并且通过正确使用开始和结束更新,重新绘制列表并不需要更多。 TST是需要大量内存的,但并非如此,如果你只拥有500个项目,你就会注意到它。比排序,比较和汇编更简单(当然,如果你有合适的TST实现)。
答案 6 :(得分:0)
不需要使用哈希表,单个排序列表给我10毫秒的结果,即0.01秒,这大约快500倍!这是我使用TListBox的测试代码:
procedure TForm1.Button1Click(Sender: TObject);
var
lIndex1: Integer;
lString: string;
lIndex2: Integer;
lStrings: TStringList;
lCount: Integer;
lItems: TStrings;
begin
ListBox1.Clear;
for lIndex1 := 1 to 577 do begin
lString := '';
for lIndex2 := 1 to 100 do
if (lIndex2 mod 6) = 0 then
lString := lString + Chr(Ord('a') + Random(2))
else
lString := lString + 'a';
ListBox1.Items.Add(lString);
end;
CsiGlobals.AddLogMsg('Start', 'Test', llBrief);
lStrings := TStringList.Create;
try
lStrings.Sorted := True;
lCount := 0;
lItems := ListBox1.Items;
with lItems do begin
BeginUpdate;
try
for lIndex1 := Count - 1 downto 0 do begin
lStrings.Add(Strings[lIndex1]);
if lStrings.Count = lCount then
Delete(lIndex1)
else
Inc(lCount);
end;
finally
EndUpdate;
end;
end;
finally
lStrings.Free;
end;
CsiGlobals.AddLogMsg('Stop', 'Test', llBrief);
end;
答案 7 :(得分:0)
我还想指出,如果应用于庞大的列表(例如包含100,000,000个或更多项目),您的解决方案将花费极长的时间。即使构建哈希表或排序列表也需要花费太多时间。
在这种情况下你可以尝试另一种方法:散列每个成员,但不是填充一个完整的哈希表,而是创建一个bitset(大到足以包含与输入项一样多的插槽的紧密因子)并且只是将每个位设置为由散列函数指示的偏移量。如果该位为0,则将其更改为1.如果它已经为1,请在单独的列表中记下有问题的字符串索引并继续。这会导致在哈希中发生冲突的字符串索引列表,因此您必须再次运行它才能找到这些冲突的第一个原因。在那之后,你应该排序&amp;对该列表中的字符串索引进行重复数据删除(因为除了第一个索引之外的所有索引都将出现两次)。完成后,您应该再次对列表进行排序,但这次会对字符串内容进行排序,以便在后续单次扫描中轻松找到重复项。
当然,这样做可能有点极端,但至少对于非常大的音量来说这是一个可行的解决方案! (哦,如果重复次数非常高,当哈希函数的差值很大,或者'哈希表'位集中的槽数被选择得太小时,这仍然不会起作用 - 这会产生很多冲突这些并不重复。)