Sorting 在Delphi中对Tstringlist中的数字数据进行数字排序
来自数据库的大量数字数据存储在Tstringlist变量中。数据可能如下所示:Sorting 在Delphi中对Tstringlist中的数字数据进行数字排序,sorting,delphi,rad-studio,Sorting,Delphi,Rad Studio,来自数据库的大量数字数据存储在Tstringlist变量中。数据可能如下所示: 4 4 1/2 12.006 13 3/8 1.05 13.25 5 1/2 2.25 13 5/8 通过将Tstringlist的sort属性设置为true,它们被排序为文本,因此13.25显示在4之前 如何对这些数据进行有效的数字排序 Delphi,Rad Studio 10.4以下是一个解决方案: // Conversion accepting input like '4', '4.003', '4 1/2'
4
4 1/2
12.006
13 3/8
1.05
13.25
5 1/2
2.25
13 5/8
通过将Tstringlist的sort属性设置为true,它们被排序为文本,因此13.25显示在4之前
如何对这些数据进行有效的数字排序
Delphi,Rad Studio 10.4以下是一个解决方案:
// Conversion accepting input like '4', '4.003', '4 1/2' or '1/4'
// No leading or trailing space allowed (use trim if required).
function MyStrToFloat(const S : String) : Double;
var
I, J : Integer;
FS : TFormatSettings;
begin
I := Pos('/', S);
if I > 0 then begin
// We have a fractional part
J := Pos(' ', S);
if J > 0 then
// Both integer and fractional parts
Result := StrToInt(Trim(Copy(S, 1, J - 1)))
else
Result := 0;
Result := Result + StrToInt(Trim(Copy(S, J + 1, I - J - 1))) /
StrToInt(Trim(Copy(S, I + 1)));
end
else begin
FS.DecimalSeparator := '.';
Result := StrToFloat(S, FS);
end;
end;
function StringListSortProc(
List : TStringList;
Index1 : Integer;
Index2 : Integer): Integer;
var
N1, N2: Double;
begin
N1 := MyStrToFloat(List[Index1]);
N2 := MyStrToFloat(List[Index2]);
if N1 > N2 then
Result := 1
else if N1 < N2 then
Result := -1
else
Result := 0;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
SL : TStringList;
S : String;
begin
SL := TStringList.Create;
try
SL.Add('4');
SL.Add('1/2');
SL.Add('4 1/2');
SL.Add('12.006');
SL.Add('13 3/8');
SL.Add('1.05');
SL.Add('13.25');
SL.Add('5 1/2');
SL.Add('2.25');
SL.Add('13 5/8');
Memo1.Lines.Add('Raw:');
for S in SL do
Memo1.Lines.Add(S);
SL.CustomSort(StringListSortProc);
Memo1.Lines.Add('Sorted:');
for S in SL do
Memo1.Lines.Add(S);
finally
SL.Free;
end;
end;
转换是在排序中完成的,这不是很有效。最好使用转换后的值创建一个新列表,然后对其进行排序。或从数据库加载列表时进行转换。您已经有了想法…这里有一个解决方案:
// Conversion accepting input like '4', '4.003', '4 1/2' or '1/4'
// No leading or trailing space allowed (use trim if required).
function MyStrToFloat(const S : String) : Double;
var
I, J : Integer;
FS : TFormatSettings;
begin
I := Pos('/', S);
if I > 0 then begin
// We have a fractional part
J := Pos(' ', S);
if J > 0 then
// Both integer and fractional parts
Result := StrToInt(Trim(Copy(S, 1, J - 1)))
else
Result := 0;
Result := Result + StrToInt(Trim(Copy(S, J + 1, I - J - 1))) /
StrToInt(Trim(Copy(S, I + 1)));
end
else begin
FS.DecimalSeparator := '.';
Result := StrToFloat(S, FS);
end;
end;
function StringListSortProc(
List : TStringList;
Index1 : Integer;
Index2 : Integer): Integer;
var
N1, N2: Double;
begin
N1 := MyStrToFloat(List[Index1]);
N2 := MyStrToFloat(List[Index2]);
if N1 > N2 then
Result := 1
else if N1 < N2 then
Result := -1
else
Result := 0;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
SL : TStringList;
S : String;
begin
SL := TStringList.Create;
try
SL.Add('4');
SL.Add('1/2');
SL.Add('4 1/2');
SL.Add('12.006');
SL.Add('13 3/8');
SL.Add('1.05');
SL.Add('13.25');
SL.Add('5 1/2');
SL.Add('2.25');
SL.Add('13 5/8');
Memo1.Lines.Add('Raw:');
for S in SL do
Memo1.Lines.Add(S);
SL.CustomSort(StringListSortProc);
Memo1.Lines.Add('Sorted:');
for S in SL do
Memo1.Lines.Add(S);
finally
SL.Free;
end;
end;
转换是在排序中完成的,这不是很有效。最好使用转换后的值创建一个新列表,然后对其进行排序。或从数据库加载列表时进行转换。你有这个想法
有两个组件可以解决您的问题:
strcplugical是你的朋友。它使用自然排序-字符串中的数字被视为数字内容而不是文本。
这封信是这样写的
我把它放在下面:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Memo1: TMemo;
ButtonSort: TButton;
Memo2: TMemo;
procedure ButtonSortClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
//Natural sorting - Digits in the strings are considered as numerical content rather than text. This test is not case-sensitive.
function StrCmpLogicalW(P1, P2: PWideChar): Integer; stdcall; external 'Shlwapi.dll';
function StrCmpLogical(const S1, S2: string): Integer;
var
Form1: TForm1;
implementation
{$R *.dfm}
function StrCmpLogical(const S1, S2: string): Integer;
begin
result := StrCmpLogicalW(PChar(S1), PChar(S2));
end;
function CustomNbrSort(List: TStringList; Index1, Index2: Integer): Integer;
begin
result := StrCmpLogical(List[index1], List[index2]);
end;
procedure DoCustomSort(const List : TStringList);
begin
List.CustomSort(CustomNbrSort);
end;
// https://stackoverflow.com/questions/18082644/how-do-i-enter-fractions-in-delphi
function FractionToFloat(const S: string): real;
var
BarPos: integer;
numStr, denomStr: string;
num, denom: real;
begin
BarPos := Pos('/', S);
if BarPos = 0 then
Exit(StrToFloat(S));
numStr := Trim(Copy(S, 1, BarPos - 1));
denomStr := Trim(Copy(S, BarPos + 1, Length(S)));
num := StrToFloat(numStr);
denom := StrToFloat(denomStr);
result := num/denom;
end;
function FullFractionToFloat(S: string): real;
var
SpPos: integer;
intStr: string;
frStr: string;
int: real;
fr: real;
begin
S := Trim(S);
SpPos := Pos(' ', S);
if SpPos = 0 then
Exit(FractionToFloat(S));
intStr := Trim(Copy(S, 1, SpPos - 1));
frStr := Trim(Copy(S, SpPos + 1, Length(S)));
int := StrToFloat(intStr);
fr := FractionToFloat(frStr);
result := int + fr;
end;
procedure TForm1.ButtonSortClick(Sender: TObject);
begin
var MyList : TStrings := TStringList.Create;
try
//MyList.Assign(Memo1.Lines);
var MyFormatSettings : TFormatSettings := FormatSettings; //Make a copy of the global variable;
MyFormatSettings.DecimalSeparator := '.'; //The system's seporator may be a comma ','
for var i : integer := 0 to Memo1.Lines.Count - 1 do begin
var s : string := Memo1.Lines[i];
if not s.Contains('.') then
s := FloatToStr(FullFractionToFloat(s), MyFormatSettings);
MyList.AddObject(s, TObject(i));
end;
TStringList(MyList).CustomSort(CustomNbrSort);
Memo2.Lines.Clear;
for var i : integer := 0 to Memo1.Lines.Count - 1 do
Memo2.Lines.Add(Memo1.Lines[Integer(MyList.Objects[i])]);
finally
MyList.Free;
end;
end;
end.
有两个组件可以解决您的问题:
strcplugical是你的朋友。它使用自然排序-字符串中的数字被视为数字内容而不是文本。
这封信是这样写的
我把它放在下面:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Memo1: TMemo;
ButtonSort: TButton;
Memo2: TMemo;
procedure ButtonSortClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
//Natural sorting - Digits in the strings are considered as numerical content rather than text. This test is not case-sensitive.
function StrCmpLogicalW(P1, P2: PWideChar): Integer; stdcall; external 'Shlwapi.dll';
function StrCmpLogical(const S1, S2: string): Integer;
var
Form1: TForm1;
implementation
{$R *.dfm}
function StrCmpLogical(const S1, S2: string): Integer;
begin
result := StrCmpLogicalW(PChar(S1), PChar(S2));
end;
function CustomNbrSort(List: TStringList; Index1, Index2: Integer): Integer;
begin
result := StrCmpLogical(List[index1], List[index2]);
end;
procedure DoCustomSort(const List : TStringList);
begin
List.CustomSort(CustomNbrSort);
end;
// https://stackoverflow.com/questions/18082644/how-do-i-enter-fractions-in-delphi
function FractionToFloat(const S: string): real;
var
BarPos: integer;
numStr, denomStr: string;
num, denom: real;
begin
BarPos := Pos('/', S);
if BarPos = 0 then
Exit(StrToFloat(S));
numStr := Trim(Copy(S, 1, BarPos - 1));
denomStr := Trim(Copy(S, BarPos + 1, Length(S)));
num := StrToFloat(numStr);
denom := StrToFloat(denomStr);
result := num/denom;
end;
function FullFractionToFloat(S: string): real;
var
SpPos: integer;
intStr: string;
frStr: string;
int: real;
fr: real;
begin
S := Trim(S);
SpPos := Pos(' ', S);
if SpPos = 0 then
Exit(FractionToFloat(S));
intStr := Trim(Copy(S, 1, SpPos - 1));
frStr := Trim(Copy(S, SpPos + 1, Length(S)));
int := StrToFloat(intStr);
fr := FractionToFloat(frStr);
result := int + fr;
end;
procedure TForm1.ButtonSortClick(Sender: TObject);
begin
var MyList : TStrings := TStringList.Create;
try
//MyList.Assign(Memo1.Lines);
var MyFormatSettings : TFormatSettings := FormatSettings; //Make a copy of the global variable;
MyFormatSettings.DecimalSeparator := '.'; //The system's seporator may be a comma ','
for var i : integer := 0 to Memo1.Lines.Count - 1 do begin
var s : string := Memo1.Lines[i];
if not s.Contains('.') then
s := FloatToStr(FullFractionToFloat(s), MyFormatSettings);
MyList.AddObject(s, TObject(i));
end;
TStringList(MyList).CustomSort(CustomNbrSort);
Memo2.Lines.Clear;
for var i : integer := 0 to Memo1.Lines.Count - 1 do
Memo2.Lines.Add(Memo1.Lines[Integer(MyList.Objects[i])]);
finally
MyList.Free;
end;
end;
end.
您可以使用自定义排序过程来执行此操作。在我对的回答中可以找到使用自定义排序的示例。编写代码将非常复杂,因为字符串很难解析为数字。例如,StrToFloat不可能与4 1/2一起工作。您是否有所有可能性的完整列表或规格?本规范对于构建一个以数据为输入并生成浮点数的转换器是必要的,然后将其用于排序。如果您可以对条目进行求值,使f.i.4 1/2求值为4.5,并用结果值替换原始值,然后,您可以使用0将每个条目左填充为固定长度,从而使列表可以准数字排序。是的。你的问题不是关于分类。它是关于将文本解析为数值的。一旦你能做到这一点,排序就变得微不足道了。尽管@KenWhite的评论基本上是正确的,但它其实并不复杂。我认为这几乎是微不足道的,但这只是对单词的吹毛求疵。您可以使用自定义排序过程来完成这项工作。在我对的回答中可以找到使用自定义排序的示例。编写代码将非常复杂,因为字符串很难解析为数字。例如,StrToFloat不可能与4 1/2一起工作。您是否有所有可能性的完整列表或规格?本规范对于构建一个以数据为输入并生成浮点数的转换器是必要的,然后将其用于排序。如果您可以对条目进行求值,使f.i.4 1/2求值为4.5,并用结果值替换原始值,然后,您可以使用0将每个条目左填充为固定长度,从而使列表可以准数字排序。是的。你的问题不是关于分类。它是关于将文本解析为数值的。一旦你能做到这一点,排序就变得微不足道了。尽管@KenWhite的评论基本上是正确的,但它其实并不复杂。我认为这几乎是微不足道的,但这只是对文字的吹毛求疵。是的,这大概就是你要做的。但是您不应该使用会在大型项目中导致问题的全局FormatSettings变量,也不应该硬编码32跳过Copy的最后一个参数。@AndreasRejbrand OK,boss!编辑我的答案:-@hmdknight我的答案对你有帮助吗?你能把它标为接受吗?用我答案左边的勾号。是的,你大概就是这么做的。但是您不应该使用会在大型项目中导致问题的全局FormatSettings变量,也不应该硬编码32跳过Copy的最后一个参数。@AndreasRejbrand OK,boss!编辑我的答案:-@hmdknight我的答案对你有帮助吗?你能把它标为接受吗?使用我答案左边的勾号。它几乎适用于少数例外情况,例如:1.9排序低于1.66,2.063排序高于2 3/8和2 7/8@hmdknight史崔克不是你的朋友。只需使用左填充设置数字格式,并使用常规字符串排序。它几乎适用于少数例外情况,如:1.9排序低于1.66,2.063排序高于2 3/8和2 7/8@hmdknight史崔克不是你的朋友。只需使用左填充设置数字格式,并使用常规字符串排序。