Delphi中是否存在区分大小写的自然排序功能?

Vik*_*els 6 delphi case-sensitive natural-sort delphi-7

我想订购一个包含不同选项的字符串列表.选项包括:

  1. 按字母顺序排序或逻辑排序
  2. 区分大小写或不区分大小写
  3. 升序或降序

我有所有分支机构除外:

区分大小写,逻辑排序.
(非常多来自php的NatSort)

现在我正在尝试找到一个能够满足我需要的功能.

为了得到一个不区分大小写的逻辑顺序,我实现了对shlwapi.dll中的StrCmpLogicalW-Function的调用

https://docs.microsoft.com/en-us/windows/desktop/api/shlwapi/nf-shlwapi-strcmplogicalw

但是,我找不到与StrCmpLogicalW等效的区分大小写.

我复制了一个看起来很有希望从另一个在线棋盘上玩的功能,并且使用Flags.

原始功能:

  function NatCompareText(const S1, S2: WideString): Integer;
  begin
    SetLastError(0);
    Result:=CompareStringW(LOCALE_USER_DEFAULT,
                           NORM_IGNORECASE or
                           NORM_IGNORENONSPACE or
                           NORM_IGNORESYMBOLS,
                           PWideChar(S1),
                           Length(S1),
                           PWideChar(S2),
                           Length(S2)) - 2;
    case GetLastError of
      0: ;
      //some ErrorCode-Handling
    else
      RaiseLastOSError;
    end;
  end; 
Run Code Online (Sandbox Code Playgroud)

来自:https: //www.delphipraxis.net/29910-natuerliche-sortierungen-von-strings.html

我试图删除Ignore-Case标志,但无济于事.

这就是我想要的结果:http: //php.fnlist.com/array/natsort

   Input:   array("Img12.png", "iMg10.png", "Img2.png", "Img1.png")
   Output:  array("Img1.png", "Img2.png", "Img12.png", "iMg10.png")
Run Code Online (Sandbox Code Playgroud)

而不是:http: //php.fnlist.com/array/natcasesort

   Input:   array("Img12.png", "iMg10.png", "Img2.png", "Img1.png")
   Output:  array("Img1.png", "Img2.png", "iMg10.png", "Img12.png")
Run Code Online (Sandbox Code Playgroud)

更新:

我已经为区分大小写的自然分类完成了第一个非常简单的解决方案.

我这样做的原因是因为我想在多个列上对Stringgrid进行排序,并为每个指定的列提供不同的选项.

为了实现natsort,我将字符串解析为字符部分和数字部分,并将每个部分存储在字符串列表中.

两个列表都遵循模式('character-part','Numerical part','Character part',...等等).

在分割字符串后,我将列表条目相互比较. - 数字部分相互减去(num1-num2) - 用于字符串比较我使用CompareStr而不是AnsiCompareStr,因为它产生与我上面链接的php-natsort函数相同的输出.

如果在任何时候,比较的结果与0不同,那么就不需要进一步的比较,我就逃避了循环.

在我看来,解决方案尚未完成,因为自然分类的主题非常广泛,至少认识到负数仍然需要实施.

一旦我完成,我将在这里发布我的代码给任何想要能够在多列上排序Stringgrids并为每列提供不同选项的人,因为我还无法在线找到这样的代码.

我不能依赖像RegEx这样的第三方工具.我的主要参考点目前是这个链接:

https://natsort.readthedocs.io/en/master/howitworks.html

Vik*_*els 2

我完成了一个可以处理正数和负数的解决方案。但并非所有 Unicode 解决方案所需的 natsort 功能都已实现,但它应该足以满足通用排序的需要。

\n\n

代码:

