Vik*_*els 6 delphi case-sensitive natural-sort delphi-7
我想订购一个包含不同选项的字符串列表.选项包括:
我有所有分支机构除外:
区分大小写,逻辑排序.
(非常多来自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这样的第三方工具.我的主要参考点目前是这个链接:
我完成了一个可以处理正数和负数的解决方案。但并非所有 Unicode 解决方案所需的 natsort 功能都已实现,但它应该足以满足通用排序的需要。
\n\n代码:
\n\nunit 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;\nRun Code Online (Sandbox Code Playgroud)\n\n对所有的子程序不是很满意,但每个人都可以按照自己的意愿进行操作。
\n