【问题标题】:custom sort method in Delphi to sort list of stringsDelphi中的自定义排序方法对字符串列表进行排序
【发布时间】:2013-03-06 20:50:31
【问题描述】:

我正在尝试在 Delphi 中对名称如下所示的文件列表(它们存储为字符串列表)进行排序

a_1.xml
a_20.xml
a_10.xml
a_2.XML

当我对sort文件名使用快速排序时,它对文件名进行如下排序

a_1.xml
a_10.xml
a_2.xml
a_20.XML

但是,我希望文件名按以下方式排序

a_1.xml
a_2.xml
a_10.xml
a_20.XML

任何帮助将不胜感激。

【问题讨论】:

    标签: string delphi sorting delphi-2010


    【解决方案1】:

    您可以使用与 Explorer 相同的比较功能,即StrCmpLogicalW

    function StrCmpLogicalW(psz1, psz2: PWideChar): Integer; stdcall;
      external 'shlwapi.dll';
    
    function StrCmpLogical(const s1, s2: string): Integer;
    begin
      Result := StrCmpLogicalW(PChar(s1), PChar(s2));
    end;
    

    如果您的字符串在 TStringList 实例中,那么您可以使用它的 CustomSort 方法。这需要这种形式的比较函数:

    TStringListSortCompare = function(List: TStringList; 
      Index1, Index2: Integer): Integer;
    

    所以,喂CustomSort这个函数:

    function StringListCompareLogical(List: TStringList; 
      Index1, Index2: Integer): Integer;
    begin
      Result := StrCmpLogical(List[Index1], List[Index2]);
    end;
    

    【讨论】:

    • 在极少数情况下'shlwapi.dll' 被篡改或丢失。那么应用程序将无法运行。
    • @rookie_developer:如果 shlwapi.dll 丢失,用户可能无论如何都无法启动您的程序。它是 Windows shell 的关键部分,StrCmpLogicalW 是随 Windows XP 添加的。主要的警告是它的行为可能会改变(MSDN:注意这个函数的行为,因此它返回的结果,可以从发布到发布。它不应该用于规范排序应用程序。)
    • @GerryColl:我知道'shlwapi.dll' DLL 包含用于 UNC 和 URL 路径、注册表项和颜色设置的函数。为什么篡改此文件会影响应用程序启动?
    • 我认为如果没有它,Windows 资源管理器将无法运行,除非 MS 中有人认为非常需要在没有 Win95 中包含的文件的情况下运行资源管理器
    • @Leadri 您使用的是预Unicode Delphi,因此类型不匹配。将字符串转换为 WideString,然后转换为 PWideChar。所以,StrCmpLogicalW(PWideChar(WideString(s1)), PWideChar(WideString(s2)); 那就叫它 StringList.CustomSort(StringListCompareLogical)
    【解决方案2】:

    根据您的具体情况调整的轻量级解决方案如下:

    function compare(List: TStringList; Index1, Index2: Integer): Integer;
    var
      n1, n2: integer;
    begin
      n1 := StrToInt(Copy(List[Index1], 3, Length(List[Index1]) - 6));
      n2 := StrToInt(Copy(List[Index2], 3, Length(List[Index2]) - 6));
      result := n1 - n2;
    end;
    
    var
      sl: TStringList;
    
    procedure AddAndSort;
    begin
      sl := TStringList.Create;
      sl.Add('a_1.xml');
      sl.Add('a_20.xml');
      sl.Add('a_10.xml');
      sl.Add('a_2.XML');
      sl.CustomSort(compare);
    end;
    

    【讨论】:

    • 如果我已经知道文件名并且数字以外的字符长度始终保持不变,则此方法有效。
    • @rookie_developer:当然。从您的 Q 中,我得到的印象是确实如此。
    【解决方案3】:

    Andreas Rejbrand 的回答是好的。但最好将此比较功能用于一般用途:

    function compare(List: TStringList; Index1, Index2: Integer): Integer;
    begin
      if Length(List[Index1]) = Length(List[Index2]) then
        begin
          if List[Index1] = List[Index2] then
            result := 0
          else
            if List[Index1] < List[Index2] then
              result := -1
            else
              result := 1;
        end
      else
        if Length(List[Index1]) < Length(List[Index2]) then
          result := -1
        else
          result := 1;
    end;
    
    //------------------------------------------------------------------
    
    var sl: TStringList;
    
    procedure AddAndSort;
    begin
      sl := TStringList.Create;
      sl.Add('a_1.xml');
      sl.Add('a_20.xml');
      sl.Add('a_10.xml');
      sl.Add('a_2.XML');
      sl.CustomSort(compare);
    end;
    

    【讨论】:

    • 我认为这会错误地排序 - 较短的字符串总是在较长的字符串之前。
    【解决方案4】:

    几年前我以answer here 写了这篇文章。虽然有点长,但是可以解决问题。

    function GTSmartCompare(List: TStringList; Index1, Index2: Integer): Integer;
    
      procedure ExtractPart(var s: string; out Result: string; out Numbers: Boolean);
      var
        n: integer;
      begin
        Numbers := False;
        n := 1;
        while (s[n] in ['0'..'9']) and (n <= Length(s)) do
          Inc(n);
    
        { n > 1 if there were digits at the start of the string}
        if n > 1 then
        begin
          Result := Copy(s, 1, n - 1);
          Delete(s, 1, n - 1);
          Numbers := True;
        end
        else
        begin
          { No digits }
          n := 1;
          while (not (s[n] in ['0'..'9']) ) and (n <= Length(s)) do
            Inc(n);
    
          if n > 1 then
          begin
            Result := Copy(s, 1, n - 1);
            Delete(s, 1, n - 1);
          end
        end;
      end; //ExtractPart()
    
    
      function CompareNextPart(var s1, s2: string): Integer;
      var
        n1, n2: Boolean;
        p1, p2: string;
      begin
        { Extract the next part for comparison }
        ExtractPart(s1, p1, n1);
        ExtractPart(s2, p2, n2);
    
        { Both numbers? The do a numerical comparison, otherwise alfabetical }
        if n1 and n2 then
          Result := StrToInt(p1) - StrToInt(p2)
        else
          Result := StrIComp(PChar(p1), PChar(p2));
      end; //CompareNextPart()
    
    var
      str1, str2, ext1, ext2: string;
    
    begin
      Result := 0;
      { For 'normal' comparison
        str2 := List[Index1];
        str2 := List[Index2];
        For comparing file names }
    
      ext1 := ExtractFileExt(List[Index1]);
      ext2 := ExtractFileExt(List[Index2]);
      str1 := ChangeFileExt(List[Index1], '');
      str2 := ChangeFileExt(List[Index2], '');
    
      while (str1 <> '') and (str2 <> '') and (Result = 0) do
        Result := CompareNextPart(str1, str2);
    
      { Comparing found no numerical differences, so repeat with a 'normal' compare. }
    
      if Result = 0 then
        Result := StrIComp(PChar(List[Index1]), PChar(List[Index2]));
    
      { Still no differences? Compare file extensions. }
    
      if Result = 0 then
        Result := StrIComp(PChar(ext1), PChar(ext2));
    
    end;
    

    [编辑]

    但是当大卫醒着时,为什么要打扰。 :p 在我的辩护中,当时很多人没有 Windows XP,这是引入 StrCmpLogicalW 的版本。

    【讨论】:

    • 如果您没有 XP,这很公平。但在那种情况下,我会考虑使用 Wine 中的 StrCmpLogicalW 的实现!我希望这将实现真正 Windows 版本的所有细微差别。
    • 当然。这是实际的旧代码。我根本不知道为什么您会支持 XP 之前的版本。另外,我不确定这段代码是否是 unicode 安全的。我把它留在这里是为了获得一些分数——呃——用于教育目的,但使用 StrCmpLogicalW 更有意义。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-08
    • 1970-01-01
    • 2019-05-21
    • 1970-01-01
    • 2015-03-26
    相关资源
    最近更新 更多