\n\n
unit MySortUnit;\n\ninterface\nuses\n  Grids\n  ,System\n  ,Classes\n  ,Windows\n  ,SysUtils;\n\ntype\n  TSortOrder=(soAscending,soDescending);     \n  TSortOption=record                         \n    SortOrder:TSortOrder;  //Determines SortOrder in a TSortOption-Record, can be replaced with a Boolean, but I prefer Enums\n    CaseSensitive:Boolean;\n    SortLogical:Boolean;\n  end;\n  TSortOptions=Array of TSortOption;\n\n\nprocedure SortGridByColumns(Grid:TStringGrid; Columns:array of Integer; Options:TSortOptions);\n\nimplementation\n\ntype TMoveSG=class(TCustomGrid);                                            //Deriving the TCustomGrid grants access to "StringGrid.MoveRow(..)".\nprocedure SortGridByColumns(Grid:TStringGrid; Columns:array of Integer; Options:TSortOptions);\ntype\n  TshlwapiStrCmpLogicalW=function(psz1, psz2: PWideChar):Integer; stdcall;  //Declare new Functiontype so I can use variables of that type, naming-convention T+{Dll-Name}+{Procedure-Name in DLL}\nvar\n  i,j:Integer;\n  InternalColumns:Array of Integer;\n  InternalOptions:TSortOptions;\n  Sorted:Boolean;\n  shlwapi:HMODULE;\n  StrCmpLogicalW:TshlwapiStrCmpLogicalW;  //Get Procedure from DLL at runtime\n\n////////////////////////////////////////////////////////////////////////////////\n  function StringCompareLogicalCaseInsensitiveASC(const String1,String2:String):Integer;\n  begin\n    Result:=StrCmpLogicalW(PWideChar(WideString(String1)),PWideChar(WideString(String2)));\n  end;\n\n  function StringCompareLogicalCaseInsensitiveDESC(const String1,String2:String):Integer;\n  begin\n    Result:=-1*StrCmpLogicalW(PWideChar(WideString(String1)),PWideChar(WideString(String2)));\n  end;\n\n\n  function StringCompareCaseInsensitiveASC(const String1,String2:String):Integer;\n  begin\n    Result:=AnsiCompareText(String1,String2);\n  end;\n\n  function StringCompareCaseInsensitiveDESC(const String1,String2:String):Integer;\n  begin\n    Result:=-1*AnsiCompareText(String1,String2);\n  end;\n\n\n\n\n  function StringCompareCaseSensitiveASC(const String1,String2:String):Integer;\n  begin\n    Result:=AnsiCompareStr(String1,String2);\n  end;\n\n  function StringCompareCaseSensitiveDESC(const String1,String2:String):Integer;\n  begin\n    Result:=-1*AnsiCompareStr(String1,String2);\n  end;\n\n\n  function StringCompareLogicalCaseSensitiveASC(const String1,String2:String):Integer;\n  const\n    Digits:set of char=[\'0\'..\'9\'];\n    Signs:set of char=[\'-\',\'+\'];\n  var\n    i,l1,l2:Integer;\n    ASign,c:Char;\n    Int1,Int2:Integer;\n    sl1,sl2:TStringList;\n    s:String;\n  begin\n    l1:=length(String1);\n    l2:=length(String2);\n\n    sl1:=TStringList.Create();\n    sl2:=TStringList.Create();\n    try\n      for i:=1 to l1 do\n      begin\n        c:=String1[i];\n\n        if (c in Digits) and (sl1.Count=0) then\n        begin\n          sl1.Add(\'\');\n          sl1.Add(c);\n        end\n        else if not(c in Digits) and (sl1.Count=0) then sl1.Add(c)\n        else\n        begin\n\n          if c in Digits then\n          begin\n            s:=sl1[sl1.Count-1];\n            if s[length(s)] in Signs then\n            begin\n              ASign:=s[length(s)];\n              Delete(s,length(s),1);\n            end\n            else ASign:=#0;\n\n            if TryStrToInt(sl1[sl1.Count-1],Int1)=True then sl1[sl1.Count-1]:=sl1[sl1.Count-1]+c\n            else\n            begin\n              sl1[sl1.Count-1]:=s;\n              if ASign=#0 then sl1.Add(c) else sl1.Add(ASign+c);\n            end;\n          end\n          else\n          begin\n            if TryStrToInt(sl1[sl1.Count-1],Int1)=false then sl1[sl1.Count-1]:=sl1[sl1.Count-1]+c else sl1.Add(c)\n          end;\n        end;\n      end;\n\n      for i:=1 to l2 do\n      begin\n        c:=String2[i];\n\n        if (c in Digits) and (sl2.Count=0) then\n        begin\n          sl2.Add(\'\');\n          sl2.Add(c);\n        end\n        else if not(c in Digits) and (sl2.Count=0) then sl2.Add(c)\n        else\n        begin\n\n          if c in Digits then\n          begin\n            s:=sl2[sl2.Count-1];\n            if s[length(s)] in Signs then\n            begin\n              ASign:=s[length(s)];\n              Delete(s,length(s),1);\n            end\n            else ASign:=#0;\n\n            if TryStrToInt(sl2[sl2.Count-1],Int1)=True then sl2[sl2.Count-1]:=sl2[sl2.Count-1]+c\n            else\n            begin\n              sl2[sl2.Count-1]:=s;\n              if ASign=#0 then sl2.Add(c) else sl2.Add(ASign+c);\n            end;\n          end\n          else\n          begin\n            if TryStrToInt(sl2[sl2.Count-1],Int1)=false then sl2[sl2.Count-1]:=sl2[sl2.Count-1]+c else sl2.Add(c)\n          end;\n        end;\n      end;\n\n      for i:=0 to Min(sl1.Count,sl2.Count)-1 do\n      begin\n        if (TryStrToInt(sl1[i],Int1)=True) and (TryStrToInt(sl2[i],Int2)=True)\n        then Result:=Int1-Int2\n        else Result:=CompareStr(sl1[i],sl2[i]);\n\n        if Result<>0 then break;\n      end;\n    finally\n      sl1.Free();\n      sl2.Free();\n    end;\n  end;\n\n  function StringCompareLogicalCaseSensitiveDESC(const String1,String2:String):Integer;\n  begin\n    Result:=-1*StringCompareLogicalCaseSensitiveASC(String1,String2);\n  end;\n////////////////////////////////////////////////////////////////////////////////\n\n////////////////////////////////////////////////////////////////////////////////\n  //Determines the Sorting-Function based on the Option provided and returns its result\n  function ExecuteSortLogic(StringRow1,StringRow2:String; ColumOption:TSortOption):Integer;\n  begin\n    if ColumOption.SortLogical=true then                                        //recognize Numbers in String as numbers?\n    begin\n      if ColumOption.CaseSensitive=True then                                    //Does Case-Sensitivity matter?\n      begin\n        if ColumOption.SortOrder=soAscending                                    //Do you want to order ascending or descending?\n        then Result:=StringCompareLogicalCaseSensitiveASC(StringRow1,StringRow2)\n        else Result:=StringCompareLogicalCaseSensitiveDESC(StringRow1,StringRow2);\n      end\n      else\n      begin\n        if ColumOption.SortOrder=soAscending\n        then Result:=StringCompareLogicalCaseInsensitiveASC(StringRow1,StringRow2)\n        else Result:=StringCompareLogicalCaseInsensitiveDESC(StringRow1,StringRow2);\n      end;\n    end\n    else\n    begin\n      if ColumOption.CaseSensitive=True then\n      begin\n        if ColumOption.SortOrder=soAscending\n        then Result:=StringCompareCaseSensitiveASC(StringRow1,StringRow2)\n        else Result:=StringCompareCaseSensitiveDESC(StringRow1,StringRow2)\n      end\n      else\n      begin\n        if ColumOption.SortOrder=soAscending\n        then Result:=StringCompareCaseInsensitiveASC(StringRow1,StringRow2)\n        else Result:=StringCompareCaseInsensitiveDESC(StringRow1,StringRow2);\n      end;\n    end;\n  end;\n\n  //The Sort-Controller-Functions, shifts through the passed columns and sorts as long as Result=0 and the final column of the columns array has not been exceeded\n  function Sort(Row1,Row2:Integer; SortOptions:TSortOptions):Integer;\n  var\n    C:Integer;\n  begin\n    C:=0;\n    Result:=ExecuteSortLogic(Grid.Cols[InternalColumns[C]][Row1],Grid.Cols[InternalColumns[C]][Row2],Options[C]);\n    if Result=0 then\n    begin\n      Inc(C);\n      while (C<=High(InternalColumns)) and (Result=0) do\n      begin\n        Result:=ExecuteSortLogic(Grid.Cols[InternalColumns[C]][Row1],Grid.Cols[InternalColumns[C]][Row2],Options[C]);\n        Inc(C);\n      end;\n    end;\n  end;\n////////////////////////////////////////////////////////////////////////////////\n  //A function to determine if AnInt is already in AnArray, necessary to weed out duplicate Columns\n  function IsIntegerInArray(AnInt:Integer; AnArray:Array of Integer):Boolean;\n  var\n    i:Integer;\n  begin\n    Result:=false;\n    for i:=0 to High(AnArray) do\n    begin\n      Result:=(AnArray[i]=AnInt);\n      if Result=True then break;\n    end;\n  end;\n////////////////////////////////////////////////////////////////////////////////\nbegin\n  //no columns? no Sorting!\n  if length(columns)=0 then exit;\n\n  //Load External Windows Library, shlwapi.dll functions may change in the future\n  shlwapi:=LoadLibrary(\'shlwapi.dll\');\n  try\n    if shlwapi<>0 then  //Loading of Library successfull?\n    begin\n      @StrCmpLogicalW:=GetProcAddress(shlwapi,\'StrCmpLogicalW\'); //Load Function from the DLL\n      if (@StrCmpLogicalW=nil) then exit;  //Loading of Function successfull?\n    end\n    else exit;\n\n    //Check that every element inside the Columns-Array has a corresponding TSortOption-Record, if "Options" is shorter than "Columns", default-options are supplied, if "Options" is longer than "columns", we cut them off\n    if High(Columns)>High(Options) then\n    begin\n      i:=length(Options);\n      setLength(Options,length(Columns));\n      for j:=i to High(Options) do\n      begin\n        Options[i].SortOrder:=soAscending;\n        Options[i].CaseSensitive:=false;\n        Options[i].SortLogical:=false;\n      end;\n    end\n    else if High(Columns)<High(Options) then\n    begin\n      setLength(Options,length(Columns));\n    end;\n    ///////////////////////////////////////////////////////////////////\n\n    //We remove duplicate and invalid Columns and their corresponding TSortOption-record\n    for i:=0 to High(Columns) do\n    begin\n      if (Columns[i]>=0) and (Columns[i]<Grid.ColCount) then                    //Iss column inside the Column-Range?\n      begin\n        if (IsIntegerInArray(Columns[i],InternalColumns)=false) then //Add each column only once           \n        begin\n          setLength(InternalColumns,length(InternalColumns)+1);\n          setLength(InternalOptions,length(InternalOptions)+1);\n          InternalColumns[High(InternalColumns)]:=Columns[i];\n          InternalOptions[High(InternalOptions)]:=Options[i];\n        end;\n      end;\n    end;\n    ///////////////////////////////////////////////////////////////////\n\n    //Make sure the freshly created InternalColumns does neither exceed ColCount nor fall below 1, if length=0 then exit\n    if (High(InternalColumns)>Grid.ColCount-1) then setLength(InternalColumns,Grid.ColCount)\n    else if (length(InternalColumns)=0) then exit;\n\n    //Translating InternalOptions back into Options so I don\'t have to write the functions with InternalOptions, the same does not work for InternalColumns for some reason\n    SetLength(Options,length(InternalColumns));\n    for i:=0 to High(InternalColumns) do Options[i]:=InternalOptions[i];\n\n    j:=0;    //secondary termination condition, should not be necessary\n    repeat\n      Inc(j);\n      Sorted:=True;  //Main termination condition\n\n      for i:=Grid.FixedRows to Grid.RowCount-2 do   //Start at row "FixedRows" since FixedRows nicht bewegt werden k\xc3\xb6nnen und die Eigenschaft nur Werte >=0 haben kann.\n      begin\n        if Sort(i,i+1,Options)>0 then               //Schaut ob Reihe i>als Reihe i+1 ist, falls ja muss i an die Stelle i+1 verschoben werden, das Grid ist also noch nicht sortiert.\n        begin\n          TMoveSG(Grid).MoveRow(i+1,i);\n          Sorted:=False;\n        end;\n      end;\n    until Sorted or (j=1000);\n  finally\n    Grid.Repaint;\n    if shlwapi<>0 then FreeLibrary(shlwapi);        //Speicher freigeben\n    @StrCmpLogicalW:=nil;\n  end;\nend;\n
Run Code Online (Sandbox Code Playgroud)\n\n

对所有的子程序不是很满意,但每个人都可以按照自己的意愿进行操作。

\n