真正快速的功能来比较两个文件的名称(完整路径)

时间:2010-09-09 11:58:25

标签: delphi

我必须检查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的)视觉控制很慢!

8 个答案:

答案 0 :(得分:13)

不要浪费时间优化汇编程序。您可以从O(n 2 )到O(n log(n)) - 将时间缩短到毫秒 - 通过对列表进行排序,然后对重复项进行线性扫描。

当你在它时,忘记SameFile函数。算法改进将使你在那里实现的任何事情都相形见绌。

编辑:根据评论中的反馈......

您可以按如下方式执行保留订单的O(n log(n))重复数据删除:

  1. 对列表的副本进行排序。
  2. 识别并将重复的条目复制到第三个列表及其复制计数减一。
  3. 按照原始版本向后走原始列表。
  4. 在内部(for y := ...)循环中,遍历重复列表。如果外部项匹配,则删除它,减少重复计数,如果计数达到零,则删除重复项。
  5. 这显然更复杂,但它仍然会快几个数量级,即使你做了可怕的脏事,比如将重复计数存储为字符串,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;对该列表中的字符串索引进行重复数据删除(因为除了第一个索引之外的所有索引都将出现两次)。完成后,您应该再次对列表进行排序,但这次会对字符串内容进行排序,以便在后续单次扫描中轻松找到重复项。

当然,这样做可能有点极端,但至少对于非常大的音量来说这是一个可行的解决方案! (哦,如果重复次数非常高,当哈希函数的差值很大,或者'哈希表'位集中的槽数被选择得太小时,这仍然不会起作用 - 这会产生很多冲突这些并不重复。)